您的位置:首页 > 运维架构 > 网站架构

ExtJS 4 MVC架构讲解

2014-12-25 22:26 239 查看

大规模客户端应用通常不好实现不好组织也不好维护,因为功能和人力的不断增加,这些应用的规模很快就会超出掌控能力,ExtJS4带来了一个新的应用架构,不但可以组织代码,还可以减少实现的内容新的应用架构遵照一个类MVC的模式,模型(Models)和控制器(Controllers)首次被引入。业界有很多种MVC架构,基本大同小异,ExtJS4的定义如下:Model模型是字段和它们的数据的集合,例如
User
模型带有
username
password
字段,模型知道如何持久化自己的数据,并且可以和其他模型关联,模型跟ExtJS3中的
Record
类有点像(区别是,
Record
只是单纯的扁平结构,而
Model
可以nest),通常都用在
Store
中去展示grid和其他组件的数据View视图是组件的一种,专注于界面展示-grid,tree,panel都是viewControllers控制器一个安放所有使你的app正确工作的代码的位置,具体一点应该是所有动作,例如如何渲染view,如何初始化model,和app的其他逻辑这篇教程中将创建一个简单的管理用户数据的例子,做完这个例子,你会学会如何用ExtJS4的架构把简单的应用组织到一起。应用架构是关于提供结构和一致性,是关于真实的类和框架代码的,遵守约定可以带来很多重要的好处:每个应用都是相同的工作方式,所以你只需要学习一次可以很容易的共享代码,因为所有应用工作模式都一样可以创建属于自己的混淆的版本用于发布

FileStructure文件结构

ExtJS4应用都遵循一个统一的目录结构,每个应有都相同。详情可以参见ExtJS4入门,MVC中,所有类都放在
app
目录里面,这个目录可以有子目录,代表的是命名空间(一个子目录对应一个命名空间),使用不同的目录存放
views
,
models
,
controllers
,
stores
,当我们完成例子的时候,目录结构应该和下面截图一样:在这个例子中所有文件都在
'account_manager'
目录中,ExtJSSDK必须的文件在目录
ext-4.0
中,因此,
index.html
应该如下:
<html><head><title>AccountManager</title><linkrel="stylesheet"type="text/css"href="ext-4.0/resources/css/ext-all.css"><scripttype="text/javascript"src="ext-4.0/ext-debug.js"></script><scripttype="text/javascript"src="app.js"></script></head><body></body></html>

Creatingtheapplicationinapp.js在app.js中创建应用

每个ExtJS4的应用都从一个
Application
类的实例开始,这个实例包含应用的全局配置(例如应用的名字),这个实例也负责维护对全部模型、视图、控制器的引用的维护,还有一个
launch
函数,会在所有加载项加载完成之后调用。让我们创建一个简单的AccountManager应用管理用户帐户。首先需要选择一个全局命名空间,所有ExtJS4应用都需要有一个全局命名空间,以让所有应用中的类安放到其中,这个例子中我们使用AM(AccountManager)
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:'Listofuserswillgohere'}]});}});
首先我们调用了
Ext.application
创建了一个应用实例,并设置了应用名称”AM”,这样做会自动创建一个全局变量”AM”,并自动注册命名空间”AM”到
Ext.Loader
,用类似的方式也设置了
app
作为
appFolder
。另外在
launch
函数中,创建了一个
Viewport
,只包含一个撑满浏览器窗口的
Panel

DefiningaController定义一个控制器

控制器是应用的粘合剂,它们所作的事情就是监听事件并执行动作,继续我们的AccountManager应用,创建一个控制器。创建
app/controller/Users.js
这个文件,并添加如下代码:
Ext.define('AM.controller.Users',{extend:'Ext.app.Controller',init:function(){console.log('InitializedUsers!ThishappensbeforetheApplicationlaunchfunctioniscalled');}});
接下来在
app.js
中添加对
Users
控制器的引用:
Ext.application({...controllers:['Users'],...});
当我们通过
index.html
查看应用,
Users
控制器会被自动加载(因为在
app.js
的Application中增加了引用),并且
Users
init
方法会在
launch
之前调用
init
方法是个极好的地方,可以用来设置如何和view交互,通常都使用
Controller
的一个方法
control
control
方法使得监听view的事件变的容易,更新一下控制器,让它告知我们panel何时渲染:
Ext.define('AM.controller.Users',{extend:'Ext.app.Controller',init:function(){this.control({'viewport>panel':{render:this.onPanelRendered}});},onPanelRendered:function(){console.log('Thepanelwasrendered');}});
我们已经更新了
init
方法,使用
this.controll
给视图设置监听器。这个
controll
方法,使用最新的组件查询引擎(ComponentQuery)可以快速方便的找到页面上的组件。如果你对ComponentQuery不熟悉,可以查看ComponentQuery文档进行详细了解。简要一点,ComponentQuery可以允许我们使用一个类似css选择器的方式找到组件。在例子的
init
方法中我们应用了
'viewport>panel'
,可以解释为“查找Viewport直接后代中的所有Panel组件”,然后我们提供了一个对象匹配事件名称(这个例子中只用了
render
)来提供响应函数。全部的影响就是无论哪个组件符合我们的选择器,当它的
render
事件触发时,我们的
onPanelRendered
函数都会被调用。当我们运行应用程序,可以看到如下图所示:这不是最激动人心的应用,但是它展示给我们,开始一段有组织的代码是多么容易。让我们增加一点内容,加一个grid。

