ExtJS5学习之MVVC

简介:

      MVVC模式并不是ExtJS首先提出的,其实ExtJS也是模仿微软的WPF中应用的MVVC设计模式。ExtJS在4.0时引入了MVC模式,在5.0时代又引入了MVVC模式。MVC模式对于大家来说应该不陌生了,MVVC是什么?要理解MVVC还是必须先了解MVC是什么?先来张MVC的结构图感受下:



 MVC是一种用来更好组织架构软件的设计模式,它把应用程序划分为3部分,各部分各司其责。

    Model:是用来表示应用程序中需要用到的数据,当然Model层中也可以包含业务逻辑和数据验证规则,以及其他各种功能接口。

   View:是用来展示数据给最终用户的视图,不同的视图可能会以不同的方式展示相同的数据给用户,比如   图表和表格都可以用来展示数据。

   Controller:它是应用程序的控制中心,它监听着应用程序中的各种事件,代理处理Model和View之间的各种命令。比如Model中数据改变了,需要通过Controller来改变View。

   那什么是MVVC呢?MVVC其实是基于MVC设计模式的一种延伸,它与MVC最关键的不同点在于它引入了ViewModel概念,ViewModel提供了Model和View之间的数据绑定,至于数据(Model)更新到视图(View)则是通过ViewController来完成。来张图感受下



 文字太抽象,还是来点代码更形象,有助于理解。

打开Eclipse,新建一个Web project,准备采用最新的MVVC设计模式来编写一个ExtJS5的Grid和Form的数据双向绑定的demo



 
 

 如图导入ExtJS5


 如果主题皮肤文件不知道怎么导入的话,请查看我的第一篇博文《ExtJS5学习之Hello World篇》
开发环境搭好了,开始编写测试代码



 请如图搭好项目结构
首先需要定义一个Application类,当然也可以不用定义,直接Ext.application({name: "appName"});这样写其实就是让ExtJS默认帮我们new一个Application实例,这里说的定义一个Application类其实就是继承ExtJS的Ext.app.Application类,进行一些我们自定义配置来覆盖默认配置,默认配置其实也是可以在Ext.application()创建application实例的运行时去覆盖的,但为了迎合面向对象的开发思想,还是定义一个Application类装装逼吧,显得高大上点。

Application.js

 

Js代码   收藏代码
  1. Ext.define('MyApp.Application', {  
  2.     extend: 'Ext.app.Application',  
  3.     autoCreateViewport: true,  
  4.     enableQuickTips: true,  
  5.       
  6.     launch: function () {  
  7.           
  8.     }  
  9. });  

Ext.define()定义一个类,类似于Java里的public class XXXX, 
extend:继承,你懂的

 

autoCreateViewport即自动帮我们创建ViewPort画布,autoCreateViewport的详细用法,我第一篇博客有详细说明,这里就不累赘了。

enableQuickTips: true 表示启用气泡提示,比如表单验证时,在文本框旁边用气泡方式显示提示信息。

在ExtJS3.x时代,只能这样写Ext.QuickTips.init();来启用,当然ExtJS5.x还是两种写法都兼容的,怎么写看各自喜好,建议采用新的写法,以免在API升级过程中旧的用法会被抛弃。

autoCreateViewport自动创建Viewport,所以我们得定义一个Viewport,

app/view/Viewport.js

 

Js代码   收藏代码
  1. /*********************全局视图容器类************************/  
  2. Ext.define("MyApp.view.Viewport",{  
  3.     extend:'Ext.container.Viewport',  
  4.     requires:['Ext.container.Viewport','MyApp.view.MainPanel'],  
  5.     alias : 'widget.baseviewport',  
  6.     alternateClassName: ["MyApp.Viewport"],  
  7.     layout: 'fit',  
  8.     loadMask:{msg : '正在加载,请稍候...'},  
  9.     items: [  
  10.         {  
  11.             xtype: 'mainpanel'  
  12.         }  
  13.     ]  
  14. });  

 viewport容器里就放了一个mainpanel,mainpanel是别名,下面继续定义一个MainPanel类

 

app/view/MainPanel.js

 

Js代码   收藏代码
  1. Ext.define("MyApp.view.MainPanel", {  
  2.     extend:'Ext.panel.Panel',  
  3.     alias : 'widget.mainpanel',  
  4.     alternateClassName: ["MyApp.MainPanel"],  
  5.     requires: [    
  6.         "Ext.layout.container.Fit",  
  7.         "MyApp.controller.PersonController",  
  8.         "MyApp.viewmodel.PersonViewModel",  
  9.         "MyApp.view.PersonGridPanel",  
  10.         "MyApp.view.PersonFormPanel"  
  11.     ],  
  12.     layout: 'hbox',  
  13.     border: 0,  
  14.     defaults: {  
  15.         flex: 1  
  16.     },  
  17.     controller: "personController",  
  18.     viewModel: {  
  19.         type: "personViewModel"  
  20.     },  
  21.     initComponent: function () {  
  22.         var me = this;  
  23.         me.items = [  
  24.             {  
  25.                 xtype: "personGridPanel"  
  26.             },  
  27.             {  
  28.                 xtype: "personFormPanel"  
  29.             }  
  30.         ];  
  31.         me.callParent(arguments);  
  32.     }  
  33. });  

 MainPanel里采用hbox水平布局,即从左到右这样水平摆放,里面放了两个子组件,personGridPanel和personFormPanel,即左边一个Grid表格右边一个FormPanel表单。

 

requires即导入当前类依赖的其他类,跟Java里的import导包差不多的意思。

controller:这个配置是5.x的MVVC模式里新引入的,其实就是MVC模式里的Controller,只不过这里的Controller的父类不再是Ext.app.Controller,变成了Ext.app.ViewController,
viewModel即当前视图的viewModel实例是什么,viewModel的参数值可以是viewMode的别名字符串,也可以是ViewModel带完整命名空间的类路径的字符串形式,也可以是viewModel的配置实例对象,比如我代码里写的那样,personViewModel是ViewModel类的别名。

app/view/PersonGridPanel.js

 

Js代码   收藏代码
  1. Ext.define("MyApp.view.PersonGridPanel",{  
  2.     extend:'Ext.grid.Panel',  
  3.     requires:[  
  4.         "Ext.grid.plugin.CellEditing",  
  5.         "MyApp.controller.PersonController",  
  6.         "MyApp.viewmodel.PersonViewModel",  
  7.         "MyApp.store.PersonStore"  
  8.     ],  
  9.     alias : 'widget.personGridPanel',  
  10.     alternateClassName: ["MyApp.personGridPanel"],  
  11.     uses: [  
  12.         "Ext.form.field.Text",  
  13.         "Ext.form.field.Number"  
  14.     ],  
  15.     plugins: [  
  16.         {  
  17.             ptype: "cellediting",  
  18.             clickToEdit: 2,  
  19.             pluginId: "cellediting"  
  20.         }          
  21.     ],  
  22.     publishes: ["currentPerson"],  
  23.     bind : {  
  24.         currentPerson: "{currentPerson}",  
  25.         store: "{personStore}",  
  26.         title: "<strong>{currentPerson.personName}</strong>"  
  27.     },  
  28.     config: {  
  29.         currentPerson: null  
  30.     },  
  31.     controller: "personController",  
  32.     viewModel: {  
  33.         type: "personViewModel"  
  34.     },  
  35.     listeners: {  
  36.         scope: "this",  
  37.         select: "onPersonSelect"  
  38.     },  
  39.       
  40.     /**表格标题行**/  
  41.     header: {  
  42.         title: "Person Grid",  
  43.         padding: "4 9 5 9",  
  44.         items: [  
  45.             {  
  46.                 text: "添加",  
  47.                 xtype: "button",  
  48.                 itemId: "add",  
  49.                 handler: "onGridButtonClick"  
  50.             },  
  51.             {  
  52.                 text: "撤消",  
  53.                 xtype: "button",  
  54.                 itemId: "reject",  
  55.                 handler: "onGridButtonClick",  
  56.                 tooltip: "撤消重填",  
  57.                 disabled: true,  
  58.                 margin: "0 0 0 15",  
  59.                 bind: {  
  60.                     disabled: "{!storeDirty}"  
  61.                 }  
  62.             },  
  63.             {  
  64.                 text: "提交",  
  65.                 xtype: "button",  
  66.                 itemId: "commit",  
  67.                 handler: "onGridButtonClick",  
  68.                 tooltip: "提交",  
  69.                 disabled: true,  
  70.                 margin: "0 0 0 15",  
  71.                 bind: {  
  72.                     disabled: "{!storeDirty}"  
  73.                 }  
  74.             }  
  75.         ]  
  76.     },  
  77.     /**表格列头*/  
  78.     columns:[  
  79.         {  
  80.             text: "姓名",  
  81.             width: "50%",  
  82.             dataIndex: "personName",  
  83.             editor: {  
  84.                 xtype: "textfield",  
  85.                 bind: "{currentPerson.personName}"  
  86.             }  
  87.         },  
  88.         {  
  89.             text: "年龄",  
  90.             width: 340,  
  91.             dataIndex: "age",  
  92.             editor: {  
  93.                 xtype: "textfield",  
  94.                 bind: "{currentPerson.age}"  
  95.             }  
  96.         }  
  97.     ],  
  98.       
  99.       
  100.     onPersonSelect: function(grid,person) {  
  101.         this.setCurrentPerson(person);  
  102.         var formPanel = Ext.ComponentQuery.query('personFormPanel')[0];  
  103.         formPanel.setCurrentPerson(person);  
  104.           
  105.     },  
  106.     updateCurrentPerson: function(current,previous) {  
  107.         var sm = this.getSelectionModel();  
  108.         if(current) {  
  109.             sm.select(current);  
  110.         }  
  111.         if(previous) {  
  112.             sm.deselect(previous);  
  113.         }  
  114.     },  
  115. });  

 personGridPanel里代码关键点就几处,我一一说明

 

