运用Ext JS 4的MVC架构

Ext JS 4简介

Ext JS 4目前是Sencha的产品,4.x的正式版本号是4.0.7。Ext JS 4提供商业版本,但如果您的项目是开源的,则可以免费使用Ext JS 4。Ext JS的论坛目前非常活跃;Ext JS还在不但地升级改进,据Sencha官方统计,使用Ext JS的开发者数目在一百万以上。

Ext JS 4与之前版本的比较

  • 渲染效率提高
  • 所有类都经过调优,包括最影响渲染效率的布局引擎重写。
  • 命名空间 命名空间是Ext JS 4的MVC的基础,自此Ext JS类能按作用域分开存放了。.NET或Java开发者应该熟悉命名空间带来的好处:命名空间让全类名映射到类文件路径变得很容易,将类按作用域分文件夹存放使得类更容易管理。以MVC为例,Ext JS类将按作用域:模型、视图和控制器分为三类,分别存放于对应文件夹中。
  • 按需加载类

清单 1. 按需加载类的例子

  
  
  
  
  1. Ext.define('MyNamespace.Cat', {   
  2.      requires: ['MyNamespace.BabyCat'],   
  3.      giveBirth: function() {   
  4.          // 实例化 BabyCat 之前,必须加载 BabyCat 的类定义。通过设置“requires”属性,能实现类的按需加载  
  5.          return new MyNamespace.BabyCat();   
  6.      }   
  7.  });   
  8.      

这个特性其实是基于全新设计的类系统的,详见下面的小结。不同于先前版本:即使用到Ext JS框架中很少一部分单元,Ext JS也会加载所有的框架,按需加载只加载需要的类。因此按需加载类为JS优化和减少内存消耗提供了一个有效途径。Sencha为此还提供了SDK工具对JS代码进行Minify,在部署前运行Minify对JS代码最小化后,将得到一个最小JS集合。

  • 全新的类系统 由于篇幅的限制,具体请参阅官方文档Class System,其中详细描述了怎样用Ext JS 4的方式定义类,以及错误处理和调试
  • MVC架构 用Ext JS 4之前的版本写大的客户端应用,您会发现越来越“难”,您会发现有四难:难写、难读、难维护、难扩展。随着越来越多的功能添加进来,代码越来越失控,一个JS文件几千行可能很普遍了,当然也不排除代码组织得很好易于扩展的情况,但这些都需要开发者付出额外的开发代价去组织自己的架构。从 Ext JS 4开始有了自己的 MVC 架构,开发者不必再付出这种额外的代价也能写出漂亮的代码。Ext JS 4对MVC有自己的定义,以下定义来自Sencha官网的文档:
  • Model:一组字段的集合以及它们对应的数据(例如:“User”类 model 有“username”和“password”字段),通过data包 (store,proxy 等 )Model 能序列化自己,并能通过关联关系从一个Model导航到另一个Model。Model的工作原理类似Ext JS 3中的Record 类,通常结合Store为表格控件或其它控件提供显示数据。
  • View:任意组件,如 Grid,Tree和Panel都是视图。
  • Controllers:在这里写所有的逻辑代码:如渲染视图、实例化模型、加载并初始化其它控制器等。

MVC的概念很简单,但实际项目中运用MVC模式将代码组织起来会不会没那么简单?答案在后面的章节“介绍开发Ext JS 4的利器 : Sencha Architect 2”中,该章节会详细介绍怎样用该工具开发MVC模式的Ext JS程序。

为什么要运用MVC架构MVC的概念

MVC是一种成熟的软件设计模式。MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。软件系统通过对自身基本部份分离的同时也赋予了各个基本部分应有的功能。专业人员可以通过自身的专长分组:

  • 控制器 Controller-负责转发请求,对请求进行处理。
  • 视图 View-界面设计人员进行图形界面设计。
  • 模型 Model-程序员编写程序应有的功能(实现算法等等)、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。

MVC模式的特点:

  • 重用性 Ext JS 4中,数据模型、视图组件和存储组件能重复使用,使用时只需将它们的名称作为引用添加到特定的Controller中。
  • 职能分离 模型、视图、控制器以及存储之间的职能分离,使得每个JS文件的职能单一化、最小化。开发人员只需要引用这些必需的职能单元即可构建新的功能。
  • 职责清晰 由于每一个JS单元文件的职责清晰,不同类型的“职责”被划分为不同的组件。在集成开发工具中,开发人员很容易利用这些组件构建自己的应用。
  • 复杂性 运用MVC模式时有一定的复杂性,因为开发者需要付出额外的努力去学习Ext JS 4的MVC框架,但在大型项目中这个付出是值得的。另外Sencha提供了相应的集成开发工具 Sencha Architect协助基于MVC框架的开发,一定程度上减轻了因运用MVC模式带给开发者的压力。

