技术经验分享:ExtJS4MVC架构讲解

简介: 技术经验分享:ExtJS4MVC架构讲解

大规模客户端应用通常不好实现不好组织也不好维护,因为功能和人力的不断增加,这些应用的规模很快就会超出掌控能力,ExtJS 4 带来了一个新的应用架构,不但可以组织代码,还可以减少实现的内容


新的应用架构遵照一个类MVC的模式,模型(Models)和控制器(Controllers)首次被引入。业界有很多种MVC架构,基本大同小异,ExtJS 4的定义如下:


Model模型 是字段和它们的数据的集合,例如User模型带有username和password字段,模型知道如何持久化自己的数据,并且可以和其他模型关联,模型跟ExtJS 3 中的Record类有点像(区别是,Record只是单纯的扁平结构,而Model可以nest),通常都用在Store中去展示grid和其他组件的数据 View视图 是组件的一种,专注于界面展示 - grid, tree, panel 都是view Controllers控制器 一个安放所有使你的app正确工作的代码的位置,具体一点应该是所有动作,例如如何渲染view,如何初始化model,和app的其他逻辑


这篇教程中将创建一个简单的管理用户数据的例子,做完这个例子,你会学会如何用ExtJS 4的架构把简单的应用组织到一起。


应用架构是关于提供结构和一致性,是关于真实的类和框架代码的,遵守约定可以带来很多重要的好处:


每个应用都是相同的工作方式,所以你只需要学习一次 可以很容易的共享代码,因为所有应用工作模式都一样 可以创建属于自己的混淆的版本用于发布 File Structure文件结构


ExtJS 4 应用都遵循一个统一的目录结构,每个应有都相同。详情可以参见ExtJS 4 入门,MVC中,所有类都放在app目录里面,这个目录可以有子目录,代表的是命名空间(一个子目录对应一个命名空间),使用不同的目录存放views,models,controllers,stores,当我们完成例子的时候,目录结构应该和下面截图一样:


在这个例子中所有文件都在'account_manager'目录中,ExtJS SDK必须的文件在目录ext-4.0中,因此,index.html应该如下:


[span class="hljs-name">html

[span class="hljs-name">head

[span class="hljs-name">title

[span class="hljs-name">link rel="stylesheet" type="text/css" href="ext-4.0/resources/css/ext-all.css"

[span class="hljs-name">script type="text/javascript" src="ext-4.0/ext-debug.js"

[span class="hljs-name">script type="text/javascript" src="app.js"


[span class="hljs-name">body


Creating the application in app.js 在app.js中创建应用


每个ExtJS 4的应用都从一个Application类的实例开始,这个实例包含应用的全局配置(例如应用的名字),这个实例也负责维护对全部模型、视图、控制器的引用的维护,还有一个launch函数,会在所有加载项加载完成之后调用。


让我们创建一个简单的Account Manager应用管理用户帐户。首先需要选择一个全局命名空间,所有ExtJS4应用都需要有一个全局命名空间,以让所有应用中的类安放到其中,这个例子中我们使用AM(Account Manager)


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创建了一个应用实例,并设置了应用名称”AM”,这样做会自动创建一个全局变量”AM”,并自动注册命名空间”AM”到Ext.Loader,用类似的方式也设置了app作为appFolder。另外在launch函数中,创建了一个Viewport,只包含一个撑满浏览器窗口的Panel。


Defining a Controller 定义一个控制器


控制器是应用的粘合剂,它们所作的事情就是监听事件并执行动作,继续我们的Account Manager应用,创建一个控制器。创建app/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 //代码效果参考:http://hnjlyzjd.com/hw/wz_24519.html

called');

}


});


接下来在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('The panel was rendered');


}


});


我们已经更新了init方法,使用this.controll给视图设置监听器。这个controll方法,使用最新的组件查询引擎(ComponentQuery)可以快速方便的找到页面上的组件。如果你对ComponentQuery不熟悉,可以查看ComponentQuery文档进行详细了解。简要一点,ComponentQuery可以允许我们使用一个类似css选择器的方式找到组件。


在例子的init方法中我们应用了'viewport > panel',可以解释为“查找Viewport直接后代中的所有Panel组件”,然后我们提供了一个对象匹配事件名称(这个例子中只用了render)来提供响应函数。全部的影响就是无论哪个组件符合我们的选择器,当它的render事件触发时,我们的onPanelRendered函数都会被调用。


当我们运行应用程序,可以看到如下图所示:


这不是最激动人心的应用,但是它展示给我们,开始一段有组织的代码是多么容易。让我们增加一点内容,加一个grid。


Defining a View 定义一个视图


直到现在,我们的应用只有很少代码,只有两个文件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 : '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组件,并设置了别名,这样我们可以用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就是规则,把.替换成/就是文件存放路径。刷新一下页面:


Controlling the grid 对列表的控制


注意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('Double clicked on ' + 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 : '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方法去指定复杂的items和buttons对象,我们使用了一个'fit'布局和一个表单,表单包括了要编辑的用户信息字段,最后我们创建了两个按钮,一个用来关闭窗口,另一个用来保存变更。


现在我们要做的就是在控制器加载这个视图,渲染并且加载用户信息:


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'),然后我们又一次借助组件查询找到了窗口中的表单,每个ExtJS4中的组件都有一个down方法,可以借助组件查询支持的选择器来迅速找到任意下层的组件。


双击表格中的一行可以看到:


Creating a Model and a Store


现在我们有了表单,可以开始编辑和保存用户信息了,但是这之前需要做一点点重构。


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',


//we no longer define the Users store in the initComponent method


store: '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:


//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'}



});


