1 Extjs初步
1.1 搭建ExtJS可视化开发环境:
假设您的机器已经安装Eclipse,Tomcat和Sql Server(或MySql),如果没有请参阅其他相关资料。
安装ExtJS应用的可视化开发环境:
可以从http://www.putdb.com获取,或直接从http://www.putdb.com/zh_cn.html下载,并按提示或以下步骤进行安装:
1、安装任意类型的一款 JAVA Web 应用服务器(如 Tomcat)和数据库(如 MySql),并且在Web应用服务器中创建一个 JNDI 链接串,以访问所创建的数据库。
2、解压缩下载的安装包 webbuilder.zip,并把 wb 目录部署到 Web 应用服务器。例如,您使用 Tomcat, 那么可直接复制 wb 目录至Tomcat的 webapps/wb目录。
3、打开浏览器并访问 [http://ip:port/wb],使用安装向导来完成WebBuilder 的安装。例如,访问http://localhost:8080/wb
4、使用管理员帐户登录到系统,管理员默认用户名称和密码均为 admin。
1.2 测试例子
开始...
Webroot目录下建立hellowword.js 输入如下内容:
Ext.application({ name: 'HelloExt', launch: function() { Ext.create('Ext.container.Viewport', { layout: 'fit', items: [ { title: 'Hello Ext', html : 'Hello! Welcome to Ext JS.' } ] }); } }); |
再建立一个helloword.html,输入如下内容
<html> <head> <title>Hello Ext</title> <link rel="stylesheet" type="text/css" href="resources/css/ext-all.css"> <script type="text/javascript" src="ext-all.js"></script> <script type="text/javascript" src="HelloExt.js"></script> </head> <body></body> </html> |
Html文件中只引入了一个css和2个js文件,注意引用路径和你建立文件路径是否能匹配,如果路径没有问题的话,打开浏览器输入
http://localhost:8080/Ext4/helloworld.html您将会看到浏览器里显示一个panel,标题是Hello Ext,内容Hello! Welcome to Ext JS.,如果没有,请查看是否有路径不匹配。
其他:
在ExtJS里最常用的,应该就是Ext.onReady和Ext.application这两个个方法了,而且它也可能是你学习ExtJS所接触的第一个方法,这个方法在当前的DOM加载完毕后自动调用,保证页面内的所有元素都能被Script所引用.可以尝试在这个方法中添加一条语句,看看页面打开后是什么反映
(先建立js和html文件,将如下代码加入js文件中,html文件相应引入对应的js文件, 本文档所有示例代码均如此方式运行以下不再重复)
Ext.onReady(function() { alert('hello world!'); }); |
上面的代码将在页面加载完毕后弹出一对话框,打印出'hello world!'字样.
获取元素
还有一个常用的方法,就是获取页面上的元素了,ExtJS提供了一个get方法,可以根据ID取到页面上的元素:
var myDiv = Ext.get('myDiv');
会取到页面上ID为'myDiv'的元素.如果使用Element.dom的方法,则可以直接操作底层的DOM节点,Ext.get返回的则是一个Element对象.
在不能使用这种方式来获取多个DOM的节点,或是要获取一些ID不一致,但又有相同特征的时候,可以通过选择器来进行获取,比如要获取页面上所有的
标签,则可以使用:
var ps = Ext.select('p');
这样你就可以对所要获取的元素进行操作了,select()方法返回的是Ext.CompositeElement对象,可以通过其中的each()方法对其所包含的节点进行遍历:
ps.each(function(el) { el.highlight(); }); |
当然,如果你要是对获取的所有元素进行相同的操作,可以直接应用于CompositeElement对象上,如:
ps.highlight();
或是:
Ext.select('p').highlight();
当然,select参数还可以更复杂一些,其中可以包括W3C Css3Dom选取器,基本的XPath,HTML属性等,详细情况,可以查看DomQuery API的文档,来了解细节.
事件响应
获取到了元素,则可能会对一些元素的事件进行一些处理,比如获取一个按钮,我们为它添加一个单击事件的响应:
复制代码 代码如下:
Ext.onReady(function() { Ext.get('myButton').on('click', function() { alert('You clicked the button!'); }); }); |
当然,你可以把事件的响应加到通过select()方法获取到的元素上:
复制代码 代码如下:
Ext.select('p').on('click', function() { alert('You clicked a paragraph!'); }); |
Widgets
ExtJS还提供了丰富的UI库来供大家使用.
2 Extjs4布局详解
2.1 Fit布局
在Fit布局中,子元素将自动填满整个父容器。注意:在fit布局下,对其子元素设置宽度是无效的。如果在fit布局中放置了多个组件,则只会显示第一个子元素
在Fit布局中,子元素将自动填满整个父容器。注意:在fit布局下,对其子元素设置宽度是无效的。如果在fit布局中放置了多个组件,则只会显示第一个子元素。典型的案例就是当客户要求一个window或panel中放置一个GRID组件,grid组件的大小会随着父容器的大小改变而改变。
示例代码:
Ext.application({ name: 'HelloExt', launch: function() { Ext.create('Ext.container.Viewport', { layout: 'fit', items: [ { title: 'Hello Ext', html : 'Hello! Welcome to Ext JS.' } ] }); } }); |
2.2 Border布局
border布局:border布局也称边界布局,他将页面分隔为west,east,south,north,center这五个部分,我们需要在在其items中指定使用region参数为其子元素指定具体位置。
border布局:border布局也称边界布局,他将页面分隔为west,east,south,north,center这五个部分,我们需要在在其items中指定使用region参数为其子元素指定具体位置。
注意:north和south部分只能设置高度(height),west和east部分只能设置宽度(width)。north south west east区域变大,center区域就变小了。
参数 split:true 可以调整除了center四个区域的大小。
参数 collapsible:true 将激活折叠功能,title必须设置,因为折叠按钮是出现标题部分的。
center 区域是必须使用的,而且center 区域不允许折叠。Center区域会自动填充其他区域的剩余空间。尤其在Extjs4.0中,当指定布局为border时,没有指定center区域时,会出现报错信息。
示例代码:
Ext.application({ name:"HelloExt", launch:function () { Ext.create('Ext.panel.Panel', { width: 1024, height: 720, layout: 'border', items: [{ region: 'south', xtype: 'panel', height: 20, split: false, html: '欢迎登录!', margins: '0 5 5 5' },{ title: 'West Region is collapsible', region:'west', xtype: 'panel', margins: '5 0 0 5', width: 200, collapsible: true, id: 'west-region-container', layout: 'fit' },{ title: 'Center Region', region: 'center', xtype: 'panel', layout: 'fit', margins: '5 5 0 0', html:'在Extjs4中,center区域必须指定,否则会报错。' }], renderTo: Ext.getBody() }); } }); |
2.3 Accordion布局
accordion布局:accordion布局也称手风琴布局,在accordion布局下,在任何时间里,只有一个面板处于激活状态。其中每个面边都支持展开和折叠。
accordion布局:accordion布局也称手风琴布局,在accordion布局下,在任何时间里,只有一个面板处于激活状态。其中每个面边都支持展开和折叠。注意:只有Ext.Panels 和所有Ext.panel.Panel 子项,才可以使用accordion布局。
示例代码:
Ext.application({ name:"HelloExt", launch:function () { Ext.create('Ext.panel.Panel', { title: 'Accordion Layout', width: 300, height: 300, x:20, y:20, layout:'accordion', defaults: { bodyStyle: 'padding:15px' }, layoutConfig: { titleCollapse: false, animate: true, activeOnTop: true }, items: [{ title: 'Panel 1', html: 'Panel content!' },{ title: 'Panel 2', html: 'Panel content!' },{ title: 'Panel 3', html: 'Panel content!' }], renderTo: Ext.getBody() }); }}); |
2.4 Card布局
Card布局:这种布局用来管理多个子组件,并且在任何时刻只能显示一个子组件。这种布局最常用的情况是向导模式,也就是我们所说的分布提交。
Card布局:这种布局用来管理多个子组件,并且在任何时刻只能显示一个子组件。这种布局最常用的情况是向导模式,也就是我们所说的分布提交。Card布局可以使用layout:'card'来创建。注意:由于此布局本身不提供分步导航功能,所以需要用户自己开发该功能。由于只有一个面板处于显示状态,那么在初始时,我们可以使用setActiveItem功能来指定某一个面板的显示。当要显示下一个面板或者上一个面板的时候,我们可以使用getNext()或getPrev()来得到下一个或上一个面板。然后使用setDisabled方法来设置面板的显示。另外,如果面板中显示的是FORM布局,我们在点击下一个面板的时候,处理FORM中提交的元素,通过AJAX将表单中的内容保存到数据库中或者SESSION中。
下面的示例代码展示了一个基本的Card布局,布局中并没有包含form元素,具体情况,还要根据实际情况进行处理:
Ext.application({ name: 'HelloExt', launch: function() { var navigate = function(panel, direction){ var layout = panel.getLayout(); layout[direction](); Ext.getCmp('move-prev').setDisabled(!layout.getPrev()); Ext.getCmp('move-next').setDisabled(!layout.getNext()); }; Ext.create('Ext.panel.Panel', { title: 'Card布局示例', width: 300, height: 202, layout: 'card', activeItem: 0, x:30, y:60, bodyStyle: 'padding:15px', defaults: {border: false}, bbar: [{ id: 'move-prev', text: 'Back', handler: function(btn) { navigate(btn.up("panel"), "prev"); }, disabled: true }, '->', { id: 'move-next', text: 'Next', handler: function(btn) { navigate(btn.up("panel"), "next"); } }], items: [{ id: 'card-0', html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>' }, { id: 'card-1', html: '<p>Step 2 of 3</p>' }, { id: 'card-2', html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>' }], renderTo: Ext.getBody() }); } }); |
2.5 Anchor布局
anchor布局将使组件固定于父容器的某一个位置,使用anchor布局的子组件尺寸相对于容器的尺寸,即父容器容器的大小发生变化时,使用anchor布局的组件会根据规定的规则重新渲染位置和大小。
anchor布局将使组件固定于父容器的某一个位置,使用anchor布局的子组件尺寸相对于容器的尺寸,即父容器容器的大小发生变化时,使用anchor布局的组件会根据规定的规则重新渲染位置和大小。
AnchorLayout布局没有任何的直接配置选项(继承的除外),然而在使用AnchorLayout布局时,其子组件都有一个anchor属性,用来配置此子组件在父容器中所处的位置。
anchor属性为一组字符串,可以使用百分比或者是-数字来表示。配置字符串使用空格隔开,例如
anchor:'75% 25%',表示宽度为父容器的75%,高度为父容器的25%
anchor:'-300 -200',表示组件相对于父容器右边距为300,相对于父容器的底部位200
anchor:'-250 20%',混合模式,表示组件党对于如容器右边为250,高度为父容器的20%
示例代码:
Ext.application({ name: 'HelloExt', launch: function() { Ext.create('Ext.Panel', { width: 500, height: 400, title: "Anchor布局", layout: 'anchor', x:60, y:80, renderTo: Ext.getBody(), items: [{ xtype: 'panel', title: '75% Width and 25% Height', anchor: '75% 25%' },{ xtype: 'panel', title: 'Offset -300 Width & -200 Height', anchor: '-300 -200' },{ xtype: 'panel', title: 'Mixed Offset and Percent', anchor: '-250 30%' }] }); } }); |
2.6 Absolute布局
Absolute布局继承Ext.layout.container.Anchor 布局方式,并增加了X/Y配置选项对子组件进行定位,Absolute布局的目的是为了扩展布局的属性,使得布局更容易使用
Absolute布局继承Ext.layout.container.Anchor 布局方式,并增加了X/Y配置选项对子组件进行定位,Absolute布局的目的是为了扩展布局的属性,使得布局更容易使用。
Ext.application({ name:"HelloExt", launch:function () { Ext.create('Ext.form.Panel', { title: 'Absolute布局', width: 300, height: 275, x:20, y:90, layout:'absolute', defaultType: 'textfield', items: [{ x: 10, y: 10, xtype:'label', text: 'Send To:' },{ x: 80, y: 10, name: 'to', anchor:'90%' },{ x: 10, y: 40, xtype:'label', text: 'Subject:' },{ x: 80, y: 40, name: 'subject', anchor: '90%' },{ x:0, y: 80, xtype: 'textareafield', name: 'msg', anchor: '100% 100%' }], renderTo: Ext.getBody() }); } }); |
2.7 Column布局
Column布局一般被称为列布局,这种布局的目的是为了创建一个多列的格式。其中每列的宽度,可以为其指定一个百分比或者是一个固定的宽度。
Column布局一般被称为列布局,这种布局的目的是为了创建一个多列的格式。其中每列的宽度,可以为其指定一个百分比或者是一个固定的宽度。
Column布局没有直接的配置选项(继承的除外),但Column布局支持一个columnWidth属性,在布局过程中,使用columnWidth指定每个面板的宽度。
注意:使用Column布局布局时,其子面板的所有columnWidth值加起来必须介于0~1之间或者是所占百分比。他们的总和应该是1。
另外,如果任何子面板没有指定columnWidth值,那么它将占满剩余的空间。
示例代码:
Ext.application({ name:"HelloExt", launch:function () { Ext.create('Ext.panel.Panel', { title: 'Column Layout - 按比例', width: 350, height: 250, x:20, y:100, layout:'column', items: [{ title: 'Column 1', columnWidth: .25 },{ title: 'Column 2', columnWidth: .55 },{ title: 'Column 3', columnWidth: .20 }], renderTo: Ext.getBody() }); } }); |
3 Extjs4文档阅读
ExtJS4使用新的类机制进行了大量的重构。为了支撑新的架构,ext4几乎重写了每一个类,因此最好先好好的理解一下新的架构,再开始编码。
本文适合想在extjs4中扩展现有类或者创建新类的开发者。其实,不管是想扩展还是使用,都建议您仔细阅读一下(如果E文好的,建议您还是阅读英文原文。链接地址是:http://docs.sencha.com/ext-js/4-0/#/guide/ )。文章共分4个部分,建议每一部分都仔细研究下,对之后的开发工作,会有意想不到的好处。
3.1 系统类(class system)
Api文档路径:http://localhost/Ext4/docs/index.html#!/guide/class_system
第一部分:概述。说明了强大的类机制的必要性
第二部分:编码规范。讨论类、方法、属性、变量和文件命名
第三部分:DIY。详细的编码示例
第四部分:错误处理和调试。提供一些有用的调试和异常处理技巧
3.1.1 概述
ExtJS4拥有超过300个的类.迄今为止,我们的社区拥有超过20万来自世界各地,使用不同后台语言的开发者.要在这种规模的框架上提供具有以下特点的架构,需要面临巨大的挑战:
1、简单易学。
2,快速开发、调试简单、部署容易。
3,良好的结构、可扩展性和可维护性。
3.1.2 编码和规范
*在所有类、命名空间(namespace)和文件名中使用一致的命名约定,有助于保持代码的良好结构和可读性。
1) Classes
类名只能包含字母和数字。允许包含数字,但是大部分情况下不建议使用,除非这些数字是专业术语的一部分。不要使用下划线,连字符等非数字字母符号。例如:
MyCompany.useful_util.Debug_Toolbar is discouraged MyCompany.util.Base64 is acceptable |
类名应该包含在使用点号分隔的命名空间中。至少,要有一个顶级命名空间。例如:
MyCompany.data.CoolProxyMyCompany.Application |
顶级命名空间和实际的类名应使用驼峰命名(CamelCased),其他则为小写。例如:
MyCompany.form.action.AutoLoad |
不是Sencha开发的类(即不是Ext自带的)不要使用Ext做为顶级命名空间。缩写也要遵守以上的驼峰式命名约定。例如:
Ext.data.JsonProxy 代替 Ext.data.JSONProxy MyCompany.util.HtmlParser代替MyCompary.parser.HTMLParser MyCompany.server.Http代替MyCompany.server.HTTP |
2) 代码文件类名对应类所在的文件(包括文件名)。因此,每个文件应该只包含一个类(类名和文件名一样)。例如:
Ext.util.Observable 存放在 path/to/src/Ext/util/Observable.js
Ext.form.action.Submit 存放在 path/to/src/Ext/form/action/Submit.js
MyCompany.chart.axis.Numeric 存放在 path/to/src/MyCompany/chart/axis/Numeric.js
path/to/src是你的应用所在目录。所有类都应该在这个通用根目录下,并且使用适当的命名空间以利于开发、维护和部署。
3)方法和变量
•和类命名一样,方法和变量也只能包含字母和数字。数字同样是允许但不建议,除非属于专业术语。不要使用下划线,连字符等任何非字母数字符号。
•方法和变量名一样使用驼峰式命名,缩写也一样。
•举例
•合适的方法名:
encodeUsingMd5() getHtml() 代替 getHTML()
getJsonResponse() 代替getJSONResponse()
parseXmlContent() 代替parseXMLContent()
•合适的变量名:
var isGoodName
var base64Encoder
var xmlReader
var httpServer
4)属性
- 类属性名称遵循以上的变量和方法命名约定.除非是静态的常量.
- 类的静态属性常量应该全部大写。例如:
- Ext.MessageBox.YES = "Yes"
- Ext.MessageBox.NO = "No"
- MyCompany.alien.Math.PI = "4.13"
3.1.3 DIY亲自动手(示例代码)
3.1.3.1 声明
3.1.3.1.1 Extjs4之前的方式
如果你曾经使用过旧版本的extjs,那么你肯定熟悉使用Ext.extend来创建一个类:
1: var MyWindow=Ext.extend(Object,{...}); |
这个方法很容易从现有的类中继承创建新的类.相比直接继承,我们没有好用的API用于类创建的其他方面,诸如:配置、静态方法、混入(Mixins)。呆会我们再来详细的重新审视这些方面。现在,让我们来看看另一个例子:
1: My.cool.Window = Ext.extend(Ext.Window, { ... }); |
在这个例子中,我们创建我们的新类,继承Ext.Window,放在命名空间中。我们有两个问题要解决:
1,在我们访问My.cool的Window属性之前,My.cool必须是一个已有的对象.
2,Ext.Window必须在引用之前加载.
第一个问题通常使用Ext.namespace(别名Ext.ns)来解决.该方法递归创建(如果该对象不存在)这些对象依赖.比较繁琐枯燥的部分是你必须在Ext.extend之前执行Ext.ns来创建它们.
1: Ext.ns('My.cool'); 2: My.cool.Window = Ext.extend(Ext.Window, { ... }); |
第二个问题不好解决,因为Ext.Window可能直接或间接的依赖于许多其他的类,依赖的类可能还依赖其它类...出于这个原因,在ext4之前,我们通常引入整个ext-all.js,即使是我们只需要其中的一小部分.
3.1.3.1.2 Extjs4新的方式
在Extjs4中,你只需要使用一个方法就可以解决这些问题:Ext.define.以下是它的基本语法:
1: Ext.define(className, members, onClassCreated); |
className: 类名
members:代表类成员的对象字面量(键值对,json)
onClassCreated: 可选的回调函数,在所有依赖都加载完毕,并且类本身建立后触发.由于类创建的新的异步特性,这个回调函数在很多情况下都很有用.这些在第四节中将进一步讨论
例如:
· Ext.define('My.sample.Person', { name: 'Unknown', constructor: function(name) { if (name) { this.name = name; } return this; }, eat: function(foodType) { alert(this.name + " is eating: " + foodType); return this; } }); var aaron = Ext.create('My.sample.Person', 'Aaron'); aaron.eat("Salad"); |
程序执行结果会弹出alert窗口显示"Aaronis eating: Salad".
· 注意我们使用Ext.create()方法创建了My.sample.Person类的一个新实例.我们也可以使用新的关键字(new My.sample.Person())来创建.然而,建议养成始终用Ext.create来创建类示例的习惯,因为它允许你利用动态加载的优势.更多关于动态加载信息,请看入门指南:入门指南
3.1.3.2 配置
在ExtJS 4 ,我们引入了一个专门的配置属性,用于提供在类创建前的预处理功能.特性包括:
- 配置完全封装其他类成员
- getter和setter.如果类没有定义这些方法,在创建类时将自动生成配置的属性的getter和setter方法。
- 同样的,每个配置的属性自动生成一个apply方法.自动生成的setter方法内部在设置值之前调用apply方法.如果你要在设置值之前自定义自己的逻辑,那就重载apply方法.如果apply没有返回值,则setter不会设置值.看下面applyTitle的例子:
· Ext.define('My.own.Window', { /** @readonly */ isWindow: true, config: { title: 'Title Here', bottomBar: { enabled: true, height: 50, resizable: false } }, constructor: function(config) { this.initConfig(config); return this; }, applyTitle: function(title) { if (!Ext.isString(title) || title.length === 0) { alert('Error: Title must be a valid non-empty string'); } else { return title; } }, applyBottomBar: function(bottomBar) { if (bottomBar && bottomBar.enabled) { if (!this.bottomBar) { return Ext.create('My.own.WindowBottomBar', bottomBar); } else { this.bottomBar.setConfig(bottomBar); } } } }); |
以下是它的用法:
var myWindow = Ext.create('My.own.Window', { title: 'Hello World', bottomBar: { height: 60 } }); alert(myWindow.getTitle()); // alerts "Hello World" myWindow.setTitle('Something New'); alert(myWindow.getTitle()); // alerts "Something New" myWindow.setTitle(null); // alerts "Error: Title must be a valid non-empty string" myWindow.setBottomBar({ height: 100 }); // Bottom bar's height is changed to 100 |
3.1.3.3 Statics
静态成员可以使用statics配置项来定义
Ext.define('Computer', { statics: { instanceCount: 0, factory: function(brand) { // 'this' in static methods refer to the class itself return new this({brand: brand}); } }, config: { brand: null }, constructor: function(config) { this.initConfig(config); // the 'self' property of an instance refers to its class this.self.instanceCount ++; return this; } }); var dellComputer = Computer.factory('Dell'); var appleComputer = Computer.factory('Mac'); alert(appleComputer.getBrand()); // using the auto-generated getter to get the value of a config property. Alerts "Mac" alert(Computer.instanceCount); // Alerts "2" |
3.1.3.4 错误处理&调试
Extjs 4包含一些有用的特性用于调试和错误处理.你可以使用Ext.getDisplayName()来显示任意方法的名字.这对显示抛出异常的类和方法非常有用.
throw new Error('['+ Ext.getDisplayName(arguments.callee) +'] Some message here'); |
当使用Ext.define()定义的类中的方法抛出异常后.你将在调用堆栈中看到类和方法名(如果你使用webkit).例如,以下是chrome浏览器的效果:
javascript是一种类无关(原文:classless)、基于原型的语言。因此javascript最强大的特点是灵活。同样的事情可以使用不同的方式,不同的编码风格和技巧去完成。这个特点也会带来一些不可预测的风险。如果没有统一的编码规范,javascript代码将很难理解、维护和复用。
相反的,基于类的编程语言拥有较为流行的面向对象模型,强类型、内建的封装机制和强制的编码约束等特点。通过强制开发人员遵守一些大的原则来使代码的行为更容易被理解,以及提高可扩展性(这里不明白,javascript这类动态语言不是更容易扩展么?)和可伸缩性。但是,这类语言没有javascript的灵活性
3.2 MVC应用模式
一直想写一些Extjs4 MVC的东西,但由于自己的英文水平足够媲美小学5年纪的学生,所以在找了一些比我强一点的网友+机器翻译,总结出了以下这篇文章。但个人强烈建议去看英文原版(http://localhost/Ext4/docs/index.html#!/guide/application_architecture)。本段代码示例建议使用firefox firebug插件配合使用,ie无法加在console.log对象。
那么,我们开始吧!
对于Extjs来说,大客户端程序一直很难写,当你为大客户端程序添加更多的功能和项目的时候,项目的体积往往迅速增长。这样的大客户端程序很难组织和维持 ,所以,Extjs4配备了一个新的应用程序体系结构,它能结构化你的代码,那就是Extjs4 MVC。
Extjs4 MVC有别于其他MVC架构,Extjs有他自己定义:
1、Model是一个Field以及他的Data的集合,Modes知道如何通过Stores来表示数据,以能用于网格和其他组件。模型的工作很像Extjs3的记录集(Record class),通常通过数据加载器(Stores)渲染至网格(grid)和其他组件上边。
2、View:用以装载任何类型的组件—grid、tree和panel等等。
3、Controller—用来放使得app工作的代码,例如 render views , instantiating Models 或者其他应用逻辑。
本篇文章,我们将创建一个非常简单的应用程序,即用户数据管理,最后,你就会知道如何利用Extjs4 MVC去创建简单应用程序。Extjs4 MVC应用程序架构提供应用程序的结构性和一致性。这样的模式带来了一些重要的好处:
1、 每个应用程序的工作方式相同,我们只需要学习一次。
2、 应用程序之间的代码共享很容易,应为他们所有的工作方式都相同
3、 你可以使用EXTJS提供的构建工具创建你应用程序的优化版本。
既然是介绍Extjs4 MVC,那么,我们开始创建这个应用。
3.2.1 文件结构(File Structure):
Extjs4 MVC的应用程序和别的应用程序一样都遵循一个统一的目录结构。在MVC布局中,所有的类放在应用程序文件夹,它的子文件夹中包含您的命名空间,模型,视图,控制器和存储器。下面来通过简单的例子来看下怎样应用。
在这个例子中,我们将整个应用程序放到一个名为”account_manager”的文件夹下,”account_manager”文件夹中的结构如上图。现在编辑index.html,内容如下:
<html> <head> <title>Account Manager</title> <link rel="stylesheet" type="text/css" href="ext-4.0/resources/css/ext-all.css"> <script type="text/javascript" src="ext-4.0/ext-debug.js"></script> <script type="text/javascript" src="app.js"></script> </head> <body></body> </html> |
3.2.2 创建app.js文件(Creating the application)
所有Extjs4MVC应用从Ext.application的一个实例开始,应用程序中应包含全局设置、以及应用程序所使用的模型(model),视图(view)和控制器(controllers),一个应用程序还应该包含一个发射功能(launch function)。
现在来创建一个简单的账户管理应用。首先,需要选择一个命名空间(所有extjs4应用应该使用一个单一的全局变来来作为命名空间)。暂时,使用”AM”来作为命名空间。
Ext.application({ 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({
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”的命名空间,他将作为整个应用的全局变量,也将作为Ext.Loader的命名空间,然后通过appFolder来指定配置选项设置相应的路径。最后,创建了一个简单的发射功能,这里仅仅创建了一个Viewport,其中包含一个panel,使其充满整个窗口。
3.2.3 定义一个控制器(Defining a Controller)
控制器是整个应用程序的关键,他负责监听事件,并对某些时间做出相应的动作。现在我们创建一个控制器,将其命名为Users.js,其路径是app/controller/Users.js。然后,我们为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'); } }); |
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' ], ... }); |
当我们访问index.html时,用户控制器(Users.js)自动加载,因为我们在上面的app.js中的定义中指定了。Init函数一个最棒的地方是控制器与视图的交互,这里的交互是指控制功能,因为他很容易就可以监听到视图类的事件处理函数,并采取相应的措施,并及时渲染相关信息到面板上来。
编写Users.js:
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'); } }); |
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');
}
});
在Users.js中,init函数使用this.control来负责监听,由于使用了新的ComponentQuery引擎,所以可以快速方便的找到页面上组件的引用(viewport > panel),这有些类似CSS选择器,通过匹配,快速的找到相匹配的组件。
在上面的init函数中,我们使用viewport> panel,来找到app.js中Viewport下的panel组件,然后,我们提供了一个对象的处理函数(this. onPanelRendered,注意,这里的对象是this,他的处理函数是onPanelRendered)。整个效果是,只要符合触发render事件的任何组件,那么onPanelRendered函数将被调用。当运行我们的应用程序,我们将看到以下内容。
3.2.4 定义一个视图(Defining a View)
到目前为止,应用程序只有几行,也只有两个文件,app.js和app/controller/Users.js。现在我们来增加一个grid,来显示整个系统中的所有用户。作为视图组件的一个子类,我们创建一个新的文件,并把他放到app/view/user目录下。命名为List.js。整个路径是这样的。app/view/user/List.js,下面,我们写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); } }); |
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);
}});
我们创建好的这个类,只是一个非常普通的类,并没有任何意义,为了能让我们更好的使用这个定义好的类,所以我们使用alias来定义一个别名,这个时候,我们的类可以使用Ext.create()和Ext.widget()创建,在其他组件的子组件中,也可以使用xtype来创建。
Ext.define('AM.controller.Users', { extend: 'Ext.app.Controller', views: [ 'user.List' ], init: ... onPanelRendered: ... }); |
修改Users.js,增加views属性,修改app.js中的launch方法,将List渲染到Viewport。
Ext.application({ ... launch: function() { Ext.create('Ext.container.Viewport', { layout: 'fit', items: { xtype: 'userlist' } }); } }); |
Ext.application({
... launch: function() {
Ext.create('Ext.container.Viewport', {
layout: 'fit',
items: {
xtype: 'userlist'
}
});
}});
看到这里,也许会有人开始抓狂了,这个user.List到底是怎么来的,为什么要这样写?如果你开始感到疑惑,那么不妨看看Ext.Loader是如何工作的(参见文档其他部分),在看过Ext.Loader之后,你就会明白了,User.List就是app/view/user下的List.js文件。为什么Ext要从view下找呢?因为我们在控制器中定了views: ['user.List']。这就是Extjs动态加载的强大之处,具体Ext.Loader,请看本站的其他文章,你就会明白了。当我们刷新页面。
3.2.5 控制网格(Controlling the grid)
要注意的是,onPanelRendered功能仍然是被调用的。这是因为gird匹配'viewport > panel'。
然后我们添加一个监听,当我们双击grid中的行,就可以编辑用户。
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')); } }); |
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')。
如果要实现真正编辑用户,那么我们需要一个真正用于用户编辑window,接着,创建一个JS文件。其路径是: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.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创建了一个表单和两个按钮,表单中,两个字段分别装载用户名和电子邮件。接下来,我们修改视图控制器,使其可以载入用户数据。
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.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快速获取编辑用户的形式引用。在Ext JS4的每个组件有一个down函数,可以使用它快速的找到任何他的子组件。当双击Grid中的行,我们可以看到如下图所示:
3.2.6 创建Model 和 Store
|
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'} ] }); |
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'}
]
});
接下来修改两个文件:
Ext.define('AM.controller.Users', { extend: 'Ext.app.Controller', stores: [ 'Users' ], ... }); |
Ext.define('AM.controller.Users', {
extend: 'Ext.app.Controller',
stores: [ 'Users' ],
...
});
修改app/view/user/List.js,使其引用Users
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', ... }); |
Ext.define('AM.view.user.List' ,{
extend: 'Ext.grid.Panel',
alias :'widget.userlist',
//we no longer definethe Users store in the `initComponent` method
store: 'Users',
...
});
现在,我们定义的用户控制器已经能顺利的加载数据了,到目前,我们所定义的Store已经足够使用了,但是Extjs4提供了一个强大的Ext.data.Model类,不如,我们利用它来重构下我们Store,创建app/model/User.js
Ext.define('AM.model.User', { extend: 'Ext.data.Model', fields: ['name', 'email'] }); |
Ext.define('AM.model.User', {
extend: 'Ext.data.Model',
fields: ['name','email']
});
创建好模型之后,我们将他引入用户控制:
//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'} ] }); |
//the Users controller will make sure that the User model isincluded 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'}
]
});
完成上面的代码后,刷新页面,看到的结果和以前的一样。
3.2.7 通过model保存数据
现在双击Grid中的行,会弹出编辑用户的window,实现Save来保存用户数据,我们需要修改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'); } }); |
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('clickedthe Save button');
}
});
在this.control中,我们增加了一个选择项,'usereditbutton[action=save]',当ComponentQuery找到符合的组件(button按钮,并且action动作为save),给他增加一个方法click,事件为updateUser。如图:
上图中,可以看到正确的Click事件,在updateUser函数中,需要一个正式的逻辑,来完成用户数据的更新。
updateUser: function(button) { var win = button.up('window'), form = win.down('form'), record = form.getRecord(), values = form.getValues(); record.set(values); win.close(); } |
updateUser: function(button) {
var win =button.up('window'),
form = win.down('form'),
record =form.getRecord(),
values=form.getValues();
record.set(values);
win.close();
}
这里,当我们点击Save按钮,我们将按钮本身传入函数updateUser,这时,我们使用button.up('window')来获取用户window的引用,然后使用win.down('form')来获取表单的引用,然后获取表单的记录,获取表单中的值,再设置记录为新的值,最后关闭window。
修改数据后点击Save,更新完成。
实际应用过程中,我们需要将数据保存到服务器,所以,需要修改文件,达到想要的目的。
3.2.8 将数据保存到服务器
这很容易做到。使用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' } } }); |
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'
}
}
});
在AM.store.Users,移除data,用一个代理(proxy)取代它,用代理的方式来加载和保存数据。在Extjs4中,代理的方式有AJAX,JSON-P 和 HTML5 localStorage,这里使用AJAX代理。数据从data/users.json中得到。
我们还用reader来解析数据,还指定了successProperty配置。具体请查看Json Reader,把以前的数据复制到users.json中。得到如下形式:
{ success: true, users: [ {id: 1, name: 'Ed', email: 'ed@sencha.com'}, {id: 2, name: 'Tommy', email: 'tommy@sencha.com'} ] } |
{ success: true, users: [
{id: 1, name:'Ed', email: 'ed@sencha.com'},
{id: 2, name:'Tommy', email: 'tommy@sencha.com'}
]}
到这里,唯一的变化是将Stroe的autoLoad设置为了true,这要求Stroe的代理要自动加载数据,当刷新页面,将得到和之前一样的效果。
最后一件事情,是将修改的数据发送到服务器,对于本例,服务端只用了一个静态的JSON,所以我们不会看到有任何变化,但至少可以确定这样做是可行的,相信服务端处理数据能力,大家都应该有。本例用,做个小小的变化,即新的代理中,使用api来更新一个新的URL。
proxy: { type: 'ajax', api: { read: 'data/users.json', update: 'data/updateUsers.json' }, reader: { type: 'json', root: 'users', successProperty: 'success' } } |
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中,创建一个虚拟的回应,从而让我们知道事件确实已经发生。updateUsers.json只包含了{"success": true}。而我们唯一要做的事情是服务的同步编辑。在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: 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();
}
现在,运行这个完整的例子,当双击grid中的某一样并进行编辑,点击Save按钮后,得到正确的请求和回应。 至此,整个MVC模式全部完成,下一节,将讨论控制器(Controller)和模式(patterns),而这些,可以使应用代码更少,更容易维护。