原文地址:http://docs.sencha.com/extjs/4.2.5/#!/guide/application_architecture
【翻译 by 明明如月 QQ 605283073 源码在文章最后】
如果有一定的英文功底,并且从事编程相关工作或者在校大学生 感兴趣一起翻译此类文章的可以加群
一起翻译一些文章或者api
下一篇:Ext JS 4 架构你的应用 第1节 (官方文档翻译)
大型客户端应用经常比较难写、难组织、难维护。
当你添加更多的函数(功能)和开发人员时它们容易失去控制。
Ext JS 4 带来了新的应用架构,不仅能够帮助组织你的代码同时也能够减少代码量。
我们的应用架构遵循MVC模式。有很多MVC架构,它们之间也彼此有少许区别。
这里我们定义我们自己的mvc架构:
Model(模型): 是一个属性及其数据的集合(例如 User 模型 会含有 username password属性)。
Model 知道怎样在数据包中保持自己,通过关联也可以链接到其他模型。
View (视图):任意类型的组件如grids, trees 和 panels 都是视图。
Controller(控制器):是使你应用工作的特殊的逻辑文件。
不管是渲染视图、实例化模型或者任何其他应用逻辑。
本指导文件中将展示创建来管理用户数据的简单应用。结束以后你将了解怎样使用Ext JS4应用架构将简单的应用组合起来。
此应用架构尽可能多的提供结构和类和框架代码的一致性。遵循以下惯例将带来很多好处:
(1)每个应用工作原理都是一样的,因此你只需要学一次。
(2)不同app间共享代码比较容易,因为他们工作方式相同。
(3)你也可以使用我们的 构建工具来创建你产品应用的优化版本。
Ext JS 4 应用遵循一个统一的目录结构,每个app都一样。
在MVC布局中所有的类都放在app/ 目录下,里面包含子文件夹对应你 模型、视图、控制器和存储的命名空间。
下面是简单的应用文件夹结构的示例:
在本例中,我们将整个应用封装在 'account_manager
' 文件夹中。
Ext JS 4 SDK 的基本文件包装在ext-4/文件夹下。
其中index.html的内容如下:
Account Manager
每一个Ext JS4应用都通过实例化一个 Application类来启动。
Application中包含对应用的全局的设置(例如app的名称)以及应用中使用到的模型、视图和控制器的引用。
同时也包含启动方法,当一切都加载完毕后自动运行。
我们创建一个简单的Account Manager app(账户管理应用)帮助我们管理用户账户。
首先要为应用起一个名字。所有的Ext JS 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 : '将在这里显示用户列表'
}
]
});
}
});
执行过程:首先我们调用Ext.application
来创建一个Application类的实例,并命名为“AM”。
这样就自动为我们设置了一个全局变量 AM并且 通过 appFolder的配置项
'app
' 注册命名空间到 Ext.Loader
。
我们也提供了一个启动方法,仅仅是创建了一个 包含单个填充全屏的Panel的Viewport。
控制器是绑定一个应用在一起的粘合剂。它主要是监听事件(通常是来自视图的)和做出某些行为。
配置我们的Account Manage 应用,我们创建一个控制器。
创建一个app/controller/Users.js 文件然后添加如下代码:
Ext.define('AM.controller.Users', {
extend: 'Ext.app.Controller',
init: function() {
console.log('初始化 Users! 此方法将在 Application launch 方法调用前调用');
}
});
然后添加新创建的 Users 控制器到app.js中。
Ext.application({
...
controllers: [
'Users'
],
...
});
当我们在浏览器中访问index.html
的时候,Users
控制器将自动加载。(因为在app.js里面我们指定了该控制器)
init方法将在Application的 launch
方法之前调用。
init方法是设置你的控制器和视图相互作用的主要场所,经常用来和其他Controller 方法 - control相结合。
control 方法比较容易监听来自你定义的视图的时间并通过一个处理方法进行处理。
我们更新Users 控制器来实现 panel渲染完成后在控制台显示渲染完成的消息。
Ext.define('AM.controller.Users', {
extend: 'Ext.app.Controller',
init: function() {
this.control({
'viewport > panel': {
render: this.onPanelRendered
}
});
},
onPanelRendered: function() {
console.log('面板已经被渲染完毕');
}
});
我们修改了init方法使用 this.control 来设置我们应用中视图的监听器。
control 方法使用新的 ComponentQuery(组件查询) 引擎,快速和容易的获取页面组件的引用。
如果你对其不熟悉可以参考 ComponentQuery documentation。 它允许我们使用类似css选择器那样的方式
来找到页面中每个匹配的元素。
'viewport > panel' 意思是“帮我找到Viewport直接子节点的所有Panel ”。
我们提供一个对象映射事件名称(本例中仅仅是render)到处理方法。
运行效果如下:
虽然上面显示的并不是最令人兴奋的应用,但是它为我们展示了代码组织多么的容易。
下面我们将添加一个grid。
到目前为止我们的应用仅包含两个文件app.js
和 app/controller/Users.js
.
我们想要添加一个grid来显示我们系统的所有用户。我们需要更好的组织我们的逻辑和使用视图。
视图只不过是一个Component(组件),经常定义为Ext JS component的子类。
我们将创建一个 app/view/user/List.js文件,来创建Users grid:
Ext.define('AM.view.user.List' ,{
extend: 'Ext.grid.Panel',
alias: 'widget.userlist',
title: '所有用户',
initComponent: function() {
this.store = {
fields: ['name', 'email'],
data : [
{name: '刘邦', email: 'ed@sencha.com'},
{name: '徐文长', email: 'tommy@sencha.com'}
]
};
this.columns = [
{header: '姓名', dataIndex: 'name', flex: 1},
{header: '邮箱', dataIndex: 'email', flex: 1}
];
this.callParent(arguments);
}
});
View 类仅仅是一个普通的类。
在此类中我们拓展了Grid 组件设置了alias (别名)以便能够通过xtype方式使用它。
我们也通过store配置了数据和grid需要渲染的列。
下一步我们需要在 Users 控制器中添加视图。因为我们在别名中指定了'widget.'的方式。
我们就可以用 'userlist'作为xtype了,这就像之前的‘panel’一样。
Ext.define('AM.controller.Users', {
extend: 'Ext.app.Controller',
views: [
'user.List'
],
init: ...
onPanelRendered: ...
});
通过修改app.js在 viewport内部 渲染它。
Ext.application({
...
launch: function() {
Ext.create('Ext.container.Viewport', {
layout: 'fit',
items: {
xtype: 'userlist'
}
});
}
});
这里需要注意的是views 数组里面的'user.List'.
这告诉应用自动的加载此视图,因此我们在启动时能够使用它。
此应用使用了Ext JS 4的新的动态加载系统来动态的从服务器端拉去此文件。
下面是效果:
注意到onPanelRendered 仍然被调用了。因为grid类仍然也匹配'viewport > panel' 选择器。原因是
我们的类拓展自Grid,Grid又拓展自Panel.
接下来我们修改User控制器来实现rows(行)的双击事件,以便将来我们实现双击编辑User。
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('双击了' + 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: '编辑用户',
layout: 'fit',
autoShow: true,
initComponent: function() {
this.items = [
{
xtype: 'form',
items: [
{
xtype: 'textfield',
name : 'name',
fieldLabel: '姓名'
},
{
xtype: 'textfield',
name : 'email',
fieldLabel: '邮箱'
}
]
}
];
this.buttons = [
{
text: '保存',
action: 'save'
},
{
text: '取消',
scope: this,
handler: this.close
}
];
this.callParent(arguments);
}
});
同样地,我们只定义了 Ext.window.Window
的一个子类 。
我们也用initComponent
来指定 items
和 buttons。
我们采用'fit'
布局和form 作为唯一的元素,包含了用户名和邮箱地址的编辑框。
最后创建了两个按钮,一个是关闭窗口一个是用来保存修改的值。
现在需要做的就是将此视图添加到控制器中,渲染并且将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快速的获得了 编辑窗口的form的引用。Ext JS 4 的每一个组件都有down方法,接受一个ComponentQuery选择器来快速地找到子组件。
双击一行我们看到如下效果:
现在我们有了编辑表单,几乎可以开始编辑我们的用户信息并保存修改后的信息。在我们开始之前,我们应该对我们的代码进行些许地重构。
此时 AM.view.user.List组件创建一个内联的Store。虽然以前那种方式也不错。但是我们希望单独再应用中来
写,以便以后在里面修改数据。
我们将store放在单独的一个文件中app/store/Users.js
接下来作两个改动:
第一个我们在Users控制器中需要包含这个Store:
Ext.define('AM.controller.Users', {
extend: 'Ext.app.Controller',
stores: [
'Users'
],
...
});
然后修改app/view/user/List.js 仅仅简单地引用Store的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 = [
...
});
添加了这个store我们的Users控制器将会自动将其载入到页面并给予一个storeId,使得在视图中引用非常方便(仅仅简单的配置store: 'Users')。
此时我们只是在store中内联地定义了我们的属性 ('name'和
'email'
)。虽然这样也可以很好地工作。但是在Ext JS 4中我们有一个强大的Ext.data.Model类,我们希望用它来编辑用户。我们通过Medole来重构Store:
Ext.define('AM.model.User', {
extend: 'Ext.data.Model',
fields: ['name', 'email']
});
上面就是定义我们Model的方法。我们只需要简单修改Store来引用Model名称取代提供内联属性即可:
Ext.define('AM.store.Users', {
extend: 'Ext.data.Store',
model: 'AM.model.User',
data: [
{name: '刘邦', email: 'ed@sencha.com'},
{name: '徐文长', email: 'tommy@sencha.com'}
]
});
我们也要求Users控制器获取User 模型的引用。
Ext.define('AM.controller.Users', {
extend: 'Ext.app.Controller',
stores: ['Users'],
models: ['User'],
...
});
我们的重构是的下一节更加容易,但是没有改变应用的行为。
如果我们重新加载页面然后双击一行,我们可以看到编辑的用户窗体仍然和期待的一样显示了出来。
是时候修改编辑方法了。
既然我们已经可以通过users grid来加载数据和通过双击每一行来打开编辑窗体,我们希望能够保存用户修改的值。
编辑用户窗体含有一个保存按钮。第一步我们修改控制器的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('点击了保存按钮');
}
...
});
我们在this.control
里面添加了第二个ComponentQuery(组件查询)选择器-'useredit button[action=save]'。
和第一个选择器工作原理是一样的-'useredit' xtype 在用户编辑窗体里面定义过的。查询窗体中所有带有‘save’动作(action)的按钮。
因为我们在编辑用户窗体里面的保存按钮里写了 {action: 'save'} ,这样就使得为该按钮添加事件比较容易。
我们单击 保存按钮时 updateUser方法被调用了:
既然我们的处理方法已经在保存按钮时得到了调用,我们就为updateUser方法添加真正的逻辑。
在此方法中,我们需要获取form的数据并以此来更新用户数据将其保存到Users store中。
updateUser: function(button) {
var win = button.up('window'),
form = win.down('form'),
record = form.getRecord(),
values = form.getValues();
record.set(values);
win.close();
}
我们修改 “刘邦”的邮箱后点击“保存”查看效果:
上面非常容易。现在我们要和服务端进行交互。现在我们编辑了两个用户记录到User Store中。
我们通过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。 在 Ext JS 4中proxy(代理)是从Store或者一个Model中加载或者保存数据的一种方式。
有 针对AJAX, JSON-P 和 HTML5 localStorage 的代理。
我们只是简单实用了 AJAX 代理。
我们通过 'data/users.json' url来加载数据。
我们也为Proxy添加了Reader 。采用 JSON Reader,指定了root和successProperty的配置。
最后我们创建data/users.json文件并将以前的数据粘贴进去。
{
"success": true,
"users": [
{"id": 1, "name": '刘邦', "email": "liubang@sencha.com"},
{"id": 2, "name": '徐文长', "email": "tommy@sencha.com"}
]
}
唯一的改变就是autoLoad设置为true,就意味着Store将要求Proxy 直接加载数据。如果我们数显页面,
我们将看到和以前同样的输出,不同点是我们不再是在应用中硬编码数据。
我们需要做的最后的一件事就是将变化保存回服务器。在本例中我们使用服务端静态的JSON文件,因此看不到任何数据库的改动。修改proxy 添加 update的url
proxy: {
type: 'ajax',
api: {
read: 'data/users.json',
update: 'data/updateUsers.json'
},
reader: {
type: 'json',
root: 'users',
successProperty: 'success'
}
}
我们依然是从users.json中读取数据,所有的数据变更都将发送到updateUsers.json.
更新一个记录以后updateUsers.json文件仅仅包含{"success": true}.由于通过HTTP POST来实现更新,你也许需要创建一个空的文件
避免接收到404错误。updateUsers.json
文件只是一个占位文件。
updateUser: function(button) {
var win = button.up('window'),
form = win.down('form'),
record = form.getRecord(),
values = form.getValues();
record.set(values);
win.close();
// 编辑完记录以后同步store
this.getUsersStore().sync();
}
现在就可以运行完整的例子了。我们编辑一行,点击“保存”按钮,可以看到请求正确发送给了updateUser.json
源码下载地址:https://yunpan.cn/cSFA5huRp8kp8 访问密码 54b3