ExtJS Complex data binding
我们先看一下效果图
视图方面有一个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.
//-------------------------------------------------------------------------
});
/* * 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
});
}
});
/** * 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);
}
});
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);
}
});
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": "[email protected]", "age": "10" },
{ id:2,"fname": "Bart", "lname": "[email protected]", "age": "12" },
{ id:3,"fname": "Homer", "lname": "[email protected]", "age": "13" },
{ id:4,"fname": "Tangke", "lname": "[email protected]", "age": "15" },
]
});
Ext.define(‘hello.store.PersonStore‘, { extend: ‘Ext.data.Store‘, model: ‘hello.model.PersonModel‘, autoLoad:true, data: [ { id:1,"fname": "Lisa", "lname": "[email protected]", "age": "10" }, { id:2,"fname": "Bart", "lname": "[email protected]", "age": "12" }, { id:3,"fname": "Homer", "lname": "[email protected]", "age": "13" }, { id:4,"fname": "Tangke", "lname": "[email protected]", "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‘ }
]
});
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);
}
});
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);
})
}
});
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);
}
});
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({});
}
}
});
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
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。