[翻译]Ext JS 教程-MVC架构 原

大规模的客户端应用程序常常难于去编写、组织机构和维护。随着你加入更多的功能,并且投入更多的开发人员,它们渐渐趋向于失去控制。ExtJS 4 带来了一种新的不仅仅能规整你的代码组织结构,而且能限制你要编写的代码数量的应用程序架构。

我们的应用程序架构承载于一种类MVC的模式,第一次推出了模型和控制器。现在已经有很多MVC架构了,它们之中大部分只有一些轻微的不同之处。

模型是域和它们的数据的一个集合(比如:一个拥有姓名和密码的用户模型)。模型指导怎样通过数据包(package)将它们自身持久化,并且能使用关联连接其它的模型。模型工作起来很香ExtJS 3中的Record类,而且一般同Stores一起用来在表格(grid)和组件中展示数据。

视图表示任何类型的组件 - 表格、树,还有面板等都是视图。

控制器是放置能够使你的应用工作的所有代码的专有位置 - 不管是渲染视图,初始化模型,还是任何其他的应用逻辑。

在这个指南中我们将会创建一个简单的用于管理用户数据的应用程序。在最后你将会指导如何使用心得ExtJS 4 应用程序架构将简单的应用程序组织到一起。

应用程序架构提供了架构和一致性的意义和提供了实际的类和框架代码一样重要。遵循这个约定将获得许多重要的好处:

1 每一个应用程序都以同样的方式运作,因而你可以只用去学一次(就掌握其工作原理)。

2 因为它们都以同样的机制工作,因此在应用之间共享代码是很容易的。

3 你可以使用我们的工具创建优化的应用程序版本用于生产环境。

文件架构

ExtJS 4 遵循对每个应用都一样的一个统一的目录结构。请浏览入门指南中关于基本的文件架构的详细解释。在MVC布局中,所有的类都放在app文件夹中,里面一次放着区分你的模型、视图、控制器和存储(store)的命名空间的文件夹。下面是当我工作完成以后的一个简单的示例应用的文件结构:

在这个例子中,我们将整个应用程序封装到一个称作“account_manager”的文件夹中。来的 ExtJS 4 SDK 的必备文件放入了 ext-4文件夹。因而我们的index.html 看起来像这样:

 <html> 
 <head> 
     <title>Account Manager</title> 
     <link rel="stylesheet" type="text/css" href="ext-4/resources/css/ext-all.css"> 
     <script type="text/javascript" src="ext-4/ext-debug.js"></script> 
     <script type="text/javascript" src="app.js"></script> 
 </head> 
 <body></body> 
 </html> 

在 app.js 中创建应用程序

每一个ExtJS 4 应用程序都是从一个Application类的实例起步。Application 包含着你整个应用程序(比如应用的名字)的全局设置,也维护着所有应用使用到的模型、视图和控制器的引用。一个Application也包含一个启动函数,它将在所有东西都加载完了以后自动运行。

让我创建一个能够帮助我们管理用户账户的简单账户管理应用。首先我们需要为这个应用程序取一个全局的命名空间。所有ExtJS 4应用程序都应该使用一个单独的全局变量,将应用程序的所有类网络其中。我们常常想着要一个简短的全局变量,因此这个例子中我们将使用“AM”:

 Ext.application({ 
     requires: ['Ext.container.Viewport'], 
     name: 'AM', 
     appFolder: 'app', 
     launch: function() { 
 Ext.create('Ext.container.Viewport', { 
             layout: 'fit', 
             items: [ 
                 { 
                     xtype: 'panel', 
                     title: 'Users', 
                     html : 'List of users will go here' 
                 } 
             ] 
         }); 
     } 
 }); 

这里将发生着几件事情。首先我们调用了 Ext.application 去创建一个新的Application类的实体,我们把名称‘AM’传给了它。这样就自动的为我们设置了一个全局变量 AM,并且向 Ext.Loader 注册了命名空间,附带通过 appFolder 配置选项设置了对应的与其(命名空间)对应的路径‘app' 。我们也提供了一个简单的启动方法,那会创建一个Viewport——它包含一个填满整个屏幕的Panel。

