Concepts
类型系统
命名规范
类
类名只有字母数字,大写开头。命名空间可以有多层,最外层命名空间开头大写。只有Sencha官方发布的可以用Ext
作为最外层命名空间。
驼峰风格,不能有连着两个字符大写。
源代码
「源代码文件与目录」严格对应「类名与命名空间」。
方法与变量
同类,但以小写开头。
属性
同上。但要注意静态属性以大写开头:
Ext.MessageBox.YES = "Yes" Ext.MessageBox.NO = "No" MyCompany.alien.Math.PI = "4.13"
实践
声明
使用Ext.define
定义类:
Ext.define(className, members, onClassCreated);
- className
- 类名。
- members
- 一组类成员的键值对。
- onClassCreated
- (可选)回调函数。
例子:
Ext.define( 'My.sample.Person', { name: 'Unknown', constructor: function(name) { if (name) { this.name = name; } }, eat: function(foodType) { alert(this.name + " is eating: " + foodType); } }); var aaron = Ext.create('My.sample.Person', 'Aaron'); aaron.eat("Salad"); // alert("Aaron is eating: Salad");
上面用Ext.create
方法创建实例,而不推荐用new My.sample.Person()
。Ext.create
是动态加载的实例。第一个参数是类名,后面的参数给类的构造函数。
配置
config
成员通过Ext.Class
的预处理器在类被创建前初始化:
-
config
成员和类的其他成员放在一起。 -
config
成员会在类创建期间自动生成没有被显式定义的getter/setter与相关方法。 -
对每个
config
成员自动生成apply
方法。在自动生成的setter
方法设值前加入处理逻辑。
例子:
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); }, 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
静态成员
定义静态成员:
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 ++; } }); 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"
异常处理与调试
抛异常时使用Ext.getDisplayName()
取得任何方法的显示名。
throw new Error('['+ Ext.getDisplayName(arguments.callee) +'] Some message here');
任何由Ext.define()
定义的类型抛出异常可以看到类名与方法名。
MVC架构
- model
- 模型,如用户有
username
和password
。 - view
- 可以是任何组件:树形菜单、面板等。
- controller
- 做程序运作起来的代码。
文件结构
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
会自动成为全局变量, 注册命名空间到Ext.Loader
对应appFolder
指定的路径app
。launch
指定的回调方法创建了一个Viewport
实例。里面有一个占满整个屏幕的Panel
。
定义控制器
控制器监听事件并调用对应的动作。
Ext.define('AM.controller.Users', { extend : 'Ext.app.Controller', init : function() { console.log('Initialized Users! This happens ' + 'before the Application launch function is called'); } });
把上面的控制器加到application
中去:
Ext.application({ ... controllers: [ 'Users' ], ... });
这样页面加载时控制器被调用,在浏览器控制台输出:
Initialized Users! This happens before the Application launch function is called
控制器还有一个control
方法可以监听视图上的控件。下面的例子中我们在初始化方法里指定监听页面上viewport
里的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'); } });
控制台输出:
The panel was rendered
注意指定监听对象的格式和css选择器很像。'viewport > panel'
表示「所有直接属于viewport
的panel
」。详细规则查看文档目录下的:
docs/index.html#!/api/Ext.ComponentQuery
定义视图
一个查询结果的列表视图:
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); } })
上面的视图类型是AM.view.user.List
,别名是widget.userlist
:
在控制器里根据类型AM.view.user.List
加上视图:
Ext.define('AM.controller.Users', { extend: 'Ext.app.Controller', views: [ 'user.List' ], init: ... onPanelRendered: ... });
把原来application
里Viewport
里的panel
换成我们的视图userList
(根据别名):
Ext.application({ ... launch : function() { Ext.create('Ext.container.Viewport', { layout: 'fit', items: { xtype: 'userlist' } }); } });
控制grid
注意onPanelRander
还是被会调用,因为grid类还是panel
的子类可以匹配viewport > panel
选择器。
再给表格里的行添加双击事件,可以弹出对应记录编辑框:
Ext.define('AM.controller.Users', { ... editUser : function(grid, record) { console.log('Double clicked on ' + record.get('name')); }, init : function() { ... this.control({ 'userlist' : { itemdblclick : this.editUser } }); } });
现在双击后能在控制抬输出信息。接下来要给编辑框做个视图:
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); } });
别忘记有了新视图要加到controller里,对应的事件里再弹出编辑框:
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); } });
创建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' } ] });
修改控制器添加stores
模块把读取记录的源代码包含进来:
Ext.define('AM.controller.Users', { extend: 'Ext.app.Controller', stores: [ 'Users' ], ... });
去掉视图里的硬编码记录:
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 = [ ... });
到目前为止我们已经把数据集合和程序分开了。但是还有可以改进的地方,因为每第记录里有哪些字段是定义在结果集里的。我们再把每个记录的结构抽象出来作为model:
Ext.define('AM.model.User', { extend : 'Ext.data.Model', fields : [ 'name', 'email' ] });
把原来store里声明列名的地方用model代替:
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'} ] });
Controller里指定model:
Ext.define('AM.controller.Users', { extend: 'Ext.app.Controller', stores: ['Users'], models: ['User'], ... });
通过Model保存对象
controller里给保存按钮加上事件:
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'); } ... });
从服务器读取记录
把硬编码在Store里的记录改成通过proxy
从服务器上读取,注意加上autoLoad
让它自动读取:
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' } } });
服务器端的程序就不写了,用个静态文件代替响应:
{ "success": true, "users": [ {"id": 1, "name": 'Ed', "email": "ed@sencha.com"}, {"id": 2, "name": 'Tommy', "email": "tommy@sencha.com"} ] }
提交更新到服务器
提交函数中调用Store
的Sync()
方法:
updateUser: function(button) { var win = button.up('window'), form = win.down('form'), record = form.getRecord(), values = form.getValues(); record.set(values); win.close(); // synchronize the store after editing the record this.getUsersStore().sync(); }
更新Store中的proxy
,为读写方法指定不同的接口:
proxy : { type : 'ajax', api : { read : 'data/users.json', update : 'data/updateUsers.json' }, reader : { type : 'json', root : 'users', successProperty : 'success' } }
布局与容器
容器
更多关于组件的信息:
docs/index.html#/guide/components
容器中可以放置组件或其他容器。如我们在页面上加一个Panel
,Panel
里再加两个子Panel
:
Ext.create('Ext.panel.Panel', { renderTo: Ext.getBody(), width: 400, height: 300, title: 'Container Panel', items: [ { xtype: 'panel', title: 'Child Panel 1', height: 100, width: '75%' }, { xtype: 'panel', title: 'Child Panel 2', height: 100, width: '75%' } ] });
布局
容器都有布局属性来描述它内部的组件。
使用布局
在上面的例子中布局是上下放的,就和HTML的DOM行为一样。现在用colum
指定左右放:
Ext.create('Ext.panel.Panel', { renderTo: Ext.getBody(), width: 400, height: 200, title: 'Container Panel', layout: 'column', items: [ { xtype: 'panel', title: 'Child Panel 1', height: 100, columnWidth: 0.5 }, { xtype: 'panel', title: 'Child Panel 2', height: 100, columnWidth: 0.5 } ] });
布局是如何工作的
窗口的布局负责初始化所有内部组件的大小和初始位置。在内部框架调用窗口的doLayout
方法,而doLayout
方法是层层包含的,所以所有内部成员的doLayout
方法都会被调用。
窗口大小的调整与内部组件的添加删除都会触发重新布局。虽然一般布局的调用都由框架来处理,但有些情况下我们希望等我们做好一些工作以后手动触发。这种情况下我们打开suspendLayout
标记来防止自动布局。等处理完成以后再关闭suspendLayout
来调用布局:
var containerPanel = Ext.create('Ext.panel.Panel', { renderTo: Ext.getBody(), width: 400, height: 200, title: 'Container Panel', layout: 'column', suspendLayout: true // Suspend automatic layouts while we do several different things that could trigger a layout on their own }); // Add a couple of child items. We could add these both at the same time by passing an array to add(), // but lets pretend we needed to add them separately for some reason. containerPanel.add({ xtype: 'panel', title: 'Child Panel 1', height: 100, columnWidth: 0.5 }); containerPanel.add({ xtype: 'panel', title: 'Child Panel 2', height: 100, columnWidth: 0.5 }); // Turn the suspendLayout flag off. containerPanel.suspendLayout = false; // Trigger a layout. containerPanel.doLayout();
组件
所有的组件都继承自Ext.Component
。
组件组合结构
Container
是一种特殊的组件,负责管理内部组件的生命周期与渲染工作。它包含的成员一般用下图来表示,顶部是Viewport
容器。
Ext.create()
创建组件实例,items
指定窗口内部成员:
var childPanel1 = Ext.create('Ext.panel.Panel', { title: 'Child Panel 1', html: 'A Panel' }); var childPanel2 = Ext.create('Ext.panel.Panel', { title: 'Child Panel 2', html: 'Another Panel' }); Ext.create('Ext.container.Viewport', { items: [ childPanel1, childPanel2 ] });