Chinaunix首页 | 论坛 | 博客
  • 博客访问: 611565
  • 博文数量: 103
  • 博客积分: 2269
  • 博客等级: 大尉
  • 技术积分: 1108
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-23 16:32
文章分类

全部博文(103)

文章存档

2012年(61)

2011年(42)

分类: 系统运维

2012-02-16 23:38:21

MVC 应用架构

  大型客户端程序总是很难编写、组织和维护。当你加入更多功能和开发者到一个项目中时,它们就会迅速膨胀失去控制。Ext JS4 带来了一个新的程序架构,它不但帮你组织代码而且件事你需要些的代码数量。

  我们的程序架构参照MVC模式,模板和控制器被首先提出。现在有许多MVC架构,都是大同小异。我们是这样定义的:

    •Model 是域与数据的集合(例如:一个拥有用户名和密码的用户模板)。模板知道如何通过数据包的方式保留它 们,并且能够通过关联被其他模板链接。模板与Ext JS 3的Record class 类似,并且一般与Stores一同使用来给grids何其他组 件提供数据。

    •View 是任意的组件--grids,tree和panel都是views。

    •Controllers是一个特别的区域,来所有决定你的应用程序是否显示视图,初始化模板和其他应用逻辑。

  在这篇指导中,我们会创建一个非常简单的应用来管理用户数据。在最后你将会知道如何运用Ext JS4程序架构把简单的应用放到一起。

  应用架构是关于提供结构与组成,也是关于真实的类与框架代码。沿用这些约定能获得许多重要的好处:

    •每个应用以同样的方式工作,所以你只需要学习一次

    •在工作方式相同的应用之间共享代码更容易

    •你能使用我们的工具为产品应用创建完善的程序版本

文件结构

  Ext JS 4 应用程序遵循一个对于所有应用都统一的目录结构。针对应用程序关于基本文件结构的详细解释请参考起步 指南。在MVC布局方面,所有的类都被放置在app目录下,该目录依次包含你的模板、视图、控制器和存储的命名空间。下面是当我们完成一个简单示例应用时 目录结构的组织方式。

  在这个例子中,我们把所有的应用文件都放到了一个叫做account_manager的文件夹下。必要的Ext JS 4 SDK文件被包含进ext-4.0目录下。然后我们的index.html的内容如下所示:

按 Ctrl+C 复制代码

在app.Js中创建应用

  每个Ext JS 4应用都以一个应用类实例开始。该应用包含全局设置(像应用名称),也包含对所有被应用使用的模板、视图和控制器的引用。一个应用也包含一个当所有组件都载入后自动执行的载入函数。

  让我们创建一个简单的帮助我们管理用户帐户的账户管理应用。首先我们需要为这个应用选择一个全局的命名空间。所有的Ext JS 4 应用应该只使用一个全局变量,所有的应用类都包含在其中。通常我们需要一个简短的全局变量所以在这个应用中我们打算使用“AM”:

按 Ctrl+C 复制代码

  有一些动作在这里发生。首先我们调用了Ext.Application来创建一个应用实例并赋予了命名空间"AM"。这 为我们自动设置了一个全局变量AM,并且向Ext.Loader注册了该命名空间,通过appFolder配置选项获得了交互目录"app"。我们也提供 了一个简单的载入函数,只创建了包含一个简单的全屏的panel的Viewport:

定义一个控制器

  控制器是一种把应用所有的操作绑在一起的粘合剂。它们所做的全部事情就是监听时间(通常来自视图)并且采取一些行动。继续我们的账户管理应用,让我们创建一个控制器。创建一个文件叫做app/controller/Users.Js并加入如下代码:

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

  现在让我们增加新创建的Users控制器到应用配置文件app.Js中:

1 Ext.application({
2 ...
3
4 controllers: [
5 'Users'
6 ],
7
8 ...
9 });

  当我们通过浏览器访问index.Html载入我们的应用时,Users控制器自动载入(因为我们在上面应用定义的时候指定过它),并且它的init函数在应用的launch函数调用之前调用。

  init函数是一个绝好的地方来设置你的控制器如何与视图交互,并且通常被用来与另外的控制器函数联接。Control函数使监听视图类上的时间和使用handler函数采取相应行动变得简单。让我们更新一下Users控制器来告诉我们panel什么时候被放置。

1 Ext.define('AM.controller.Users', {
2 extend: 'Ext.app.Controller',
3
4 init: function() {
5 this.control({
6 'viewport > panel': {
7 render: this.onPanelRendered
8 }
9 });
10 },
11
12 onPanelRendered: function() {
13 console.log('The panel was rendered');
14 }
15 });

  我们已经更新了init函数来使用this.Control来设置对应用中的视图的监听。Control函数使用新的 ComponentQuery引擎来快速容易地得到页面上组件的引用。如果你还不熟悉ComponentQuery,一定要去完整地看一下 ComponentQuery文档。简单说,它允许我们传递一个类似Css的能够在页面中找到所有的匹配的组件的选择字符串。

  在上述我们的init函数中我们使用了“viewport>panel”,这可以转换成“帮我找到所有的 Viewport的直接panel子孩子”。我们然后采用一个映射事件名称到句柄函数的对象(当前就是render)。这全部的教过就是不论何时匹配我们 选择器的所有组件触发一个render事件,我们的onPanelRendered函数都会被调用。

  当我们执行我们的程序会看到下图:

  虽然不是最令人兴奋的应用,但是它显示了采用有组织的代码是多么容易。现在让我们通过增加一个grid表格来丰富一下我们的应用。

定义一个视图

  知道现在我们的应用程序只有几行和两个文件(app.js和app/controller/Users.js)。现在我们想要增加一个网格来显示我们系统的所有用户,是时候更好地整理一下我们的逻辑然后开始使用视图。

  一个视图无非就是一个组件,通常被定义为Ext JS组件的子类。我们打算通过建立一个叫做app/view/user/List.js的文件来创建显示用户的网格,将下列代码写入该文件:

1 Ext.define('AM.view.user.List' ,{
2 extend: 'Ext.grid.Panel',
3 alias : 'widget.userlist',
4 title : 'All Users',
5 initComponent: function() {
6 this.store = {
7 fields: ['name', 'email'],
8 data : [
9 {name: 'Ed', email: 'ed@sencha.com'},
10 {name: 'Tommy', email: 'tommy@sencha.com'}
11 ]
12 };
13 this.columns = [
14 {header: 'Name', dataIndex: 'name', flex: 1},
15 {header: 'Email', dataIndex: 'email', flex: 1}
16 ];
17 this.callParent(arguments);
18 }
19 });

  我们的视图类不过是一个普通的类。目前我们恰好继承了Grid组件并且设置了一个别名以使我们能把它当做一个xtype来使用(不仅如此)。我们也传入了store配置和网格应该有的columns。

  下一步我们需要把这个视图加到我们的Users控制器中。因为我们设置了‘widget’形似的别名,我们可以使用'userlist'作为一个xtype,就像我们之前使用'panel'一样。

1 Ext.define('AM.controller.Users', {
2 extend: 'Ext.app.Controller',
3 views: [
4 'user.List'
5 ],
6 init: ...
7 onPanelRendered: ...
8 });

  然后通过如下所示修改app.js中的launch方法把它加入到viewport中:

1 Ext.application({
2 ...
3
4 launch: function() {
5 Ext.create('Ext.container.Viewport', {
6 layout: 'fit',
7 items: {
8 xtype: 'userlist'
9 }
10 });
11 }
12 });

  这里仅需要注意我们再试图数组中声明了'user.List'。这告诉应用程序去自动加载文件以便我们执行launch时可以使用它。应用程序使用Ext JS 4的心的动态载入系统来自动从服务器拉取该文件。下面是我们现在刷新页面看到的:

控制表格

  注意我们的onPanelRendered函数仍然被调用。这是因为我们的grid类仍然匹配“viewport > panel”选择器。原因是我们们的类继承了Grid,而grid继承Panel。

  同时,对于每个是直接孩子的Panel或者Panel的子类,我们添加到这个选择器的函数会被调用,所以让我们通过使用新的xtype来把范围缩小一些。让我们在表格行上监听双击以便我们随后编辑用户。

1 Ext.define('AM.controller.Users', {
2 extend: 'Ext.app.Controller',
3
4 views: [
5 'user.List'
6 ],
7
8 init: function() {
9 this.control({
10 'userlist': {
11 itemdblclick: this.editUser
12 }
13 });
14 },
15
16 editUser: function(grid, record) {
17 console.log('Double clicked on ' + record.get('name'));
18 }
19 });

  注意我们改变了组建选择器(变成了简单的“userlist”),事件名(“itemdblclick”)和处理函数名(“editUser”)。目前我们只是打出了我们双击的用户名字。

  信息输出到控制台是很好的方式但是我们想要真正地编辑我们的用户。让我们现在开始做吧,在app/view/user/Edit.js中生成一个视图:

1 Ext.define('AM.view.user.Edit', {
2 extend: 'Ext.window.Window',
3 alias : 'widget.useredit',
4
5 title : 'Edit User',
6 layout: 'fit',
7 autoShow: true,
8
9 initComponent: function() {
10 this.items = [
11 {
12 xtype: 'form',
13 items: [
14 {
15 xtype: 'textfield',
16 name : 'name',
17 fieldLabel: 'Name'
18 },
19 {
20 xtype: 'textfield',
21 name : 'email',
22 fieldLabel: 'Email'
23 }
24 ]
25 }
26 ];
27
28 this.buttons = [
29 {
30 text: 'Save',
31 action: 'save'
32 },
33 {
34 text: 'Cancel',
35 scope: this,
36 handler: this.close
37 }
38 ];
39 this.callParent(arguments);
40 }
41 });

  我们又定义了一个现有的组件的子类——Ext.window.Window。我们再一次使用了 initComponent来配置复杂的对象items和buttons。我们使用了一种“fit”的布局并且一个表单作为一个单独的item,这个表单 包含了编辑名字和邮箱地址的区域。最后我们创建了两个按钮,一个用来关闭窗口,一个用来保存变化信息。

  我们现在只需要做的是把这个视图加入到控制器中,放置它并且载入用户信息到视图中。

1 Ext.define('AM.controller.Users', {
2 extend: 'Ext.app.Controller',
3
4 views: [
5 'user.List',
6 'user.Edit'
7 ],
8
9 init: ...
10
11 editUser: function(grid, record) {
12 var view = Ext.widget('useredit');
13
14 view.down('form').loadRecord(record);
15 }
16 });

  首先我们使用便利的方法Ext.widget创建了视图,该方法与 Ext.create('widget.useredit')等价。然后我们再次使用组件选择快速得到引用来编辑窗口中的表单。每个组件在 Ext JS 4中都有一个down函数,该函数接受一个组件选择字符串来快速找到任何孩子组件。

  在我们的表格中双击一行现在出现了像下面的情景:

创建一个模板(Model)和仓库(Store)

  现在我们有了编辑表单,几乎是时候开始编辑我们的用户并且保存变化信息。在我们这么做之前,我们应该重构一下我们的代码。

  AM.view.user.List组件内部创建了一个Store。这样运行得很好但是我们想要无论在程序的什么地方都能引用这个Store以便我们能改变其中的数据。我们开始把Store分离到它自己的文件中——app/store/User.js:

1 Ext.define('AM.store.Users', {
2 extend: 'Ext.data.Store',
3 fields: ['name', 'email'],
4 data: [
5 {name: 'Ed', email: 'ed@sencha.com'},
6 {name: 'Tommy', email: 'tommy@sencha.com'}
7 ]
8 });

  现在我们仅仅需要改变两个小地方——首先我们命令Users控制器在载入的时候包含这个Store:

1 Ext.define('AM.controller.Users', {
2 extend: 'Ext.app.Controller',
3 stores: [
4 'Users'
5 ],
6 ...
7 });

  然后我们更新app/view/user/List.js来简单通过id来引用Store:

1 Ext.define('AM.view.user.List' ,{
2 extend: 'Ext.grid.Panel',
3 alias : 'widget.userlist',
4
5 //we no longer define the Users store in the `initComponent` method
6 store: 'Users',
7
8 ...
9 });

  通过在定义中包含Users控制器所需要的Store,Store会自动载入到页面并被分配一个storeid,这使他们在视图中方便被引用(在当前简单通过配置store:'Users')。

  同时我们在store中定义了我们的字段('name'和'email')。这运行得很好但是在Ext JS 4中我们 有一个更强大的Ext.data.Model类,当编辑我们的用户时我们想要利用它。我们将会通过重构我们的Store使用Model来结束这一章,我们 将会在app/model/User.js放入如下内容:

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

  上述那些就是我们定义模板(model)所需要做的一切,现在我们来更新Store引用模板而不是内部提供字段,并且使用户控制器也获得对模板的引用:

//the Users controller will make sure that the User model is included on the page and available to our app
Ext.define('AM.controller.Users', {
extend: 'Ext.app.Controller',
stores: ['Users'],
models: ['User'],
...
});

// we now reference the Model instead of defining fields inline
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'}
]
});

  我们的重构将会使下一章更容易但是不会影响程序当前的行为。如果我们现在刷新页面并且双击一行,我们会看到编辑用户的窗口与预期那样出现。现在是时候来完成编辑的功能了。

使用模板保存数据

  现在我们使表格载入数据并且当我们双击一行时打开一个编辑窗口,我们想要保存用户做出的改变。上面定义的编辑用户窗口包含了一个表单(有name和email字段),和一个保存按钮。首先让我们更新控制器的init函数来监听保存按钮的点击事件:

1 Ext.define('AM.controller.Users', {
2 init: function() {
3 this.control({
4 'viewport > userlist': {
5 itemdblclick: this.editUser
6 },
7 'useredit button[action=save]': {
8 click: this.updateUser
9 }
10 });
11 },
12 updateUser: function(button) {
13 console.log('clicked the Save button');
14 }
15 });

  我们control函数增加了第二个组件选择器('useredit button[action=save]')。这 和第一个选择器以同样的方式工作——它使用我们在上面定义的用来聚焦我们编辑用户窗口的'useredit'xtype,然后在窗口中寻找带有属性值为 save的action属性的按钮。当我们定义编辑用户窗口,我们传了{action:'save'}给保存按钮,这给了我们一个容易的方法来定位按钮。

  我们可以对当点击保存按钮时调用updateUser函数调用感到满意。

  现在我们看到函数正确关联到保存按钮的点击时间,让我们为updateUser函数写入真正的逻辑。在函数中我们需要从表单中获得数据,更新用户视图,然后将我们填入的信息保存到用户store中。来看看我们怎么做的:

1 updateUser: function(button) {
2 var win = button.up('window'),
3 form = win.down('form'),
4 record = form.getRecord(),
5 values = form.getValues();
6
7 record.set(values);
8 win.close();
9 }

  让我们分解一下这里发生的事情。我们的点击时间给予我们点击按钮的引用,但是我们真正想要的是访问包含数据的表单和窗口 本身。为了让事情变得简单我们再次使用组件选择器,首先使用botton.up('window')来得到编辑用户窗口的引用,然后使用 win.down('form')来得到表单。

  在这之后我们简单地得到表单中的数据记录并且使用用户输入到表单的数据更新它。最后我们关闭窗口来把关注点移到表格。下面是我们再次运行程序看到的,改变名字字段为'Ed Spencer'并且点击保存。

保存到服务器

  很简单。让我们使程序与我们的服务器端联系来结束。在我们艰难地敲入用户记录到用户存储器(Store)中的同时,让我们通过AJAJ来读取:

1 Ext.define('AM.store.Users', {
2 extend: 'Ext.data.Store',
3 model: 'AM.model.User',
4 autoLoad: true,
5 proxy: {
6 type: 'ajax',
7 url: 'data/users.json',
8 reader: {
9 type: 'json',
10 root: 'users',
11 successProperty: 'success'
12 }
13 }
14 })

  这里我们取消了'data'属性并用了一个(代理)Proxy代替。在Ext JS 4中代理是载入和保存来自一个存储 器或者模板数据的方式。其他代理方式还有AJAX,JSON-P和HTML5本地存储。这里我们使用一个简单的AJAX代理,我们让它从 'data/users.json'载入数据。

  我们也给代理关联了一个reader。这个reader负责将服务器端返回的数据解析成存储器(Store)能够理解的 数据格式。这次我们使用了JSON reader,并且指定了root和successProperty配置(查阅Json Reader文档来获取更多 配置信息)。最后我们创建data/users.json文件并粘贴先前的数据进去:

1 {
2 success: true,
3 users: [
4 {id: 1, name: 'Ed', email: 'ed@sencha.com'},
5 {id: 2, name: 'Tommy', email: 'tommy@sencha.com'}
6 ]
7 }

  我们所做的唯一改变时设置了autoLoad为true,这意味着存储器会让代理立刻载入数据。如果我们刷新页面,我们会看到相同的效果,除了我们再也不讲data数据写死在代码里了。

  在这里我们最后需要做的是把变化的数据回传给服务器。这个例子中我们在服务器端仅仅使用了静态JSON文件,所以我们不会看到任何数据库变化,但是我们至少能够证实一切都正确地耦合在一起乐。首先我们给代理做一点小变化来告诉它将变化数据回传到一个不同的url:

1 proxy: {
2 type: 'ajax',
3 api: {
4 read: 'data/users.json',
5 update: 'data/updateUsers.json'
6 },
7 reader: {
8 type: 'json',
9 root: 'users',
10 successProperty: 'success'
11 }
12 }

  我们仍然从users.json读取数据,但是任何更新将会被送到updateUsers.json。我们可以返回一个 虚拟的回复然后我们就能够知道事情发生了。这updateUsers.json文件只包含{"success":true}。另外一个我们需要的改变是告 诉我们存储器在编辑完后同步自己,这需要我们在updateUser函数中增加几行,如下所示:

1 updateUser: function(button) {
2 var win = button.up('window'),
3 form = win.down('form'),
4 record = form.getRecord(),
5 values = form.getValues();
6
7 record.set(values);
8 win.close();
9 this.getUsersStore().sync();
10 }

  现在我们可以完整运行程序并且确定所有东西都工作。我们会编辑一行,点击保存按钮然后看到请求被正确地送到updateUser.json。

配置

  新引入的Sencha SDK Tools使配置任何Ext JS 4程序比之前更加容易。该工具允许你以JSB3文件格式生成一张的所有需要的清单,然后再几分钟之内创建一个你程序需要的最小化的自定义工程。

  更多细节请参阅起步指导

下一步

  我们已经建立了一个管理用户数据并回传更新到服务器的简单应用。我们从简单开始然后慢慢重构代码它更加整洁有条理。在这 基础上增加更多功能到我们的程序而不出现spaghetti code(结构化差的代码)变得容易。本程序完整的代码可以在Ext JS 4 SDK下载 包中找到,在examples/apps/simple文件夹中。

  在下一篇指导中,我们将进一步研究控制器的能够使你的程序代码更加简短和易于维护的用法和模式。

阅读(5703) | 评论(2) | 转发(0) |
给主人留下些什么吧!~~

roothomes2012-03-15 15:05:02

最乖啦啦:  大型客户端程序总是很难编写、组织和维护。.....
前台就要 MVC 的确不容易,而且比以前代码量要多,但是结构清晰。
需要花费不少的时间;
业务变化不大的项目,是个不错的选择

最乖啦啦2012-02-20 22:47:18

 大型客户端程序总是很难编写、组织和维护。