Ext JS提供了mvc和mvvm的应用程序框架支持,这两种架构方法都是关注于将应用程序代码和业务逻辑分离。每一种方法都有自己的优点,这取决于怎么分离应用程序模块。下面我们就简单看一下:
什么是mvvm框架?
mvvm简单说来就是在MVC的基础上,新增了一个类似数据仓库的概念。就是说我有了数据模型(model),也有了视图展现(view),还有对其分发处理的控制器(controller)之后,多了一个储蓄所,如下图:
上面的三个模块就是传统的MVC模式,ExtJS在此基础上提供了VM模块。它发出请求,通过我们指定的模型来组装后返回,之后每次有数据请求时,都可以从这里获取数据,减少了服务器的请求次数。
层级结构关系
下面是ExtJS的MVVM框架的目录结构,对于不知道Extjs环境如何部署的朋友,可参考这篇文章《 Extjs5.0集成Eclipse 》。
这里能够很清楚的看到controller、model、store及view三个层次,也就是MVVM的分层目录。这种思想在ExtJS4.X中也有体现,但是在5.X之后添加了新的特性,就是viewmodel和viewcontroller:
这里的viewmodel就是MVVM中的VM,viewcontroller就是在原来controller基础上的演进,目的是实现了动态加载,防止一次性加载过多的js导致的数据阻塞的问题。
上图就展现了login文件夹下三个js文件的关系。Login.js是一个登陆窗体,调用的方法逻辑都在LoginController.js中,项目初始化的参数存储在LoginModel.js,各司其职,非常明了。
下篇就以一个登录的实例来介绍下ExtJS5.0中是如何实现的。
1、校验
对于最基本的校验当然是不能缺少的。这里的校验包括对空值、输入字段的长度及是否正确都进行了校验,
比如如下两图:
2、验证码刷新
如上图中验证码为 R8DR,点击后变为 0PSS。
在后台对验证码进行了大小写处理,由于大些更容易辨认,所以在前台生成时用大些,输入的验证码大小写都是可以的。
3、重置与确定
点击重置后会将输入的内容清空
点击确定后则进入后台页面(目前还是比较丑陋的),这里利用session进行了限制,如果正常登陆后刷新页面是不会重新登陆的。
下一篇螃蟹就登陆的ExtJS代码进行详解,核心代码如下:
- Ext.define('app.view.login.Login', {
- extend: 'Ext.window.Window',
- requires: [
- 'app.view.login.LoginController',
- 'app.view.login.LoginModel',
- 'Ext.form.Panel',
- 'Ext.button.Button',
- 'Ext.form.field.Text',
- 'Ext.form.field.ComboBox'
- ],
- viewModel: 'login',
- controller: 'login',
- title: 'IT学习者-人事管理系统登陆',
- closable: false,
- width : 400,
- height : 230,
- cls: 'login',
- buttonAlign : 'center',
- items:[{
- xtype : "displayfield",
- value : "",
- height:30,
- margin : "0 0 0 0"
- },{
- layout : "column",
- items : [{
- columnWidth:.7,
- xtype: 'form',
- reference: 'form',
- defaults : {
- labelSeparator : ':',
- labelWidth : 60,
- width : 200,
- labelAlign : 'left'
- },
- defaultType : 'textfield',
- items : [ {
- xtype : 'textfield',
- fieldLabel : ' 用户名 ',
- emptyText:"请输入用户名",
- regex : /([A-Za-z]{1})\w{1,19}/,
- regexText : '用户名格式有误',
- name : 'loginName',
- allowBlank : false,
- blankText : '用户名不能为空',
- minLength : 5,
- minLengthText : '用户名的长度为[5-16]',
- maxLength : 16,
- maxLengthText : '用户名的长度为[5-16]',
- margin : "10 10 10 50"
- }, {
- xtype : 'textfield',
- name : 'password',
- inputType : 'password',
- fieldLabel : '密 码',
- fieldCls : 'password',
- emptyText:"请输入密码",
- inputType : 'password',
- allowBlank:false,
- blankText : '密码不能为空',
- minLength : 5,
- minLengthText : '密码的长度为[5-20]',
- maxLength : 20,
- maxLengthText : '密码的长度为[5-20]',
- margin : "15 10 10 50"
- },{
- xtype:'textfield',
- width:120,
- fieldLabel : '验证码',
- name : 'authcode',
- allowBlank:false,
- blankText : '验证码不能为空',
- margin : "15 0 0 50"
- },{
- xtype:'panel',
- columnWidth:.4,
- height:30,
- html:"<a href='#' οnclick='javascript:refreshCode();'><img id='validateCodeImg' title='点击更换' alt='点击更换' src='authCode' /></a>",
- margin : "-26 0 175"
- }]
- },{
- layout:'fit',
- bodyStyle: 'background:transparent',//设置为透明,不不妨碍更换主题了
- columnWidth:.28,
- height:120,
- items:[{
- xtype : "displayfield",
- hideLabel : true,
- margin : "-105 0 0 0",
- value : "<img src='./images/itxxz.png' />"
- }]
- }]
- }],
- buttons: [{
- text: '确定',
- listeners: {
- click: 'onLoginClick'
- }
- },{
- text: "重置",
- handler: function () {
- this.up('window').down('form').getForm().reset();
- }
- }]
- });
- //刷新验证码
- function refreshCode() {
- document.getElementById("validateCodeImg").src = "authCode?"+Math.random();
- }
的
刚开始学习的时候,螃蟹也是一头雾水,但是由于ExtJS5.0的书写及其规范,API相当完整,于是,在学习的时候螃蟹尽量的不脱离其设计思想,在原有的框架基础上进行开发改进。
比如我们定义一个类,就可以通过以下的语法进行定义:
- Ext.define('TextClass', {
- name: 'itxxz',
- value: 'IT学习者'
- });
这样,我们就定义了一个TextClass的类,这个类里有name和value两个属性,以后使用的时候,直接new或者Create就可以了。
就好比上篇中我们定义了登陆窗口,
- Ext.define('app.view.login.Login', {..}
这里的定义就与TextClass的定义不同了,学过java的朋友可能更好理解一些,这里的 app.view.login.Login 其实就是一个文件的路径,而app并非如下图的根路径,而是我们定义的命名空间,具体的定义规则在《Extjs5.0集成Eclipse》中的sencha命令已有介绍。
那么view.login.Login就是文件的路径了,比如我们的登陆窗口是定义的Login.js文件,那么定义这个类的时候,规则就是命名空间加上路径最后以类的名字结尾,这样就可以找到该文件了,就好比我们定义的 app.view.login.Login一样,如下图:
这时候问题就来了,如果我们想定义一个window窗口,该如何下手?
在ExtJS中也引入了集成的思想,这样就避免了我们重复发明轮子的烦恼,想实现什么,只要extend一下就可以了:
- extend: 'Ext.window.Window',
在我们平常写js的时候往往会有很多js的引入来相互配合,这在ExtJS中也有体现,就是通过require来实现:
- requires: [
- 'app.view.login.LoginController',
- 'app.view.login.LoginModel',
- 'Ext.form.Panel',
- 'Ext.button.Button',
- 'Ext.form.field.Text',
- 'Ext.form.field.ComboBox'
- ],
这是我们需要的js文件,通过本篇讲解的类的定义规则,相信应该可以看懂这种命名规范了。
下面我们来看一下特殊的写法:
- viewModel: 'login',
- controller: 'login',
这里是引用viewModel和controller,有关这方面的介绍可看一下《ExtJS5.0的mvvm分层思想》。
通过上图中可以看到,login文件夹下一共定义了三个文件:Login.js、LoginController.js、LoginModel.js 。
Login.js的代码在上一篇已经张贴了出来,下面先简单看下另外的代码:
LoginController.js
- Ext.define('app.view.login.LoginController', {
- extend: 'Ext.app.ViewController',
- alias: 'controller.login',
- loginText: 'Logging in...',
- constructor: function () {
- this.callParent(arguments);
- this.loginManager = new app.LoginManager({});
- },
- onSpecialKey: function(field, e) {
- if (e.getKey() === e.ENTER) {
- this.doLogin();
- }
- },
- onLoginClick: function() {
- this.doLogin();
- },
- doLogin: function() {
- var form = this.lookupReference('form');
- if (form.isValid()) {
- Ext.getBody().mask(this.loginText);
- this.loginManager.login({
- data: form.getValues(),
- scope: this,
- success: 'onLoginSuccess',
- failure: 'onLoginFailure'
- });
- }
- },
- onLoginFailure: function() {
- Ext.getBody().unmask();
- },
- onLoginSuccess: function() {
- Ext.getBody().unmask();
- this.fireViewEvent('login', this.getView(), null, null, this.loginManager);
- }
- });
LoginModel.js
- Ext.define('app.view.login.LoginModel', {
- extend: 'Ext.app.ViewModel',
- alias: 'viewmodel.login',
- // Just some data to seed the process. This might be pulled from a cookie or other
- // in a real app.
- data: {
- defaultOrg: 1,
- username: 'IT学习者-螃蟹'
- }
- });
在上面两贴代码的第三行,都有一个alias属性,它的作用就是为该类起一个别名,用过数据库的朋友对此应该不陌生。
这样再回头看这两行代码应该知文识意了,没错,就是根据别名来引用的。
- viewModel: 'login',
- controller: 'login',
在这样引用后,ExtJS会默认生成这两个文件的对象了。
这篇就先介绍类的定义继承及引用,下篇我们继续分析
这是用于加载数据的请求,有些类似ajax,指定请求的url,请求的字段属性,也就是fields中定义的属性,再一个就是要指定根节点root。
var store_itxxz = Ext.create('Ext.data.Store', { storeId:'simpsonsStore', fields:['empid', 'userName', 'sex'], proxy: { type: 'ajax', url: 'emp/queryAll', reader: { type: 'json', root: 'data' } }, autoLoad: true });
然后就是如何调用的问题
var grid = Ext.create('Ext.grid.Panel', { title: 'Simpsons', store: store_itxxz, columns: [ {header: '登录名', dataIndex: 'userName'}, {header: 'empid', dataIndex: 'empid', flex:1} ], dockedItems: [{ xtype: 'pagingtoolbar', store: store_itxxz, // GridPanel使用相同的数据源 dock: 'bottom', displayInfo: true }] });
这里第3行和第10行的store:store_itxxz中,左边的store是指grid中定义的数据源属性,右边的store_itxxz是指我们数据对象。
第10行是用来加载列表的数据,做显示用,第10行为分页用。
java后台代码:
/** * 员工列表 * @return * @throws Exception */ @RequestMapping("/queryAll") @ResponseBody public Map<String,List<Employee>> queryAllEmps() throws Exception{ log.info("员工列表"); List<Employee> allList = employeeService.selectAllEmps(); Map<String,List<Employee>> map = new HashMap<String,List<Employee>>(); map.put("data", allList); System.out.println(map); return map; }
后台代码中返回的是一个map,以ajax形式返回,是SpringMVC的基本用法,这里就不多介绍了。
第12行,map中put了一个key为data,value为allList的元素,这里的data就是store_itxxz中的root指定的根节点。
也可以修改一下,比如根节点叫做itxxz_data,那么再此处只需要将key改为itxxz_data,store_itxxz中奖root的定义data也改为itxxz_data就可以了。
数据的返回格式如下:
{ data: [ {empid: '001', userName:'IT学习者', sex:'男'}, {empid: '002', userNmae:'螃蟹', sex:'女'} ] }
效果图可参考《Extjs5.0 GridPanel数据动态加载》