定义一个控制器

控制器是将一个应用程序绑紧的胶水。它们真正做的事情是监听事件(常常来自视图)然后做出一些动作。继续我们的AccountManager应用程序,让我们创建一个控制器。创建一个叫做 /controller/Users.js 的文件并向里面添加如下代码:

 Ext.define('AM.controller.Users', { 
     extend: 'Ext.app.Controller', 
     init: function() { 
         console.log('Initialized Users! This happens before the Application launch function is called'); 
     } 
 }); 

现在就让我们早app.js中把我们新创建的用户控制器添加到应用程序的配置当中:

 Ext.application({ 
     ... 
     controllers: [ 
         'Users' 
     ], 
     ... 
 }); 

当我们通过在浏览器里面访问inde.html以加载我们的应用程序,用户控制器会被自动加载(因为我们在前面的应用程序定义中指定了它),而且它的init方法也在应用程序的launch方法之前就被调用了。

init方法是一个决定你的控制器如何同视图交互的好地方,而且它常常和另外一个控制器函数control一起使用。 control方法使得监听在你的视图类上面的事件并使用一个(事件)处理方法做出一些行动变得容易起来。让我们更新我们的用户控制器,让它了告诉我们panel在什么时候被渲染:

 Ext.define('AM.controller.Users', { 
     extend: 'Ext.app.Controller', 
     init: function() { 
         this.control({ 
             'viewport > panel': { 
                 render: this.onPanelRendered 
             } 
         }); 
     }, 
     onPanelRendered: function() { 
         console.log('The panel was rendered'); 
     } 
 }); 

我们已经更了init方法,用this.control在我们应用程序上面设置监听器。这个control方法使用新的ComponentQuery引擎去迅速简单的获取页面上组件的引用。如果你还不是很熟悉 ComponentQuery,应该确保去看看ComponentQuery文档以获得一个整体的解释。简要的来说,它允许我们传入一个类CSS的选择器去寻找页面上每一个匹配的组件。

在上面的init方法中我们向它提供了’viewport > panel‘,将翻译成 “为我寻找作为Viewport的子节点的每一个Panel”。然后我们为处理器方法提供一个对应事件名称(在这里就是render)的对象。最后的效果就是每当任何匹配我们选择器的组件触发了render事件,我们的onPanelRendered方法就会被调用。

这并不是一个令人兴奋的应用程序,但是它展示了使用开始获得结构良好的代码有多容易。现在让我通过添加一个表格(grid)来丰富这个应用。

定义一个视图

直到现在我们的应用程序仅仅只有几行代码长,只有 app.js和app/controller/User.js两个文件。现在我们想添加一个展示我们系统中所有用户的表格,是时候更好的组织我们的业务逻辑并且开始使用视图了。

视图更像是一个组件,常常定义成一个ExtJS 组件的子类。我们通过创建一个新的 app/view/user/List.js 文件并向其中加入如下代码来创建我们的用户列表:

 Ext.define('AM.view.user.List' ,{ 
     extend: 'Ext.grid.Panel', 
     alias: 'widget.userlist', 
     title: 'All Users', 
     initComponent: function() { 
         this.store = { 
             fields: ['name', 'email'], 
             data  : [ 
                 {name: 'Ed',    email: 'ed@sencha.com'}, 
                 {name: 'Tommy', email: 'tommy@sencha.com'} 
             ] 
         }; 
         this.columns = [ 
             {header: 'Name',  dataIndex: 'name',  flex: 1}, 
             {header: 'Email', dataIndex: 'email', flex: 1} 
         ]; 
         this.callParent(arguments); 
     } 
 }); 

我们的视图类无非就是一个一般的类。这里我们碰巧是扩展了Grid Component,并且设置了别名以便我们向一个xtype一样使用它(等会儿就不止那样了)。我们也传递了store配置和columns给表格去渲染。