bind:即数据绑定,把Model数据绑以key-value形式暴露出去,view视图里可以采用{key}

这种表达式来引用Model里的数据。

config:就是把在这里定义的属性自动生成get/set函数,也就是说如果你类里面需要生成get/set函数的属性可以放到config里定义,extjs会自动帮你生成get/set,这个特性在ExtJS4.x时代就有了。

controller:即当前视图的controller是谁,同理这里可以配置成controller类的别名也可以是controller类包含完整命令空间的类路径字符串。不过要记住的是,在MVVC模式里,controller都指的是Ext.app.ViewController,不再是Ext.app.Controller.

ViewModel:即MVVC中的第二个V,ExtJS5.x里的数据双向绑定就是依赖ViewModel,
app/viewmodel/PersonViewModel.js

Js代码   收藏代码
  1. Ext.define("MyApp.viewmodel.PersonViewModel", {  
  2.     extend : "Ext.app.ViewModel",  
  3.     alias: "viewmodel.personViewModel",  
  4.     requires:[  
  5.         "MyApp.store.PersonStore",  
  6.         "MyApp.model.PersonModel"  
  7.     ],  
  8.     data: {  
  9.         currentPerson: null  
  10.     },  
  11.     formulas: {  
  12.         dirty: {  
  13.             bind: {  
  14.                 bindTo: "{currentPerson}",  
  15.                 deep: true  
  16.             },  
  17.             get: function(data) {  
  18.                 console.log(data);  
  19.                 return data ? data.dirty : false;  
  20.             }  
  21.         },  
  22.         storeDirty: {  
  23.             bind: {  
  24.                 bindTo: "{currentPerson}",  
  25.                 deep: true  
  26.             },  
  27.             get: function() {  
  28.                 return this.getStore("personStore").isDirty();  
  29.             }  
  30.         }  
  31.     },  
  32.     stores: {  
  33.         personStore: {  
  34.             type: "personStore"  
  35.         }  
  36.     }  
  37. });   

 viewModel的关键点就是data,stores,

data即当前时刻Model的数据

stores即定义数据源,可以定义多个数据源,personStore数据源的引用别名,可以通过grid.getStore("store引用名")来获取这里的数据源,后面的type是PersonStore定义的别名,即表示这里的Store是哪个类的实例。如果有多个store你可以这样:
stores: {
    aaa: {type: ""xx.xxxx.AA""},

    bbb: {type: ""xx.xxxx.BB""}

}

外部通过getStore("aa"),getStore("bb")这样来获取Store对象,后面的xx.xxx.AA是Store类的完整类路径(包含命名空间)

 

至于formulas是里定义的是一些函数用于绑定按钮禁用状态。

 

app/view/PersonFormPanel.js

