我们先看一下效果图
视图方面有一个container,包含了4个组件,一个grid(Editable Grid),一个form(Form),一个view(DataView),一个panel(DataPanel)
四个组件之间通过一个controller来根据各个组件的事件进行数据的同步显示。
数据存在于store,store又用到一个model。
app/view/MainView.js ----- container
app/view/PersonDataView.js ------- DataView
app/view/PersonGridView.js --------- Editable Grid
app/view/PersonFormView.js -------- Form
app/view/PersonDataPanel.js -------- Data Panel
app/controller/MainController.js ------- controller
app/store/PersonStore.js --------- Store
app/model/PersonModel.js ---------- Model
app/Application.js -------- Application
app.js --------------- main entry
1. app.js
/* * This file is generated and updated by Sencha Cmd. You can edit this file as * needed for your application, but these edits will have to be merged by * Sencha Cmd when upgrading. */ Ext.scopeCss = true; Ext.setGlyphFontFamily('FontAwesome'); Ext.application({ name: 'hello', extend: 'hello.Application' // autoCreateViewport: 'hello.view.MainView' //------------------------------------------------------------------------- // Most customizations should be made to hello.Application. If you need to // customize this file, doing so below this section reduces the likelihood // of merge conflicts when upgrading to new versions of Sencha Cmd. //------------------------------------------------------------------------- });
2. Application.js
/** * The main application class. An instance of this class is created by app.js when it calls * Ext.application(). This is the ideal place to handle application launch and initialization * details. */ Ext.define('hello.Application', { extend: 'Ext.app.Application', name: 'hello', controllers:[ 'MainController' ], launch: function () { // TODO - Launch the application var me = this, ct = Ext.getBody(); Ext.widget({ xtype: 'mainview', renderTo:ct, height:480, width:720, frame:true, title:'Complex Data Binding Example by Saki', glyph:0xf0eb }); } });
launch函数创建了一个widget, xtype为mainview. 该mainview对应MainView.js的alias.
3. MainView.js
Ext.define('hello.view.MainView', { extend: 'Ext.panel.Panel', alias: 'widget.mainview', initComponent: function () { var me = this, cfg = {}; Ext.apply(cfg, { layout: { type: 'hbox', align: 'stretch' }, defaults: { flex: 1 }, items: [{ xtype: 'container', layout: { type: 'vbox', align: 'stretch' }, defaults: { flex: 1, margin: 5 }, items: [{ title: 'Editable Grid', xtype: 'persongridview', glyph: 0xf0ce }, { title: 'DataView', glyph: 0xf009, layout: 'fit', items:[{ xtype: 'persondataview' }] }] },{ xtype: 'container', layout: { type: 'vbox', align: 'stretch' }, defaults: { flex: 1, margin: 5 }, items: [{ title: 'Form', xtype: 'personformview', glyph: 0xf044, frame: true }, { title: 'Data Panel', xtype: 'personpanelview', glyph: 0xf0f6 }] }] }); Ext.apply(me, cfg); me.callParent(arguments); } });
MainView总共包含了4个视图组件,每个组件通过xtype来指定,分别为4个组件的alias.
感觉每个组件的别名必须以widget.来打头,不然的话会有问题,可能这是Extjs的一种机制吧。
4. MainController.js
Ext.define('hello.controller.MainController', { extend:'Ext.app.Controller', views:[ 'MainView', 'PersonGridView', 'PersonFormView', 'PersonPanelView', 'PersonDataView' ], stores:[ 'PersonStore' ], refs:[{ ref:'data', selector:'persondataview' },{ ref:'form', selector:'personformview' },{ ref:'panel', selector:'personpanelview' },{ ref:'grid', selector:'persongridview' }], init:function() { var me = this; me.listen({ component: { persongridview: { rowselectionchange: 'onRowSelectionChange', edit: 'onGridEdit' }, persondataview: { itemselectionchange: 'onItemSelectionChange' }, 'personformview button': { click: 'onFormButtonClick' }, 'personformview field': { change: 'onFormFieldChange' } } }); }, onGridEdit:function(editor,e) { var me = this; me.getForm().loadRecord(e.record); me.getPanel().loadRecord(e.record); }, onFormFieldChange:function(field) { var me =this, form = me.getForm(), record = form.getRecord() || false; if(record){ form.updateRecord(); me.getPanel().loadRecord(record); } form.updateUi(); }, onFormButtonClick:function(btn){ this.getForm()[btn.itemId +'Record'](); }, onRowSelectionChange:function(grid, selected) { alert("onRowSelectionChange"); var me = this, record = selected[0];// || false; var sm = me.getData().getSelectionModel(); me.getForm().loadRecord(record); alert("getForm ok"); me.getPanel().loadRecord(record); if(record) { sm.select(record);//[record]); } else { sm.deselectAll(); } }, onItemSelectionChange:function(view, selected) { var me = this, record = selected[0] || false; me.getForm().loadRecord(record); me.getPanel().loadRecord(record); me.getGrid().getSelectionModel().select(selected); } });
MainController里面定义了views,stores,到底有什么用呢?
看一下官方注释就明白了。
MainController里面定义了ref,到底有什么用呢?
通过上面的ref定义,在MainController中就可以使用getData(),getForm(),getGrid(),getPanel()函数,selector就对应的每个视图的alias.
5. PersonStore.js
Ext.define('hello.store.PersonStore', { extend: 'Ext.data.Store', model: 'hello.model.PersonModel', autoLoad:true, data: [ { id:1,"fname": "Lisa", "lname": "lisa@simpsons.com", "age": "10" }, { id:2,"fname": "Bart", "lname": "bart@simpsons.com", "age": "12" }, { id:3,"fname": "Homer", "lname": "homer@simpsons.com", "age": "13" }, { id:4,"fname": "Tangke", "lname": "marge@simpsons.com", "age": "15" }, ] });
6. PersonModel.js
Ext.define('hello.model.PersonModel', { extend: 'Ext.data.Model', idProperty:'id', // alias:'User', fields: [ { name: 'id', type: 'int' }, { name: 'fname', type: 'string' }, { name: 'lname', type: 'string' }, { name: 'age', type: 'int' } ] });
7. PersonGridView.js
Ext.define('hello.view.PersonGridView', { extend:'Ext.grid.Panel', alias:'widget.persongridview', uses:[ 'Ext.grid.plugin.CellEditing' ], initComponent:function() { var me = this, cfg={}; Ext.apply(cfg, { store:Ext.getStore('PersonStore'), columns:[{ text:'First Name', dataIndex:'fname', editor:{ xtype:'textfield' } },{ text:'Last Name', flex:1, dataIndex:'lname', editor:{ xtype:'textfield' } },{ text:'Age', dataIndex:'age', editor:{ xtype:'numberfield' } }], listeners:{ selectionchange:'onSelectionChange' }, setModel:{ allowDeselect:true }, plugins:[{ ptype:'cellediting', clicksToEdit:2, pluginId:'cellediting' }], tbar:{ xtype:'toolbar', items:['->',{ text:'Add Record', itemId:'addRecord', glyph:0xf067, handler:me.onAddRecord, scope:me }] } }); Ext.apply(me,cfg); me.callParent(arguments); }, onAddRecord:function() { var me = this, store = me.getStore(), record = store.add({})[0]; me.getPlugin('cellediting').startEdit(record, 0); }, onSelectionChange:function(selModel, selected, eOpts) { this.fireEvent('rowselectionchange', this, selected, eOpts); } });
store:Ext.getStore('PersonStore')用于获取数据。
有些地方用的代码是Ext.data.StoreManager.lookup('PersonStore'),效果是一样的。为什么呢?具体看官方注释:
8. PersonFormView.js
Ext.define('hello.view.PersonFormView', { extend:'Ext.form.Panel', alias:'widget.personformview', uses:[ 'Ext.form.field.Number' ], frame:true, initComponent:function() { var me = this, cfg={}; Ext.apply(cfg, { defaultType:'textfield', defaults: { anchor: '100%' }, bodyPadding:10, items:[{ fieldLabel:'First Name', name:'fname' },{ fieldLabel:'Last Name', name:'lname' },{ fieldLabel:'Age', name:'age', xtype:'numberfield' }], buttons:[{ text:'Rejext', itemId:'reject', disabled:true, glyph:0xf0e2 },{ text:'Commit', itemId:'commit', glyph:0xf00c, disabled:true }] }); Ext.apply(me,cfg); me.callParent(arguments); }, loadRecord:function(record) { var me = this; if(record) { me.callParent([record]); } else { me.clearValues(); } }, clearValues:function() { var me = this; me.getForm()._record = null; me.getForm().setValues({ fname:'', lname:'', age:undefined }); me.uploadUi(); }, commitRecord:function() { var me = this, record = me.getRecord(); if(record) { me.updateRecord(); record.commit(); me.updateUi(); } }, rejectRecord:function() { var me = this, record = me.getRecord(); if(record) { record.rejectRecord(); me.loadRecord(record); me.updateUi(); } }, updateUi:function() { var me =this, record =me.getRecord(), disabled=record && record.dirty?false:true; Ext.each(me.query('button'), function(btn) { btn.setDisabled(disabled); }) } });
9. PersonDataView.js
Ext.define('hello.view.PersonDataView', { extend:'Ext.view.View', alias:'widget.persondataview', autoscroll:true, frame:true, initComponent:function() { var me = this, cfg={}; Ext.apply(cfg, { store:Ext.getStore('PersonStore'), itemSelector:'div.person-item', tpl:[ '<tpl for=".">', '<div class="person-item">', '<strong>{fname}.{lname}</strong>{{age}}', '</div>', '</tpl>' ], listeners: { selectionchange: 'onSelectionChange' }, selModel:{ allowDeselect:true } }); Ext.apply(me,cfg); me.callParent(arguments); }, onSelectionChange:function(selModel, selected, eOpts) { this.fireEvent('itemselectionchange', this, selected,eOpts); } });
10. PersonPanelView.js
Ext.define('hello.view.PersonPanelView', { extend:'Ext.panel.Panel', alias:'widget.personpanelview', cls:'person-panel', data:{}, bodyPadding:0, frame:true, tpl:[ '<table>', '<tr><td>First Name:</td><td><strong>{fname}</strong></td></tr>', '<tr><td>Last Name:</td><td><strong>{lname}</strong></td></tr>', '</table>' ], loadRecord:function(record) { alert("loadRecord"); var me = this; if(record) { me.update(record.getData()); } else { me.update({}); } } });
11. 疑问?
11.1 xtype和alias的对应关系
看了一个Extjs内部的一些组件,确实也是这样。
Ext.define('Ext.form.field.Text', { extend: 'Ext.form.field.Base', alias: 'widget.textfield', requires: [ 'Ext.form.field.VTypes', 'Ext.form.trigger.Trigger', 'Ext.util.TextMetrics' ], alternateClassName: [ 'Ext.form.TextField', 'Ext.form.Text' ], config: { hideTrigger: false, triggers: undefined },
11.2 loadRecord的用法
在PersonFormView中有一个loadRecord的函数,其重写了父类的loadRecord函数。
在Ext.form.Panel中,
在PersonPanelView中有也有一个loadRecord函数,但是其父类Ext.panel.Panel并没有loadRecord函数。
两者传入的参数都是record参数,record是一个model模型。
{fname},{lname} 分别对应了record model中的fields.
11.3 getStore
onAddRecord:function() { var me = this, store = me.getStore(), record = store.add({some:'id'})[0]; me.getPlugin('cellediting').startEdit(record, 0); },
这里还不是太明白,如果添加一个默认数据。
11.4 store:
在PersonDataView和PersonGridView里面分别使用了store配置。其分别派生自Ext.view.View和Ext.grid.Panel.
Ext.grid.Panel的官方注释:
store : Ext.data.StoreREQUIRED
The Store the grid should use as its data source.
Ext.view.View的官方注释:
store : Ext.data.StoreREQUIRED
The Ext.data.Store to bind this DataView to.
Available since: 2.3.0