运用MVC以前:

  • 代码组织凌乱 上面的小结中提到Ext JS 4之前的代码通常会碰到四“难”,除此之外,代码很难管理,由于没有分离出类似控制器单元,导致“拷贝、粘贴”过多,应用逻辑代码支离破碎,给调试和测试带来很多不便。
  • 缺乏整体逻辑 由于视图等“静态”代码和事件监听代码混杂在一起,代码功能职责混乱,很多本应作为公用的代码也一起被混进去,导致可扩展性几乎全无,因此为新功能编写新的代码也变得越来越不容易,修改代码更是容易出错。

运用MVC以后:

  • 代码层次结构清晰

图 1.文件结构

运用Ext JS 4的MVC架构_第1张图片

  • JavaScript脚本都按职责存放在不同的JS文件中。
  • 不同的JS文件按MVC目录命名约定"controller"、"store"、"view"、"model"分类存放。
  • 整个工程里只有一个HTML文件,而且只作为入口文件

清单 2.入口文件

  
  
  
  
  1. <html> 
  2.  <head> 
  3.     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  4.     <title>Demo</title> 
  5.     <link rel="stylesheet" type="text/css"   
  6.      href="http://extjs.cachefly.net/ext-4.0.2a/resources/css/ext-all.css"/> 
  7.     <script type="text/javascript"   
  8.      src="http://extjs.cachefly.net/ext-4.0.2a/ext-all-debug.js"></script> 
  9.     <link rel="stylesheet" type="text/css" href="style.css"/> 
  10.     <script type="text/javascript" src="demo.js"></script> 
  11. </head> 
  12.  <body></body> 
  13. </html> 

整个工程仅此一个HTML文件,供十一行,其中demo.js是工程的启动文件,里面包含了所有UI widget的构建,后面会具体介绍。

  • 整体逻辑更加条理

入口定义在Ext.application({...})中,这是Ext JS 4为开发MVC应用新增的函数,我们可以在里面引入初始时所需的Controller、View或Model,在launch函数中编写初始代码,和以前在Ext.onReady()中一样。因为Ext引擎会在Ext.application({...})函数执行时创建一个全局application实例并能在Controller中被引用得到 (this.application.*),所以我们也可以在application中定义一些公共函数,甚至注册一些事件,方便其它Controller调用。

"model"文件夹中只有模型相关的类定义,每个模型定义必须包含字段,还能定义字段校验规则,不同模型之间的关联,以及数据代理(连接服务器存取数据)。比如雇员\部门对应两类模型,体现为两个类文件"Employee"和"Department"。

"view"文件夹中定义了所有的 widget,每一个 widget 对应一个类文件。view 的代码属于静态代码,后面将提到怎样用工具自动生成。

"controller"文件夹中的控制器按管理范围的不同划分为不同的类文件,其中每一个控制逻辑都包括初始化、组件事件监听以及引用等。比如,导航菜单的控制器会包含对主页面菜单按钮的动作监听”click“,并在该事件中负责创建相应的子页面;而子页面对应的控制器只负责该页面中的组件的行为,如提交按钮的点击等。另外控制器本身能被动态加载,在下文的例子中我们还可以看到,不同的控制器被加载的时机和顺序是由用户行为(如点击某按钮)控制的。其实这个特性是基于依赖于 Ext JS 4 的动态加载类的新特性的。详见"按需加载类"一节。

由此可见,运用MVC后HTML里不再直接写JS了,JS按逻辑、职责分门别类存于不同的目录,对应到不同的文件中。整体逻辑更加条理

入口定义在Ext.application({...})中,这是Ext JS 4为开发MVC应用新增的函数,我们可以在里面引入初始时所需的controller、view或model,在launch函数中编写初始代码,和以前在Ext.onReady()中一样。因为Ext引擎会在Ext.application({...})函数执行时创建一个全局application实例并能在controller中被引用得到 (this.application.*),所以我们也可以在application中定义一些公共函数,甚至注册一些事件,方便其它controller调用。

"model"文件夹中只有模型相关的类定义,每个模型定义必须包含字段,还能定义字段校验规则,不同模型之间的关联,以及数据代理 ( 连接服务器存取数据 )。比如雇员\部门对应两类模型,体现为两个类文件"Employee"和"Department"。

"view"文件夹中定义了所有的widget,每一个widget对应一个类文件。view的代码属于静态代码,后面将提到怎样用工具自动生成。