Js代码   收藏代码
  1. Ext.define("MyApp.view.PersonFormPanel", {  
  2.     extend: "Ext.form.Panel",  
  3.     alias: "widget.personFormPanel",  
  4.     requires: [  
  5.         "Ext.form.field.Number",  
  6.         "MyApp.controller.PersonController",  
  7.         "MyApp.viewmodel.PersonViewModel"  
  8.     ],  
  9.     controller: "personController",  
  10.     viewModel: {  
  11.         type: "personViewModel"  
  12.     },  
  13.     publishes: ["currentPerson"],  
  14.     /**自动生成get/set*/  
  15.     config: {  
  16.         currentPerson: null  
  17.     },  
  18.   
  19.     bind : {  
  20.         currentPerson: "{currentPerson}",  
  21.         title: "<strong>{currentPerson.personName}</strong>"  
  22.     },  
  23.     bodyPadding: 10,  
  24.     defaultType: "textfield",  
  25.     defaults: {  
  26.         anchor: "100%",  
  27.         selectOnFocus: true  
  28.     },  
  29.     header: {  
  30.         title: "Person Form",  
  31.         padding: "4 9 5 9",  
  32.         items: [  
  33.             {  
  34.                 text: "撤消",  
  35.                 xtype: "button",  
  36.                 itemId: "reject",  
  37.                 handler: "onFormButtonClick",  
  38.                 tooltip: "撤消重填",  
  39.                 disabled: true,  
  40.                 margin: "0 0 0 15",  
  41.                 bind: {  
  42.                     disabled: "{!dirty}"  
  43.                 }  
  44.             },  
  45.             {  
  46.                 text: "提交",  
  47.                 xtype: "button",  
  48.                 itemId: "commit",  
  49.                 handler: "onFormButtonClick",  
  50.                 tooltip: "提交",  
  51.                 disabled: true,  
  52.                 margin: "0 0 0 15",  
  53.                 bind: {  
  54.                     disabled: "{!dirty}"  
  55.                 }  
  56.             }  
  57.         ]  
  58.     },  
  59.     items: [  
  60.         {  
  61.             name: "id",  
  62.             hidden: true,  
  63.             fieldLabel: "",  
  64.             bind: {  
  65.                 value: "{currentPerson.id}"  
  66.             }  
  67.         },  
  68.         {  
  69.             fieldLabel: "姓名",  
  70.             //disabled: true,  
  71.             bind: {  
  72.                 value: "{currentPerson.personName}",  
  73.                 disabled: "{!currentPerson}"  
  74.             }  
  75.         },  
  76.         {  
  77.             fieldLabel: "年龄",  
  78.             //disabled: true,  
  79.             bind: {  
  80.                 value: "{currentPerson.age}",  
  81.                 disabled: "{!currentPerson}"  
  82.             }  
  83.         }  
  84.     ],  
  85.     height: 310  
  86. });  

 PersonFormPanel和PersonGridPanel代码差不多,唯一就是Grid需要绑定Store数据源。

 

app/store/PersonStore.js

Js代码   收藏代码
  1. Ext.define("MyApp.store.PersonStore", {  
  2.     extend : "Ext.data.Store",  
  3.     requires: ["MyApp.model.PersonModel"],  
  4.     model: 'MyApp.model.PersonModel',  
  5.     alias: "store.personStore",  
  6.     storeId: "personStore",  
  7.     pageSize: 10,  
  8.     proxy: {  
  9.         type: 'ajax',  
  10.         url: MyApp.util.AppUtil.basePath + 'person.json',  
  11.         reader: { rootProperty: 'items', totalProperty: 'total' }  
  12.     },  
  13.     reader: {type: 'json'},  
  14.     sorters: [{  
  15.         property: 'id',  
  16.         direction: 'asc'  
  17.     }],  
  18.     autoLoad: true,  
  19.     isDirty: function() {  
  20.         var dirty = this.getModifiedRecords().length;  
  21.         dirty = dirty || this.getNewRecords().length;  
  22.         dirty = dirty || this.getRemovedRecords().length;  
  23.         return !!dirty;  
  24.     }  
  25. });   

 Store就没有什么好说的,关键点就是配置Model类和proxy,proxy数据代理那里我为了简便起见,就没有编写访问数据库代码了,而仅仅是访问一个json文件,store需要的数据都以json字符串的形式定义在person.json文件里。Store是依赖于Model的,所以requires里需要引入PersonModel类。

 

下面贴出person.json里定义的测试数据:

webContent\person.json

纯文本文件代码   收藏代码
  1. {  
  2.     "total"12,  
  3.     "items": [  
  4.         {  
  5.             "id"1,  
  6.             "personName""益达1",  
  7.             "age"28  
  8.         },  
  9.         {  
  10.             "id"2,  
  11.             "personName""益达2",  
  12.             "age"28  
  13.         },  
  14.         {  
  15.             "id"3,  
  16.             "personName""益达3",  
  17.             "age"28  
  18.         },  
  19.         {  
  20.             "id"4,  
  21.             "personName""益达4",  
  22.             "age"28  
  23.         },  
  24.         {  
  25.             "id"5,  
  26.             "personName""益达5",  
  27.             "age"28  
  28.         },  
  29.         {  
  30.             "id"6,  
  31.             "personName""益达6",  
  32.             "age"28  
  33.         },{  
  34.             "id"7,  
  35.             "personName""益达7",  
  36.             "age"28  
  37.         },  
  38.         {  
  39.             "id"8,  
  40.             "personName""益达8",  
  41.             "age"28  
  42.         },  
  43.         {  
  44.             "id"9,  
  45.             "personName""益达9",  
  46.             "age"28  
  47.         },  
  48.         {  
  49.             "id"10,  
  50.             "personName""益达10",  
  51.             "age"28  
  52.         },  
  53.         {  
  54.             "id"11,  
  55.             "personName""益达11",  
  56.             "age"28  
  57.         },  
  58.         {  
  59.             "id"12,  
  60.             "personName""益达12",  
  61.             "age"28  
  62.         }  
  63.     ]  
  64. }  

 PersonModel就是一个普通实体类,就好比Java里的一个普通的JavaBean,仅仅是一些类属性声明;