我们的重构可以让接下来的工作简单一下,但是不会影响现有功能,我们刷新一下页面,检查一下是否正常工作:


Saving data with the Model 利用模型保存数据


现在我们有了一个用户数据表,双击每一行都能打开一个编辑窗口,现在要做的是保存编辑变更,编辑窗口有一个编辑表单,还有保存按钮,现在我们更新一下控制器让保存按钮有响应:


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方法中增加了一个组件查询选择器'useredit button【action=save】',这里说明一下,这个选择器和第一个工作方式相同,注意我们定义保存按钮的时候增加了一个配置{action: 'save'},这个选择器的意思就是选择xytpe是useredit的组件下的所有action属性是save的按钮


检查一下updateUser是不是被调用了:


上图说明工作正常,接下来填充updateUser真正的逻辑。我们需要把数据从表单中取出,再设置回store中:


updateUser: function(button) {


var win = 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,最后关闭了窗口关注回到表格上,我们把用户名更改为'Ed Spencer'点击保存应该可以看到:


Saving to the server 保存到服务器


很简单吧。让我们增加和服务器端的交互完成这个例子。现在我们还是应编码了两行表格的数据,现在让我们通过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能理解的格式,这次我们使用了JSON reader,并且指定了root和successProperty配置(JSON reader的详细配置看文档),最后我们创建一下数据文件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) {


var win = button.up('window'),


form = win.down('form'),


record = form.getRecord(),


values = form.getValues();


record.set(values);


win.close();


this.getUsersStore().sync();


}


现在我们可以运行一下整个例子,检查是不是每个功能都正常,编辑表格中的一行,看看能不能正确的把请求发送给updateUser.json


Deployment 发布


新出台的Sencha SDK Tools 点此下载 让ExtJS4的应用发布变的轻松。这个工具可以让你生成依赖清单,并生成一个最小化版本


详细介绍可以查看 ExtJS 入门


Next Steps 下一步


这里我们创建了一个非常简单的例子,但是基本功能都囊括了,你可以开始举一反三开发其他功能了,记得遵守开发模式,代码组织方式,以上代码的例子在Ext JS 4 SDK中,examples/app/simple目录中