"controller"文件夹中的控制器按管理范围的不同划分为不同的类文件,其中每一个控制逻辑都包括初始化、组件事件监听以及引用等。比如,导航菜单的控制器会包含对主页面菜单按钮的动作监听”click“,并在该事件中负责创建相应的子页面;而子页面对应的控制器只负责该页面中的组件的行为,如提交按钮的点击等。另外控制器本身能被动态加载,在下文的例子中我们还可以看到,不同的控制器被加载的时机和顺序是由用户行为(如点击某按钮)控制的。其实这个特性是基于依赖于Ext JS 4的动态加载类的新特性的。详见"按需加载类"一节。

由此可见,运用MVC后HTML里不再直接写JS了,JS按逻辑、职责分门别类存于不同的目录,对应到不同的文件中。

开发Ext JS 4的利器:Sencha Architect

Sencha Architect是Sencha公司出品的一款辅助ExtJS开发的商业IDE软件,能帮助ExtJS开发人员更加专注于核心JS代码的开发,从而大大减少花费在编写界面、组织代码等反复性的工作上的时间。笔者写作时的工具版本是2.0.0 Build 412,此工具最大的特点是能帮助用户管理符合MVC模式的代码。例如,视图类代码能通过拖放组件结合属性设置的方式完全自动生成,不用写手一行代码。其它如Model、Store和Controller的代码能通过属性设置,方法、事件设置自动生成。理想情况下,一个熟练的ExtJS开发者在使用Sencha Architect时,百分之九十以上的时间会花在controller的实现和自定义组件(包括 override 一些组件)的开发。这一点也不夸张,因为在Sencha Architect中开发界面实在太轻松了。感兴趣的朋友可以从官网下载Sencha Architect的30天试用版尝尝鲜。

主要特性有:

  • 自动生成View代码

通过拖放组件的方式生成复杂视图。

图 2.视图设计

运用Ext JS 4的MVC架构_第2张图片

  • 代码模式和设计模式切换

设计好的视图,能方便切换到代码模式下预览,拷贝或导出。

图 3.模式切换

运用Ext JS 4的MVC架构_第3张图片

  • 属性设置面板

图 4.属性面板

运用Ext JS 4的MVC架构_第4张图片

  • 项目导航面板

图 5.导航面板

运用Ext JS 4的MVC架构_第5张图片

示例和代码

本文所指的入口代码是什么?

相信读者对Ext.onReady()不会陌生,本文所指的入口代码的功能与Ext.onReady() 是一样的,不同在于,新版的Ext JS 4将命名空间和全局application变量自然地融合在入口函数中,开发者很容易从Model、View或Controller中调用application中的函数、变量和事件,并从使用命名空间的过程中获益,使得MVC的开发变得更方便。

下面的示例程序是一个非常实用的集表单提交,表格应用和图表显示的综合运用的例子。由于篇幅限制,本文只列举主要的 JS 单元,感兴趣的朋友请到本文末尾处下载完整的示例程序。

  • 入口代码

本示例使用了默认的appFolder:app,实际中用户可以覆盖此属性,使用符合项目要求的路径名。我在项目中倾向于appFolder中的所有类由Sencha Architect工具维护,通过配置 mvn,在compile时将appFolder中生成的类拷贝到webApp中。手工维护的JS文件放在独立的命名空间中(称其为扩展空间吧),并在入口中声明,这样能被application引用并加载,同时在扩展空间的类也能 equire到application对应命名空间中的类,这样做的好处是,您能将override的代码移出来放到扩展空间中,另外还能放一些项目中用到的插件。

清单 3. 入口代码

  
  
  
  
  1. /* 动态加载依赖的前提 */   
  2.  Ext.Loader.setConfig({   
  3.     enabled: true,     
  4.     paths: {   
  5.         'Extention': 'js'// 设置一个扩展命名空间,区分工具生成的代码  
  6.     }   
  7.  });   
  8.  
  9.  Ext.application({   
  10.     requires: [   
  11.         'Extention.RandomGen'// 加载扩展命名空间中的类  
  12.     ],   
  13.  
  14.     views: [   
  15.         'MyViewport'  
  16.     ],   
  17.     autoCreateViewport: true,   
  18.     name: 'MyApp',   
  19.     controllers: [   
  20.      // 引用初始页面时,所需要的最小 controller 集合,  
  21.      // 其它的由主菜单按钮触发动态加载  
  22.         'AppLaunchCtrl'  
  23.     ],   
  24.     /* 以下函数仅示意用户能在 application 中定义自己的全局函数,具体实现请下载代码后查看 */   
  25.     findTab: function(tabPanel,  record) {},   
  26.     activateTab: function(tabPanel, targetTab) {},   
  27.     widget: function(tabPanel, controllerName, widgetName, record, cfg) {}   
  28.  });  
  • 主菜单控制器 控制主菜单中三个按钮的点击事件,以及它们的状态:如按下和浮起。
  • 表单子页面控制器 表单提交相关,本示例中,点击提交按钮后将弹出一个“Save”提示框。
  • 表格子页面控制器 通过查询按钮控制表格的 store 重新加载新数据。
  • 图表子页面控制器 通过下拉选择框控制图表的刷新。