接下来我们需要把这个视图添加到我们的Users控制器中。因为我们使用了特别的“widget.”格式设置了一个别名,我们现在就可以像一个 xtype那样使用’userlist‘了,就像我们之前已经使用过的’panel‘一样。

 Ext.define('AM.controller.Users', { 
     extend: 'Ext.app.Controller', 
     views: [ 
         'user.List' 
     ], 
     init: ... 
     onPanelRendered: ... 
 }); 

然后通过修改app.js中的启动方法在主窗口中的渲染它。

 Ext.application({
      ...
 
      launch: function() {
 Ext.create('Ext.container.Viewport', {
              layout: 'fit',
              items: {
                  xtype: 'userlist'
              }
          });
      }
  }); 

这里只需要注意的另外一件事情就是我们在视图数组里面指定了’user.List‘。这就会告诉应用程序自动加载那个文件,以便我们在启动时使用到它。引用程序使用 ExtJS 4 的新的动态加载系统去自动的把这个文件从服务器推出来。下面是现在当我们刷新页面时所看到的:

控制这个表格

请注意我们 onPanelRendered 方法仍然会被调用。这是因为我们的表格类仍然匹配 ’viewport > panel‘选择器。会这样的原因是我们的类扩展了Grid,而它相应的扩展了 Panel。

现在,我们向这个选择器中加入的监听器实际上会为每一个viewport的直接子节点的Panel或者Panel的子类而被调用,于是让我们用新的xtype把那些绑定起来。现在我们已经在做了,让我们监听行上面的双击事件,以便稍后可以编辑那个用户。

 Ext.define('AM.controller.Users', { 
     extend: 'Ext.app.Controller', 
     views: [ 
         'user.List' 
     ], 
     init: function() { 
         this.control({ 
             'userlist': { 
                 itemdblclick: this.editUser 
             } 
         }); 
     }, 
     editUser: function(grid, record) { 
         console.log('Double clicked on ' + record.get('name')); 
     } 
 }); 

请注意我们改变了 ComponentQuery 选择器(简化’userlist’),事件名臣(‘itemdblclick’)和处理方法名字(‘editUser’)。现在我们只是记录了我们双击的用户的名字:

在控制台界面上记录很好,但是我们真正想要的是去编辑我们的用户。现在就这样做,新建一个 app/view/user/Edit.js 的视图先:

 Ext.define('AM.view.user.Edit', { 
     extend: 'Ext.window.Window', 
     alias: 'widget.useredit', 
     title: 'Edit User', 
     layout: 'fit', 
     autoShow: true, 
     initComponent: function() { 
         this.items = [ 
             { 
                 xtype: 'form', 
                 items: [ 
                     { 
                         xtype: 'textfield', 
                         name : 'name', 
                         fieldLabel: 'Name' 
                     }, 
                     { 
                         xtype: 'textfield', 
                         name : 'email', 
                         fieldLabel: 'Email' 
                     } 
                 ] 
             } 
         ]; 
         this.buttons = [ 
             { 
                 text: 'Save', 
                 action: 'save' 
             }, 
             { 
                 text: 'Cancel', 
                 scope: this, 
                 handler: this.close 
             } 
         ]; 
         this.callParent(arguments); 
     } 
 }); 

我们再一次只定义了一个已经存在的组件的子类——这次是 Ext.window.Window。我们再一次使用了 initComponent 去定制复杂对象条目和按钮。我们使用了一种“fit”布局,还有一个单独的表单条目,它包含了编辑姓名和邮件地址的域。最后我们创建了两个按钮,一个用来关闭窗口,另一个将被用来保存我们(对用户账户)做出的更改。

我们现在需要做的是:把视图添加到控制器中,渲染它并且把User加载到它的里面。

 Ext.define('AM.controller.Users', { 
     extend: 'Ext.app.Controller', 
     views: [ 
         'user.List', 
         'user.Edit' 
     ], 
     init: ... 
     editUser: function(grid, record) { 
         var view = Ext.widget('useredit'); 
         view.down('form').loadRecord(record); 
     } 
 }); 

首先我们使用了便利的 Ext.widget 方法创建了视图,它等价于使用 Ext.create(‘widget.useredit’)。然后我们再一次让ComponentQuery去快速得到编辑窗口中表单的引用。ExtJS 4中的每一个组件都有一个down方法,它接受一个ComponentQuery选择器去快速寻找任何子组件。