相关文章
|
2天前
|
负载均衡 5G 网络性能优化
深入解析LTE(长期演进技术)的基本架构及其关键组件
深入解析LTE(长期演进技术)的基本架构及其关键组件
18 2
|
5天前
|
Cloud Native 持续交付 云计算
云原生技术:重塑软件开发与架构的未来
在云计算的推动下,云原生技术正逐渐成为软件开发的新标准,强调利用容器、服务网格、微服务等技术实现敏捷开发与高效运维。本文探讨了云原生技术如何重塑软件开发与架构的未来,介绍了其核心概念(如容器化、微服务架构、CI/CD)及优势(如敏捷性、可扩展性、成本效益),并讨论了其在金融服务、电子商务和物联网等领域的实际应用及面临的挑战。尽管存在技术复杂性和人才短缺等问题,云原生技术仍将成为软件开发的主流趋势。
|
1天前
|
Cloud Native Devops 持续交付
云原生技术:引领未来IT架构的变革
本文深入探讨了云原生技术的崛起,及其如何通过容器化、微服务架构和DevOps实践等核心原则,推动现代IT基础设施向更加敏捷、高效和可扩展的方向演进。通过分析云原生技术的发展趋势和挑战,揭示了其对业务模式、开发流程和文化的深远影响,为读者呈现了一个全面而深刻的云原生世界视角。
|
2天前
|
Kubernetes Cloud Native 持续交付
深入理解云原生技术及其在现代IT架构中的应用
【9月更文挑战第18天】云原生技术,作为推动企业数字化转型的引擎,正以它独特的魅力重塑着信息技术的未来。本文将带你一探究竟,从云原生的基础概念出发,逐步深入到其核心组件、设计理念以及如何在实际应用中发挥巨大作用。你将了解到容器化、微服务架构、持续集成与持续部署(CI/CD)等关键实践,并见证它们如何帮助企业构建更加灵活、高效和可靠的应用。
|
1天前
|
运维 Cloud Native 安全
云原生技术:重塑现代IT架构的引擎
在当今数字化时代,企业正面临着前所未有的挑战与机遇。随着云计算技术的不断发展,云原生技术作为其核心驱动力之一,正在彻底改变企业的IT架构和运营模式。本文将深入探讨云原生技术的内涵、特点及其对企业数字化转型的影响,揭示其在现代IT架构中的核心地位和作用。同时,我们还将分析云原生技术面临的安全挑战,并展望未来的发展趋势,为企业在云原生领域的实践提供有益的参考。
|
2天前
|
运维 Kubernetes Cloud Native
探索云原生技术:容器化与微服务架构的融合之道
【9月更文挑战第18天】在数字化转型的浪潮中,云原生技术以其灵活性、可扩展性成为企业创新的强大引擎。本文将深入探讨云原生技术的核心概念,特别是容器化和微服务架构如何相辅相成,共同推动现代应用的开发与部署。通过实际代码示例,我们将揭示这些技术如何简化运维,加速产品上市时间,并提高系统的可靠性和弹性。无论你是开发人员、架构师还是IT决策者,这篇文章都将为你提供宝贵的洞见和实践指导。
12 2
|
2天前
|
Kubernetes Cloud Native Java
云原生技术之旅:从容器化到微服务架构
【9月更文挑战第18天】云原生技术正改变着我们构建、部署和管理应用的方式。本文将通过一次虚拟的旅行,带领读者探索云原生的核心概念,如容器化、微服务、持续集成与交付等。我们将以一个实际案例为线索,逐步展开对Kubernetes集群管理、Docker容器创建和Spring Boot微服务开发的讨论。就像在旅途中不断发现新风景一样,您将了解到这些技术如何协同工作,提升开发效率和应用性能。准备好了吗?让我们启航!
|
3天前
|
运维 Cloud Native Devops
云原生技术:重塑现代IT架构的新引擎
在当今数字化转型的浪潮中,云原生技术以其敏捷、高效和可扩展的特性,正引领着一场IT架构的革命。本文旨在深入探讨云原生的概念、核心组件及其在现代企业中的应用价值,揭示其如何助力企业实现更快的创新速度、更高的资源利用率以及更优的用户体验。不同于传统的云计算模式,云原生从一开始就为云环境量身打造,通过容器化、微服务、DevOps等关键技术,解锁了软件开发和运维的新范式。
|
4天前
|
存储 缓存 关系型数据库
后端技术在现代软件架构中的关键作用
本文将深入探讨后端技术在现代软件架构中的关键作用,从其定义、重要性到具体应用案例,全面解析后端技术如何支撑复杂系统的高效运行。通过简明易懂的语言和条理清晰的结构,帮助读者理解后端技术的核心概念及其在实际项目中的应用。
14 0
|
7天前
|
存储 安全 算法
探索操作系统的心脏:内核技术与架构
本文深入探讨了现代操作系统中至关重要的部分——内核。通过分析其功能、架构以及在系统性能和稳定性中的作用,揭示了内核技术背后的复杂性及其对操作系统整体表现的影响。我们将从基本概念入手,逐步深入到内核的具体实现细节,旨在为读者提供一个全面而清晰的理解框架。