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.
    //-------------------------------------------------------------------------
});

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": "[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‘ }
    ]


});

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

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。