app/model/PersonModel.js

Js代码   收藏代码
  1. Ext.define("MyApp.model.PersonModel", {  
  2.     extend : "Ext.data.Model",    
  3.     fields : [  
  4.         {name: 'id', type: 'int'},  
  5.         {name: 'personName', type: 'string'},  
  6.         {name: 'age', type: 'int'}  
  7.     ]  
  8. });   

 

编写app.js来创建ExtJS的Application实例对象来运行我们的应用程序,这个文件存放路径没有什么规范约束,不像MVVC模式那样,controller类必须放controller目录下,Store类必须放store目录下。

webContent\app.js

Js代码   收藏代码
  1. Ext.Loader.setConfig({  
  2.     enabled : true  
  3. });  
  4. Ext.Loader.setPath({  
  5.     'Ext.ux' : 'extjs/ux',  
  6.     'MyApp.util' : 'app/util'  
  7. });  
  8.   
  9. /** 
  10.  * 加载ExtJS插件文件 
  11.  */  
  12. Ext.require(  
  13.     [  
  14.         'Ext.ux.PageSizePaging',  
  15.         'MyApp.util.AppUtil'  
  16.     ]  
  17. );  
  18. Ext.application({  
  19.     requires: ['Ext.container.Viewport','MyApp.view.Viewport'],  
  20.     //项目名称简称  
  21.     name: 'MyApp',  
  22.     appFolder: 'app',  
  23.     autoCreateViewport: true,  
  24.       
  25.     launch: function () {  
  26.         //Ext.create('MyApp.view.Viewport');  
  27.     }  
  28. });  

 OK,最后新建一个JSP页面,测试一把,就完事儿了。

webContent\index.jsp

Jsp代码   收藏代码
  1. <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>  
  2. <%  
  3. String path = request.getContextPath();  
  4. String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";  
  5. request.setAttribute("basePath", basePath);  
  6. %>  
  7.   
  8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">  
  9. <html>  
  10.   <head>  
  11.     <base href="<%=basePath%>">  
  12.     <title>ExtJS5-MVVC设计模式学习</title>  
  13.     <meta http-equiv="X-UA-Compatible" content="IE=edge">  
  14.     <meta http-equiv="pragma" content="no-cache">  
  15.     <meta http-equiv="cache-control" content="no-cache">  
  16.     <meta http-equiv="expires" content="0">      
  17.     <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">  
  18.     <meta http-equiv="description" content="This is my page">  
  19.     <!-- 加载ExtJS5  默认的经典蓝主题皮肤样式文件 -->  
  20.     <link href="${basePath}extjs/theme/ext-theme-classic/ext-theme-classic-all.css" rel="stylesheet" type="text/css"/>  
  21.     <script type="text/javascript" src="${basePath}extjs/bootstrap.js"></script>  
  22.     <script type="text/javascript" src="${basePath}extjs/ext-locale-zh_CN.js"></script>  
  23.     <script type="text/javascript" src="${basePath}app.js"></script>   
  24.       
  25.   </head>  
  26.     
  27.   <body>  
  28.         
  29.   </body>  
  30. </html>  

 启动tomcat部署我们的测试项目,如图


 

 

启动Tomcat,打开浏览器,输入http://localhost:8080/extjs5-mvvc/访问页面,你将会看到如图效果:


 OK,今天就写到这儿,如果有什么问题请加我Q-Q:736031305,

或者加裙:一起交流学习

转载:http://iamyida.iteye.com/blog/2182606

目录
相关文章
|
JavaScript 前端开发 API
|
前端开发 JavaScript .NET
|
前端开发 JavaScript Web App开发
|
缓存 JavaScript 存储
|
Web App开发 前端开发 JavaScript
|
JavaScript 前端开发