双击表格中的一行,将发生下面这样的事情:

创建一个模型和存储

现在我们拥有了我们的编辑表单,它几乎可以开始编辑我们的用户并且保存那些变更了。然后在我们那样做之前,我们应该对我们的代码做一些小小的重构。

目前 AM.view.user.List 组件创建了一个内置的存储。它工作得很好,但是我们更乐于在应用程序的任何地方引用到那个存储,以便我们可以更新它里面的数据。我们开始把存储分离到属于它自己的文件中 —— app/store/Users.js:

现在我们仅做两个小的变更——首先我们叫我们的Users控制器在它加载的时候包含这个存储:

 Ext.define('AM.controller.Users', { 
     extend: 'Ext.app.Controller', 
     stores: [ 
         'Users' 
     ], 
     ... 
 }); 

然后我们更新 app/view/user/List.js ,通过id简单的引用这个存储:

 Ext.define('AM.view.user.List' ,{ 
     extend: 'Ext.grid.Panel', 
     alias: 'widget.userlist', 
     title: 'All Users', 
     // we no longer define the Users store in the `initComponent` method 
     store: 'Users', 
     initComponent: function() { 
         this.columns = [ 
         ... 
 }); 

通过包含这个存储,我们的Users控制器关注到它们被自动导入到页面上的定义,并且赋予了一个storeId,让他们可以很容易的在我们的视图中被引用到(在这里是通过简单的配置 store:’Users’)。

现在我们仅仅只在存储上定义了内置的域(‘name’和‘email’)。这样工作得很好,但是在 ExtJS 4中我们会乐于去利用强大的 Ext.data.Model 类,当需要编辑我们的Users时。通过使用一个Model——我们将放入 app/model/User.js 中——来结束这一节的内容:

 Ext.define('AM.model.User', { 
     extend: 'Ext.data.Model', 
     fields: ['name', 'email'] 
 }); 

那就是定义我们的Model全部要做得事情。现在我们仅需要更新我们的存储区引用Model的名字,而不是提供内置的域…

 Ext.define('AM.store.Users', { 
     extend: 'Ext.data.Store', 
     model: 'AM.model.User', 
     data: [ 
         {name: 'Ed',    email: 'ed@sencha.com'}, 
         {name: 'Tommy', email: 'tommy@sencha.com'} 
     ] 
 }); 

并且我们也将叫Users控制器去获取User模型的引用:

 Ext.define('AM.controller.Users', { 
     extend: 'Ext.app.Controller', 
     stores: ['Users'], 
     models: ['User'], 
     ... 
 }); 

我们的重构将会使下一节变得容易,但不是对应用程序现有的行为造成影响。如果我们现在刷新页面并且双击一行,我们会发现编辑User的窗口仍然像预期的一样显示了。现在是时候来完成编辑的功能了:

使用Model来保存数据

现在我们有了我们的用户表格,而且当我们双击每一行都会打开一个编辑窗口,我们需要保存用户做出的更改。上面定义的编辑用户的窗口包含一个表单(里面有姓名和电子邮件的域),和一个保存按钮。首先让我们更新我们控制器的init方法,去侦听保存按钮的点击事件:

 Ext.define('AM.controller.Users', { 
     ... 
     init: function() { 
         this.control({ 
             'viewport > userlist': { 
                 itemdblclick: this.editUser 
             }, 
             'useredit button[action=save]': { 
                 click: this.updateUser 
             } 
         }); 
     }, 
     ... 
     updateUser: function(button) { 
         console.log('clicked the Save button'); 
     } 
     ... 
 }); 

我们在this.control 调用中添加了第二个 ComponentQuery 选择器——这次是‘useredit button[action=save]’。它像第一个选择器那样工作——它使用我们在上面编辑用户窗口中定义的‘useredit’的 xtype,然后寻在任何在这个窗口中的带有‘save’action的按钮。当我们定义我们的用户编辑窗口时,我们向保存按钮中传入了{action:‘save’},这给了我们一种寻的到那个按钮的简便方法。

我们可以使用updateUser方法在我们点击保存按钮时被调用来满足我们的需求:

现在我们已经看到我们的处理句柄已经被正确的绑定到了保存按钮的点击事件上面,让我们望updateUser方法里面填充真实的业务逻辑吧。在这个方法中,我们需要从表单中获取数据,更新我们的User,然后保存到我们在上面创建的Users存储中。让我们看看我们怎么才能做到那样:

 updateUser: function(button) { 
     var win    = button.up('window'), 
         form   = win.down('form'), 
         record = form.getRecord(), 
         values = form.getValues(); 
     record.set(values); 
     win.close(); 
 } 

让我们分析分析这里发生了什么。我们的点击事件给了我们用户点击的那个按钮的引用,但是我们真正想要的是获得包含了数据的表单,还有窗口本身。为了让事情进展快速,我们将再一次在这里使用ComponentQuery,首先使用 button.up('window') 去获得用户编辑窗口的引用,然后是 win.down('form') 获取表单。

在那以后我们将取得现在已经被导入表单的记录,并且将用户输入到表单中的任何东西来更新它。最后我们关闭窗口,将注意力转回到表格。下面是当我们再一次运行我们的应用时将看到的,把名字域改成‘Ed Spencer’然后点击保存:

保存到服务器

足够简单。让我们通过使它同服务器交互来完成这个东西。目前我们是把两条用户数据硬编码到Users存储中的,因此让我先从换用Ajax来读取那些数据开始吧:

 Ext.define('AM.store.Users', { 
     extend: 'Ext.data.Store', 
     model: 'AM.model.User', 
     autoLoad: true, 
     proxy: { 
         type: 'ajax', 
         url: 'data/users.json', 
         reader: { 
             type: 'json', 
             root: 'users', 
             successProperty: 'success' 
         } 
     } 
 }); 

这里我们去除了’data‘属性,并且用一个Proxy替换了它。代理(Proxies)是ExtJS 4 中从存储和模型中加载和保存数据的方法。其中有AJAX,JSON-P和HTML5本地存储代理。这里我们使用了简单的AJAX代理,我们告知它从url’data/users.json‘加载数据。

我们也给Proxy绑定一个阅读器(Reader)。阅读器负责将服务的回应数据解码成存储(Store)可以理解的格式。这里我们使用了一个 JSON 阅读器,并且定制根路径和successProperty配置。最后我们创建了data/users.json 文件,并且把我们前面的数据粘贴到里面:

 { 
     "success": true, 
     "users": [ 
         {"id": 1, "name": 'Ed',    "email": "ed@sencha.com"}, 
         {"id": 2, "name": 'Tommy', "email": "tommy@sencha.com"} 
     ] 
 } 

我们所做的另外一个仅有的更改是设置autoLoad为true,意味着存储会立即叫它的代理去加载那些数据。如果我们现在就刷新页面,我们将会看到和之前一样的输出,所不同的是我们现在不再把数据硬编码到我们的应用程序中了。

最后我们想做的一件事情是把我们所做的更改发送回服务器。对于这个例子我们只在服务器端使用了静态的JSON文件,因此我们看不到对数据库的任何更改,但是我们至少验证了所有东西被正确的塞到了一起。首先我们对我们的新代理做一些小更改,告诉他把更新发送到一个不同的url:

 proxy: { 
     type: 'ajax', 
     api: { 
         read: 'data/users.json', 
         update: 'data/updateUsers.json' 
     }, 
     reader: { 
         type: 'json', 
         root: 'users', 
         successProperty: 'success' 
     } 
 } 

我们仍然从users.json读取数据,但是任何更改将会被发送到updateUsers.json。这样是为了我们在不改写我们的测试数据的前提下指导事情发生了变化。更行了一条记录以后,updateUser.json 包含了 {“success”:true} 。它通过一个HTTP POST 指令被更新了,你可以通过创建一个空的文件避免收到一个404错误。

我们仅需要做出了另外一个更改时告诉我们的存储在编辑之后同步它自身,我们可以通过在updateUser方法里面额外添加一行代码来做到,现在就看起来像这样:

 updateUser: function(button) { 
     var win    = button.up('window'), 
         form   = win.down('form'), 
         record = form.getRecord(), 
         values = form.getValues(); 
     record.set(values); 
     win.close(); 
     // synchronize the store after editing the record 
     this.getUsersStore().sync(); 
 } 

现在我们可以运行我们的完整示例了,并且确保一切都在正常运行。我们将标记一行,点击保存按钮然后查看请求被准确的发送到了 updateUser.json。

部署

新推出的 Sencha SDK Tools (download here) 让部署任何 ExtJS4 应用程序变得比以前更加容易。这个工具允许你在几分钟之内,采用JSB3文件形式生成一个包含所有依赖的manifest文件,并且创建一个迷你化的定制构建,仅包含你的应用程序需要的东西。

接下来

我们已经创建了一个非常简单的应用程序来管理用户数据,并且发送任何更新到服务器。我们从简单到深入,最终重构了我们的代码,使之条理结构清晰。这样它就更容易添加更多的功能,而不用为应用程序创建绝缘的(spaghetti)代码。这个应用程序完整的源代码可以在 ExtJS 4 SDK 的下载中找到,就在 examples/app/simple 文件夹里面。

---------------------------------------------------

下面是文章来源:

http://docs.sencha.com/extjs/4.2.1/#!/guide/application_architecture

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据之美

Ubuntu on Windows10 跨平台开发环境搭建权威指南

程序猿经常争论的一个话题是:日常开发到底 Windows 好还是 Linux 好?进而演化出另一个问题:到底选 MacBook 好还是 SurfaceBook ...

664140
来自专栏FreeBuf

打造一款属于自己的远程控制软件(一)

本人为了工作中便于管理手中大量的计算机一直在寻找一款合适的远程控制软件。鉴于网上下载的远程控制软件大多都被不同程度地植入后门,于是萌生了自己打造一款远控的想法,...

1.5K80
来自专栏青玉伏案

iOS开发之使用CocoaPods更新第三方出现“target overrides the `OTHER_LDFLAGS`……”问题解决方案

  今天在自己的项目中用CocoaPods引入第三方SDWebImage的时候,出现了问题。当更新完毕后,在终端没太注意这个问题的提示,就直接使用SDWebIm...

207100
来自专栏Fred Liang

Service Worker 实现 web 应用消息推送

Service Worker 是事件驱动的 worker,生命周期与页面无关,关联页面未关闭时,它也可以退出,没有关联页面时,它也可以启动.

70920
来自专栏進无尽的文章

推送-远程通知推送教程

本文翻译自:raywenderlich.com,原文作者:Jack Wu,译者:JMStack

18620
来自专栏魏艾斯博客www.vpsss.net

WP-Sweep 插件清理 WordPress 垃圾评论和数据结构

48820
来自专栏随心DevOps

使用 React 和 Django REST Framework 构建你的网站

在我们最近的工作中,构建网站使用的架构是带有 Django REST Framework(DRF)后端的 React 前端。它们是通过在前端使用 axios(前...

2.1K70
来自专栏崔庆才的专栏

如何搭建一台FTP服务器

由于整个学校相当于一个大型局域网,相互之间传送数据非常快,比如要共享个电影,传点资料什么的。所以我们可以选择搭建一个FTP服务器来共享文件。通过本文给大家说一下...

5K00
来自专栏网络

强大的开源网络侦查工具:IVRE

IVRE简介 IVRE(又名DRUNK)是一款开源的网络侦查框架工具,IVRE使用Nmap、Zmap进行主动网络探测、使用Bro、P0f等进行网络流量被动分析,...

39770
来自专栏散尽浮华

Linux下快速迁移海量文件的操作记录

有这么一种迁移海量文件的运维场景:由于现有网站服务器配置不够,需要做网站迁移(就是迁移到另一台高配置服务器上跑着),站点目录下有海量的小文件,大概100G左右,...

32870

扫码关注云+社区

领取腾讯云代金券