清单 4.图表子页面控制器代码

  
  
  
  
  1. Ext.define('MyApp.controller.ChartCtrl', {   
  2.     extend: 'Ext.app.Controller',   
  3.  
  4.     stores: [   
  5.         'MyChartStore',   
  6.         'LatestMonths'  
  7.     ],   
  8.  
  9.     refs: [   
  10.         {   
  11.             ref: 'tabPanel',   
  12.             selector: '#tabPanel'  
  13.         },   
  14.         {   
  15.             ref: 'latestMon',   
  16.             selector: '#comboLatest'  
  17.         }   
  18.     ],   
  19.     /* 请参看 init 中的事件绑定 */   
  20.     onComboboxSelect: function(combo, records, options) {   
  21.         console.log('onComboboxSelect');   
  22.         this.getMyChartStoreStore().load();   
  23.         return false;   
  24.     },   
  25.  
  26.     init: function(application) {   
  27.         // 绑定月份下拉框的 select 事件  
  28.         this.control({   
  29.             "#comboLatest": {   
  30.                 select: this.onComboboxSelect   
  31.             }   
  32.         });   
  33.           
  34.  /* 绑定 store 的 load 事件,完成动态数据效果(演示)  
  35.            实际中项目不需要这样,  
  36.    应在 store 上定义 proxy 从服务器拿数据  
  37.  */   
  38.         this.getMyChartStoreStore().on(   
  39.         {   
  40.             'load' : function(me, operation, eOpts) {   
  41.                 me.loadData(generateData(8));   
  42.             }   
  43.         }   
  44.         );   
  45.  // 初始化月份下拉框的值为”1“  
  46.         this.getLatestMon().setValue('1');   
  47.  // 为图表加载数据,此处显示调用,  
  48.  // 是因为此 store 属性 autoLoad 定义 false   
  49.         this.getMyChartStoreStore().load();   
  50.     }   
  51.  });   
  52.   
  • 运行结果

图 6.表单

运用Ext JS 4的MVC架构_第6张图片

图 7.表格

运用Ext JS 4的MVC架构_第7张图片

 

图 8.图表

运用Ext JS 4的MVC架构_第8张图片

项目中的主要问题和解决办法

  • 关于Sencha Architect代码的管理

(1)此工具能生成几乎所有必须的代码,但对于手工编写的部分 JS 代码,本文推荐在 webApp 下建立独立的目录,并在入口文件中定义对应的路径到命名空间的映射。

(2)用编译工具如 mvn 将生成目录拷贝到 webApp 中。

  • 关于Controller

(1)划分好Controller的范围和生命周期。

(2)划分好页面功能区,然后确定需要哪些Controller;区分哪些是动态的,比如一个子窗口或新页签,对应Controller的加载时机也应该是动态的。

(3)将需要动态加载的Controller从入口文件定义中移除。

(4)这样做的一个显而易见的好处是,避免初始页面"过载";另外能避免因init()调用太早导致事件绑定失败,原因也显而易见:需要绑定事件的组件还没有被创建出来。

  • 关于store多实例

设置过storeId的store将在storeManager中注册为单例;有时这是个限制,比如想在多个view实例中拥有独立的store时,Sencha Architect目前没有好的办法。办法有二,一是 override view,让每个view在create时拥有自己的store;二是在Model中设置proxy( 当然这时得移除 store 中的 proxy)。第二种方法来自Sencha的技术支持,但我还没试过。

  • 关于TreePanel对属性设置的要求

我在使用Sencha Architect的时候碰到过这个问题,Architect能显示热数据,即属性设置好后,设计器能实时将数据作为预览显示出来;我一开始就碰到树不能显示的问题,接着是显示了又无法展开下级。

(1)设置好store的proxy中的idProperty、root属性。我一开始设置错了root(设置了一个不存在的 field),后来查了很久,找到了这个位置 , 问题解决。中间还怀疑是工具的 bug。 瞧,一个很不起眼的地方,但很打击士气。

(2)设置好树节点所用到的Model中的idProperty属性。这个属性决定点击树的节点往下钻取时往服务器传递的参数。

总结

Ext JS从4.0以后有了很大的变化,特别是增加了对MVC开发模式的支持,给Ext JS开发注入了新的活力,也极大地方便了大型WEB项目的开发。本文通过对使用MVC前后的比较,透过一个很实用的MVC实例,演示了运用Ext JS 4 MVC开发Web前端比用以前的版本要简单很多;文章最后,本人根据自己在开发过程中的经历提出了一些常见的困难以及解决办法。希望读者能从中得到一些启发和帮助。

你可能感兴趣的:(mvc,ExtJs)