DefiningaView定义一个视图

直到现在,我们的应用只有很少代码,只有两个文件
app.js
app/controller/Users.js
,现在我们想增加一个grid显示所有系统中的用户,是时候更好的组织一下逻辑并开始使用视图了。视图也是组件,通常都是ExtJS现有组件的子类,现在准备创建用户表,先创建
app/view/user/List.js
,添加代码:
Ext.define('AM.view.user.List',{extend:'Ext.grid.Panel',alias:'widget.userlist',title:'AllUsers',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
组件,并设置了别名,这样我们可以用
xtype
的方式调用这个组件,另外我们也添加了
store
columns
的配置。接下来我们需要添加这个视图到
Users
控制器。因为我们用
'widget.'
设置了别名,所以我们可以使用
userlist
作为xtype,就像我们使用之前使用的
'panel'
Ext.define('AM.controller.Users',{extend:'Ext.app.Controller',views:['user.List'],init:...onPanelRendered:...});
接下来修改
app.js
让视图在viewport中渲染,需要修改
launch
方法
Ext.application({...launch:function(){Ext.create('Ext.container.Viewport',{layout:'fit',items:{xtype:'userlist'}});}});
唯一需要注意的是我们在views数组中指定了
'user.List'
,这告诉应用去自动加载对应的文件,ExtJS4的动态加载系统会根据规则从服务器自动拉取文件,例如user.List就是规则,把.替换成/就是文件存放路径。刷新一下页面:

Controllingthegrid对列表的控制

注意
onPanelRendered
方法依然被调用,因为我们的grid依然满足
'viewport>panel'
选择器,因为我们的视图继承自
Grid
,从而继承自
Panel
现在我们需要收紧一下选择器,我们使用xtype作为选择器替换之前的
'viewport>panel'
,监听双击事件,以便继续做编辑用户信息:
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('Doubleclickedon'+record.get('name'));}});
注意我们更换了组件查询选择器为
'userlist'
,监听的事件更改为
'itemdblclick'
,响应函数设置为
'editUser'
,现在只是简单的日志出双击行的name属性:可以看到日志是正确的,但我们实际想做的是编辑用户信息,让我们现在做,创建一个新的视图
app/view/user/Edit.js
Ext.define('AM.view.user.Edit',{extend:'Ext.window.Window',alias:'widget.useredit',title:'EditUser',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
方法去指定复杂的
items
buttons
对象,我们使用了一个
'fit'
布局和一个表单,表单包括了要编辑的用户信息字段,最后我们创建了两个按钮,一个用来关闭窗口,另一个用来保存变更。现在我们要做的就是在控制器加载这个视图,渲染并且加载用户信息:
Ext.define('AM.controller.Users',{extend:'Ext.app.Controller',views:['user.List','user.Edit'],init:...editUser:function(grid,record){varview=Ext.widget('useredit');view.down('form').loadRecord(record);}});
首先我们用
Ext.widget
方法创建了视图,这个方法等同于
Ext.create('widget.useredit')
,然后我们又一次借助组件查询找到了窗口中的表单,每个ExtJS4中的组件都有一个
down
方法,可以借助组件查询支持的选择器来迅速找到任意下层的组件。双击表格中的一行可以看到:

CreatingaModelandaStore

现在我们有了表单,可以开始编辑和保存用户信息了,但是这之前需要做一点点重构。
AM.view.user.List
创建了一个内联的
Store
,这样可以工作但是我们需要把
Store
分离出来以便我们在应用的其他位置可以引用并更新其中的信息,我们把它放在它应该在的文件中
app/store/Users.js
Ext.define('AM.store.Users',{extend:'Ext.data.Store',fields:['name','email'],data:[{name:'Ed',email:'ed@sencha.com'},{name:'Tommy',email:'tommy@sencha.com'}]});
现在我们需要做两处变更,首先我们需要让
Users
初始化的时候加载这个
Store
Ext.define('AM.controller.Users',{extend:'Ext.app.Controller',stores:['Users'],...});
然后我们要把之前直接在视图中内联的store更改掉,
app/view/user/List.js
,改成用id引用store
Ext.define('AM.view.user.List',{extend:'Ext.grid.Panel',alias:'widget.userlist',//wenolongerdefinetheUsersstoreinthe`initComponent`methodstore:'Users',...});
控制器的代码中中引入了store,store会被自动加载到页面并赋予一个storeId,这让视图中使用store变的容易(这个例子中,只要配置
store:'Users'
就可以了)现在我们只是在store中内联的定义了两个字段(‘name’和‘email’),这样可以工作,但是ExtJS4中有一个强大的
Ext.data.Model
类,在编辑用户的时候我们可以借助它,使用Model重构一下Store,在
app/model/User.js
中创建一个Model:
Ext.define('AM.model.User',{extend:'Ext.data.Model',fields:['name','email']});
这就是定义我们的Model需要做的,现在需要让Store引用Model替换掉使用内联字段的方式,并且让控制器也引用Model:
//theUserscontrollerwillmakesurethattheUsermodelisincludedonthepageandavailabletoourappExt.define('AM.controller.Users',{extend:'Ext.app.Controller',stores:['Users'],models:['User'],...});//wenowreferencetheModelinsteadofdefiningfieldsinlineExt.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'}]});
我们的重构可以让接下来的工作简单一下,但是不会影响现有功能,我们刷新一下页面,检查一下是否正常工作:

SavingdatawiththeModel利用模型保存数据

现在我们有了一个用户数据表,双击每一行都能打开一个编辑窗口,现在要做的是保存编辑变更,编辑窗口有一个编辑表单,还有保存按钮,现在我们更新一下控制器让保存按钮有响应:
Ext.define('AM.controller.Users',{init:function(){this.control({'viewport>userlist':{itemdblclick:this.editUser},'usereditbutton[action=save]':{click:this.updateUser}});},updateUser:function(button){console.log('clickedtheSavebutton');}});
我们在
this.control
方法中增加了一个组件查询选择器
'usereditbutton[action=save]'
,这里说明一下,这个选择器和第一个工作方式相同,注意我们定义保存按钮的时候增加了一个配置
{action:'save'}
,这个选择器的意思就是选择xytpe是useredit的组件下的所有action属性是save的按钮检查一下
updateUser
是不是被调用了:上图说明工作正常,接下来填充
updateUser
真正的逻辑。我们需要把数据从表单中取出,再设置回store中:
updateUser:function(button){varwin=button.up('window'),form=win.down('form'),record=form.getRecord(),values=form.getValues();record.set(values);win.close();}
让我们分解一下这里都做了什么。我们的响应函数接收到按钮的引用,但是我们关心的是表单和窗口,通过按钮和组件查询,可以找到关心的东西,这里首先用了
button.up('window')
找到了窗口,然后用
win.down('form')
找到了表单。这之后我们取出表单关联的record并用表单中的输入值更新了record,最后关闭了窗口关注回到表格上,我们把用户名更改为
'EdSpencer'
点击保存应该可以看到:

Savingtotheserver保存到服务器

很简单吧。让我们增加和服务器端的交互完成这个例子。现在我们还是应编码了两行表格的数据,现在让我们通过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
,代理是让Store或者Model加载和保存数据的一个方式,有AJAX,JSONP,HTML5的localStorage本地存储等。这里我们使用了一个简单的AJAX代理,让它通过URL
'data/users.json'
加载数据。我们同时给代理附加了一个reader,reader是用来把服务器返回的数据解码成Store能理解的格式,这次我们使用了JSONreader,并且指定了root和
successProperty
配置(JSONreader的详细配置看文档),最后我们创建一下数据文件
data/users.json
,输入内容:
{success:true,users:[{id:1,name:'Ed',email:'ed@sencha.com'},{id:2,name:'Tommy',email:'tommy@sencha.com'}]}
其他的变更就是我们给Store设置了
autoLoad
属性并设置为
true
,这意味着Store生成之后会自动让Proxy加载数据,刷新一下页面应该是看到和之前同样的结果,不同的是现在不是在程序中应编码数据了最后的事情是将变更传回服务器端,这个例子中我们使用静态的JSON文件,没有使用数据库,但足够说明我们例子的了,首先做一点点变化告知proxy用于更新的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}
,其他要做的就是让Store在编辑之后进行同步,需要在
updateUser
函数中增加一行代码:
updateUser:function(button){varwin=button.up('window'),form=win.down('form'),record=form.getRecord(),values=form.getValues();record.set(values);win.close();this.getUsersStore().sync();}
现在我们可以运行一下整个例子,检查是不是每个功能都正常,编辑表格中的一行,看看能不能正确的把请求发送给
updateUser.json

Deployment发布

新出台的SenchaSDKTools点此下载让ExtJS4的应用发布变的轻松。这个工具可以让你生成依赖清单,并生成一个最小化版本详细介绍可以查看ExtJS入门

NextSteps下一步

这里我们创建了一个非常简单的例子,但是基本功能都囊括了,你可以开始举一反三开发其他功能了,记得遵守开发模式,代码组织方式,以上代码的例子在ExtJS4SDK中,
examples/app/simple
目录中
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: