【问题标题】:ExtJS - Complex form serializationExtJS - 复杂的表单序列化
【发布时间】:2013-07-09 20:37:23
【问题描述】:

我有一个复杂的表单,我不能使用表单序列化技术。表单内有许多字段以及动态网格(用户选择某些条件时动态生成的网格)。

我想要做的是,收集用户输入/选择 + 添加网格中可用的选定记录,然后最终用这些数据制作一个 JSON 数组,以便能够发布服务器端。

我猜,我可以使用 ExtJS 的 getCmp 函数来获取整个数据,但您可能会猜到这种方式有点难以维护。另外,我不知道用这种方式获取网格数据!

PS:代码有点长,所以我裁剪了一些部分。

型号和商店定义

Ext.Loader.setConfig({enabled: true});
Ext.Loader.setPath('Ext.ux', '<?php echo js_url(); ?>resources/ux');

Ext.require([
  'Ext.grid.*',
  'Ext.data.*',
  'Ext.form.*',
  'Ext.state.*',
  'Ext.util.*',
  'Ext.layout.container.Column',
  'Ext.selection.CheckboxModel',
  'Ext.ux.RowExpander',
  'Ext.ux.statusbar.StatusBar'  
]);

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());
};

// Article Model
Ext.define('Article', {
    extend: 'Ext.data.Model',
    fields: [
        {name: 'ARTICLE_ID', type: 'int'},
        {name: 'DESCRIPTION', type: 'string'}
    ]
});

// Article Json Store
var articles = new Ext.data.JsonStore({
    model: 'Article',
    proxy: {
        type: 'ajax',
        url: '<?php echo base_url() ?>create/get_articles',
        reader: {
            type: 'json',
            root: 'artList',
            idProperty: 'ARTICLE_ID'
        }
    }
});

winArticle = new Ext.Window({
width: 600,
modal: true,
title: 'Artikel Seçimi',
closeAction: 'hide',
bodyPadding: 10,
items: new Ext.Panel({
    items: [
        {
            xtype: 'fieldset',
            title: 'Artikel Sorgulama',
            defaultType: 'textfield',
            layout: 'anchor',
            defaults: {
                anchor: '100%'
            },
            height: '76px',
            items: [
                {
                    xtype: 'fieldcontainer',
                    layout: 'hbox',
                    defaultType: 'textfield',
                    items: [
                        {
                            xtype: 'combobox',
                            id: 'articleNo',
                            inputWidth: 320,
                            fieldLabel: 'ARTİKEL NO',
                            fieldStyle: 'height: 26px',
                            margin: '10 15 0 0',
                            triggerAction: 'query',
                            pageSize: true
                        },
                        {
                            xtype: 'button',
                            text: 'SORGULA',
                            width: 100,
                            scale: 'medium',
                            margin: '8 0 0 0'
                        }
                    ]
                }
            ]
        },
        {
            xtype: 'fieldset',
            title: 'Artikel Bilgileri',
            height: '140px',
            layout: 'fit',
            items: [
                {
                    xtype: 'fieldcontainer',
                    layout: 'hbox',
                    defaultType: 'textfield',
                    fieldDefaults: {
                        labelAlign: 'top'
                    },
                    items: [
                        {
                            fieldLabel: 'ARTİKEL TANIMI',
                            name: 'artDesc',
                            flex: 3,
                            margins: '0 5 0 0'
                        },
                        {
                            fieldLabel: 'PAKET İÇERİĞİ',
                            name: 'artgebi',
                            flex: 1
                        }
                    ]
                },
                {
                    xtype: 'fieldcontainer',
                    layout: 'hbox',
                    defaultType: 'textfield',
                    id: 'artContainer1',
                    fieldDefaults: {
                        labelAlign: 'top'
                    },
                    items: [
                        {
                            fieldLabel: 'SUBSYS',
                            name: 'artSubsys',
                            flex: 1,
                            margins: '0 5 0 0'
                        },
                        {
                            fieldLabel: 'VARIANT',
                            name: 'artVariant',
                            flex: 1,
                            margins: '0 5 0 0'
                        },
                        {
                            fieldLabel: 'VARIANT TANIMI',
                            name: 'artVariantDesc',
                            flex: 2
                        }
                    ]
                }
            ]
        },
        {
            xtype: 'fieldset',
            title: 'Aksiyon Seviyeleri',
            id: 'article-fieldset',
            items: [
                {
                    xtype: 'button',
                    id: 'btnArticleLevelAdd',
                    text: 'LEVEL EKLE',
                    scale: 'medium',
                    width: 100,
                    style: 'float: right',
                    margin: '0 7 0 0',
                    handler: function() {

                        var getLevels = function() {
                            var count = winArticle.down('fieldset[id=article-fieldset]').items.items.length;
                            return count;
                        }

                        var count = getLevels();

                        if (count === 3) {
                            Ext.getCmp('btnArticleLevelAdd').disable();
                        }

                        var container = 'artContainer' + count;
                        //console.log(container);

                        Ext.getCmp('article-fieldset').add([
                            {
                                xtype: 'fieldcontainer',
                                layout: 'hbox',
                                id: 'artContainer' + count,
                                defaultType: 'textfield',
                                fieldDefaults: {
                                    labelAlign: 'top'
                                },
                                items: [
                                    {

                                        name: 'artLevel' + count,
                                        allowBlank: false,
                                        inputWidth: 216,
                                        fieldStyle: 'text-align: right; font-size: 13pt; background-color: #EAFFCC;',
                                        margins: '0 5 5 0'
                                    },
                                    {

                                        name: 'artValue' + count,
                                        allowBlank: false,
                                        inputWidth: 216,
                                        fieldStyle: 'text-align: right; font-size: 13pt; background-color: #EAFFCC;',
                                        margins: '0 5 0 0'
                                    },
                                    {
                                        xtype: 'button',
                                        text: 'SİL',
                                        width: 40,
                                        cls: 'btn-article-remove',
                                        handler: function() {
                                            if(count <= 3) {
                                                Ext.getCmp('btnArticleLevelAdd').enable();
                                            } else {
                                                Ext.getCmp('btnArticleLevelAdd').disable();
                                            }
                                            winArticle.down('fieldset[id=article-fieldset]').remove(container);
                                        }
                                    }
                                ]
                            }
                        ]);
                    }
                },
                {
                    xtype: 'fieldcontainer',
                    layout: 'hbox',
                    id: 'article-level-container',
                    defaultType: 'textfield',
                    fieldDefaults: {
                        labelAlign: 'top'
                    },
                    items: [
                        {
                            fieldLabel: 'LEVEL',
                            name: 'artLevel',
                            inputWidth: 216,
                            margins: '0 5 5 0',
                            allowBlank: false,
                            fieldStyle: 'text-align: right; font-size: 13pt; background-color: #EAFFCC;'
                        },
                        {
                            fieldLabel: 'VALUE',
                            name: 'artValue',
                            inputWidth: 216,
                            allowBlank: false,
                            blankText: 'zorunlu alan, boş bırakılamaz',
                            fieldStyle: 'text-align: right; font-size: 13pt; background-color: #EAFFCC;',
                            listeners: {
                                change: function(textfield, newValue, oldValue) {
                                    if(oldValue == 'undefined' || newValue == '') {
                                        Ext.getCmp('btnArticleSave').disable();
                                    } else {
                                        Ext.getCmp('btnArticleSave').enable();
                                    }
                                }
                            }
                        }
                    ]
                }
            ]
        }
    ]
}),
buttons: [
    {
        text: 'KAPAT',
        scale: 'medium',
        width: 100,
        cls: 'btn-article-close',
        listeners: {
            click: function() {
                winArticle.close();
            }
        }
    },
    '->',
    {
        text: 'EKLE',
        scale: 'medium',
        disabled: true,
        width: 100,
        margin: '0 9 0 0',
        cls: 'btn-article-save',
        id: 'btnArticleSave'
    }
]
});

EXT.ONREADY 功能

Ext.onReady(function () {

Ext.QuickTips.init();

Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
    expires: new Date(new Date().getTime() + (1000 * 60 * 60 * 24 * 7))
}));

var Discounts = Ext.create('Ext.form.Panel', {
    id: 'discount-types',
    bodyPadding: 10,
    width: 760,
    height: 600,
    title: 'DNR TANIMLAMA / SCREEN 0',
    layout: 'card',
    bodyStyle: 'padding:20px',
    defaults: {
        border: false,
        anchor: '100%'
    },
    style: {
        'box-shadow': '0 2px 5px rgba(0, 0, 0, 0.6)',
        '-webkit-box-shadow': '0 0 8px rgba(0, 0, 0, 0.5)'
    },
    frame: true,
    buttons: [
        {
            text: 'ÖNCEKİ ADIM',
            id: 'move-prev',
            cls: 'np-button',
            scale: 'medium',
            iconCls: 'dnr-prev-icon',
            iconAlign: 'left',
            handler: function (btn) {
                navigate(btn.up('panel'), 'prev');
                var itemd = Discounts.getLayout().getActiveItem();
                Discounts.setTitle('DNR TANIMLAMA ' + ' / ' + itemd.cardTitle);
                Ext.getCmp('dnr-submit').disable();
                Ext.getCmp('dnr-submit').setVisible(false);
            },
            disabled: true
        },
        {
            text: 'SONRAKİ ADIM',
            id: 'move-next',
            scale: 'medium',
            cls: 'np-button',
            iconCls: 'dnr-next-icon',
            iconAlign: 'right',
            handler: function (btn) {
                navigate(btn.up('panel'), 'next');
                var itemd = Discounts.getLayout().getActiveItem();
                Discounts.setTitle('DNR TANIMLAMA ' + ' / ' + itemd.cardTitle);
                var cardNum = Discounts.items.indexOf(itemd);

                if (cardNum == 3) {
                    Ext.getCmp('dnr-submit').enable();
                    Ext.getCmp('dnr-submit').setVisible(true);
                }
            },
            disabled: true
        },
        '->',
        {
            text: '&nbsp KAYDET ',
            id: 'dnr-submit',
            scale: 'medium',
            iconCls: 'dnr-submit-icon',
            iconAlign: 'right',
            cls: 'dnr-submit',
            disabled: true,
            hidden: true,
            handler: function (btn) {

            }
        }
    ],
    items: [
        {
            id: 'screen-0',
            cardTitle: 'SCREEN 0',
            layout: 'form',
            items: [
                {
                    layout: {
                        type: 'vbox',
                        align: 'center'
                    },
                    margin: '60 0 0 0',
                    items: [
                        {
                            xtype: 'combobox',
                            inputWidth: 295,
                            fieldLabel: 'DNR TİPİ',
                            fieldStyle: 'height: 26px',
                            id: 'discount-type',
                            store: discounts,
                            valueField: 'DNR_TYPE_ID',
                            displayField: 'DNR_TYPE_DESC',
                            queryMode: 'remote',
                            forceSelection: true,
                            stateful: true,
                            stateId: 'cmb_disc_type',
                            allowBlank: false,
                            emptyText: 'DNR tipini seçiniz...',
                            triggerAction: 'all',
                            listeners: {
                                select: function (e) {
                                    var discType = Ext.getCmp('discount-type').getValue();
                                    var discDetail = Ext.getCmp('discount-detail');

                                    discdetails.removeAll();

                                    if (discType != 0) {
                                        discDetail.setDisabled(false);
                                        discdetails.proxy.extraParams = { 'dnrtype': discType };
                                        discdetails.load();
                                    }
                                }
                            }
                        },
                        {
                            xtype: 'combobox',
                            inputWidth: 400,
                            fieldStyle: 'height: 26px',
                            id: 'discount-detail',
                            valueField: 'ID',
                            displayField: 'DNR_TITLE',
                            store: discdetails,
                            forceSelection: true,
                            stateful: true,
                            stateId: 'cmb_disc_detail',
                            margin: '25 0 0 0',
                            disabled: true,
                            allowBlank: false,
                            msgTarget: 'side',
                            emptyText: 'İNDİRİM TİPİNİ SEÇİNİZ...',
                            blankText: 'İndirim tipi boş olamaz!',
                            triggerAction: 'all',
                            listeners: {
                                select: function (e) {
                                    var discDetail = Ext.getCmp('discount-detail').getValue();

                                    if (discDetail != 'null') {
                                        var value = discdetails.getAt(discdetails.find('ID', discDetail)).get('DNR_DESCRIPTION');
                                        Ext.getCmp('dnr-type-desc-panel').setVisible(true);
                                        Ext.getCmp('dnr-type-desc-panel').update(value);
                                    }
                                }
                            }
                        },
                        {
                            xtype: 'textarea',
                            grow: false,
                            name: 'invoiceText',
                            fieldLabel: 'FATURA METNİ',
                            id: 'invoice-text',
                            blankText: 'Fatura metni boş olamaz!',
                            width: 400,
                            height: 60,
                            margin: '30 0 0 0',
                            allowBlank: false,
                            msgTarget: 'side',
                            listeners: {
                                change: function (e) {
                                    if (Ext.getCmp('invoice-text').getValue().length === 0) {
                                        Ext.getCmp('move-next').disable();
                                    } else {
                                        Ext.getCmp('move-next').enable();
                                    }
                                }
                            }
                        },
                        {
                            xtype: 'panel',
                            id: 'dnr-type-desc-panel',
                            layout: {type: 'hbox', align: 'stretch'},
                            height: 145,
                            width: 400,
                            cls: 'dnr-desc-panel',
                            margin: '60 0 0 0',
                            html: '&nbsp',
                            hidden: true
                        }
                    ]
                }
            ]
        },
        {
            id: 'screen-1',
            cardTitle: 'SCREEN 1',
            layout: 'form',
            items: [
                {
                    layout: 'column',
                    width: 730,
                    height: 90,
                    items: [
                        {
                            xtype: 'fieldset',
                            title: 'ARTİKEL / HEDEF GRUP / MAL GRUBU SEÇİMİ',
                            cls: 'dnr-fieldset',
                            width: 730,
                            height: 80,
                            margin: '0',
                            items: [
                                {
                                    xtype: 'buttongroup',
                                    columns: 5,
                                    columnWidth: 140,
                                    frame: false,
                                    margin: '5 0 0 18',
                                    items: [
                                        {
                                            text: 'ARTİKEL',
                                            scale: 'medium',
                                            margin: '0 18px 0 0',
                                            width: 120,
                                            height: 36,
                                            id: 'btn-article',
                                            cls: 'btn-grp-choose btn-grp-article',
                                            listeners: {
                                                click: function () {
                                                    winArticle.center();
                                                    winArticle.show();
                                                }
                                            }
                                        },
                                        {
                                            text: 'PUAR',
                                            scale: 'medium',
                                            margin: '0 18px 0 0',
                                            width: 120,
                                            height: 36,
                                            cls: 'btn-grp-choose btn-grp-puar',
                                            listeners: {
                                                click: function() {
                                                    winPuar.show();
                                                }
                                            }
                                        },
                                        {
                                            text: 'MAL GRUBU',
                                            scale: 'medium',
                                            margin: '0 18px 0 0',
                                            width: 120,
                                            height: 36,
                                            cls: 'btn-grp-choose btn-grp-choose',
                                            listeners: {
                                                click: function() {
                                                    winArticleGroup.show();
                                                }
                                            }
                                        },
                                        {
                                            text: 'HEDEF GRUP',
                                            scale: 'medium',
                                            margin: '0 18px 0 0',
                                            width: 120,
                                            height: 36,
                                            cls: 'btn-grp-choose btn-grp-target',
                                            listeners: {
                                                click: function() {
                                                    winTargetGroup.show();
                                                }
                                            }
                                        },
                                        {
                                            text: 'SUPPLIER',
                                            scale: 'medium',
                                            width: 120,
                                            height: 36,
                                            cls: 'btn-grp-choose btn-grp-supplier',
                                            listeners: {
                                                click: function() {
                                                    winSupplier.show();
                                                }
                                            }
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                },
                {
                    xtype: 'gridpanel',
                    id: 'article-grid',
                    selType: 'rowmodel',
                    elStatus: true,
                    plugins: [
                        { ptype: 'cellediting', clicksToEdit: 1},
                        { ptype: 'datadrop'}
                    ],
                    /* ***************************************************************
                     * here is the tricky part! when user change any fields above
                     * this grid will dynamically generate upon user request. So that
                     * we arent sure which columns will be available.
                     * ***************************************************************/
                    columns: [
                        {
                            text: 'COLUMN A',
                            dataIndex: ''
                        }
                    ]
                }
            ]
        },
        renderTo: 'content'
})
});

【问题讨论】:

  • 我的建议是从商店内的记录中读取任何数据,无论它们是什么类型。
  • 亲爱的@sra 我很好奇我们如何处理选定的网格记录?这三天真的没有解决。当用户指定标准时,我可以动态创建网格存储,然后显示网格面板。我做不到最后一部分,即用整个数据制作 JsonStore。应该有简单的方法来做到这一点。我可以在服务器端处理的 JsonStore 中有哪些可用数据并不重要。
  • 我不确定我是否真的明白你的目标是什么......你想要 1. 只从网格中获取选定的记录,2. 只从网格中获取修改过的记录,3. 获取所有记录或 4. 完全不同的东西。你期望什么数据结构?我的意思是你仍然需要能够将数据转换回后端的对象......?
  • 亲爱的@sra,只从网格中获取选定的记录以及所有其他表单字段。从源代码可以看出,共有三个面板。当用户从第一个面板开始时,他/她选择必要的字段。当用户到达最后一个面板时,我想获取他/她选择的所有记录,包括网格。
  • 好的,请查看我的编辑并给我反馈

标签: javascript extjs


【解决方案1】:

更新答案

经过一些澄清后,我认为答案应该很简单(至少我认为是这样)对于以下答案,我假设您在要获取表单和网格数据时在表单内,并且只有一位Ext.form.Panel

// Navigate up to the form:
var form = this.up('form'),
// get the form values
    data = form.getValues(),
// get the selected record from the grid
    gridRecords = form.down('grid').getSelectionModel().getSelected(),
// some helper variables
    len = gridRecords.length,
    recordData = [];

// normalize the model data by copying just the data objects into the array
for(i=0;i<len;i++) {
    recordData .push(gridRecords[i].data);
}
// apply the selected grid records to the formdata. For that you will need a property name, I will use just 'gridRecords' but you may change it
data.gridRecords = recordData;

// send all back via a ajax request
Ext.Ajax.request({
    url: 'demo/sample',
    success: function(response, opts) {
        // your handler
    },
    failure: function(response, opts) {
        // your handler
    },
    jsonData: data
});

应该是这样的

提供更多可以从/通过网格获取的数据选项

// get all data that is currently in the store
form.down('grid').getStore().data.items
// get all new and updated records
form.down('grid').getStore().getModifiedRecords()
// get all new records
form.down('grid').getStore().getNewRecords()
// get all updated records
form.down('grid').getStore().getUpdatedRecords()

下面的旧答案(针对更复杂的场景)

你说的:

你有一个带有表格的网格,也许还有网格。您还需要的地方 从表单中获取数据时读出网格。

在下面的答案中,我将仅介绍 getValues绑定/解除绑定事件到每个网格,而不是

  • 表单加载/提交
  • 记录加载/更新
  • 设定值

我的建议是让您的表单更加智能,以便能够处理此问题。

这是什么意思?

默认表单关心插入到其正文中任何位置的所有字段。在 99,9% 的情况下,这完全没问题,但并非对所有人都适用。您的表单还需要注意插入的网格。

怎么做

第一件事是,当您制作表格的网格部分时,我建议给它们一个名称属性。其次,您需要知道表单如何收集和利用字段,以便能够将其复制到网格中。为此,您需要查看Ext.form.Basicconstructor,其中重要部分是这个

// We use the monitor here as opposed to event bubbling. The problem with bubbling is it doesn't
// let us react to items being added/remove at different places in the hierarchy which may have an
// impact on the dirty/valid state.
me.monitor = new Ext.container.Monitor({
    selector: '[isFormField]',
    scope: me,
    addHandler: me.onFieldAdd,
    removeHandler: me.onFieldRemove
});
me.monitor.bind(owner);

这里发生的是监视器被初始化,从这里开始将查找插入到绑定组件中的任何字段,监视器将在其中调用适当的处理程序。目前监视器正在寻找字段,但您需要一个正在寻找网格的监视器。这样的监视器看起来像:

me.gridMonitor = new Ext.container.Monitor({
    selector: 'grid',
    scope: me,
    addHandler: me.onGridAdd,
    removeHandler: me.onGridRemove
});
me.gridMonitor.bind(owner);

因为我对您的数据结构了解不多,所以我无法告诉您您可能需要哪些网格事件,但您应该在 addHandler/removeHandler 中注册/取消注册它们,例如

onGridAdd: function(grid) {
    var me = this;
    me.mon(grid,'select',me.yourHandler,me);
},
onGridRemove: function(grid) {
    var me = this;
    me.mun(grid,'select',me.yourHandler,me);
}

此外,您还需要以下辅助方法

/**
 * Return all the {@link Ext.grid.Panel} components in the owner container.
 * @return {Ext.util.MixedCollection} Collection of the Grid objects
 */
getGrids: function() {
    return this.gridMonitor.getItems();
},

/**
 * Find a specific {@link Ext.grid.Panel} in this form by id or name.
 * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
 * {@link Ext.grid.Panel name }).
 * @return {Ext.grid.Panel} The first matching grid, or `null` if none was found.
 */
findGrid: function(id) {
    return this.getGrids().findBy(function(f) {
        return f.id === id || f.name === id;
    });
},

最重要的是从网格中获取数据的方法。这里我们需要覆盖

getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
    var values  = {},
        fields  = this.getFields().items,
        grids  = this.getGrids().items, // the grids found by the monitor
        f,
        fLen    = fields.length,
        gLen    = grids.length, // gridcount
        isArray = Ext.isArray,
        grid, gridData, gridStore, // some vars used while reading the grid content
        field, data, val, bucket, name;

    for (f = 0; f < fLen; f++) {
        field = fields[f];

        if (!dirtyOnly || field.isDirty()) {
            data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);

            if (Ext.isObject(data)) {
                for (name in data) {
                    if (data.hasOwnProperty(name)) {
                        val = data[name];

                        if (includeEmptyText && val === '') {
                            val = field.emptyText || '';
                        }

                        if (values.hasOwnProperty(name)) {
                            bucket = values[name];

                            if (!isArray(bucket)) {
                                bucket = values[name] = [bucket];
                            }

                            if (isArray(val)) {
                                values[name] = bucket.concat(val);
                            } else {
                                bucket.push(val);
                            }
                        } else {
                            values[name] = val;
                        }
                    }
                }
            }
        }
    }
    // begin new part
    for (g = 0; g < gLen; g++) {
        grid = grids[f];
        gridStore = grid.getStore();
        gridData = [];

        // You will need a identification variable to determine which data should be taken from the grid. Currently this demo implement three options
        // 0 only selected
        // 1 complete data within the store
        // 2 only modified records (this can be splitted to new and updated)
        var ditems = grid.submitData === 0 ? grid.getSelectionModel().getSelection() : 
                     grid.submitData === 1 ? gridStore.getStore().data.items : gridStore.getStore().getModifiedRecords(),
            dlen = ditems.length;
        for(d = 0; d < dLen; d++) {
            // push the model data to the current data list. It doesn't matter of which type the models (records) are, this will simply read the whole known data. Alternatively you may access the rawdata property if the reader does not know all fields.
            gridData.push(ditems[d].data);
        }
        // assign the array of record data to the grid-name property
        data[grid.name] = gridData;
    }
    // end new part
    if (asString) {
        values = Ext.Object.toQueryString(values);
    }
    return values;
}

如果它看起来像这样,就连在一起

Ext.define('Ext.ux.form.Basic', {
    extend: 'Ext.form.Basic',

    /**
     * Creates new form.
     * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel}
     * @param {Object} config Configuration options. These are normally specified in the config to the
     * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
     */
    constructor: function(owner, config) {
        var me = this;

        me.callParent(arguments);
        // We use the monitor here as opposed to event bubbling. The problem with bubbling is it doesn't
        // let us react to items being added/remove at different places in the hierarchy which may have an
        // impact on the dirty/valid state.
        me.gridMonitor = new Ext.container.Monitor({
            selector: 'grid',
            scope: me,
            addHandler: me.onGridAdd,
            removeHandler: me.onGridRemove
        });
        me.gridMonitor.bind(owner);
    },

    onGridAdd: function(grid) {
        var me = this;
        me.mon(grid,'select',me.yourHandler,me);
    },

    onGridRemove: function(grid) {
        var me = this;
        me.mun(grid,'select',me.yourHandler,me);
    },

    /**
     * Return all the {@link Ext.grid.Panel} components in the owner container.
     * @return {Ext.util.MixedCollection} Collection of the Grid objects
     */
    getGrids: function() {
        return this.gridMonitor.getItems();
    },

    /**
     * Find a specific {@link Ext.grid.Panel} in this form by id or name.
     * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
     * {@link Ext.grid.Panel name }).
     * @return {Ext.grid.Panel} The first matching grid, or `null` if none was found.
     */
    findGrid: function(id) {
        return this.getGrids().findBy(function(f) {
            return f.id === id || f.name === id;
        });
    },

    getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
        var values  = {},
            fields  = this.getFields().items,
            grids  = this.getGrids().items, // the grids found by the monitor
            f,
            fLen    = fields.length,
            gLen    = grids.length, // gridcount
            isArray = Ext.isArray,
            grid, gridData, gridStore, // some vars used while reading the grid content
            field, data, val, bucket, name;

        for (f = 0; f < fLen; f++) {
            field = fields[f];

            if (!dirtyOnly || field.isDirty()) {
                data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);

                if (Ext.isObject(data)) {
                    for (name in data) {
                        if (data.hasOwnProperty(name)) {
                            val = data[name];

                            if (includeEmptyText && val === '') {
                                val = field.emptyText || '';
                            }

                            if (values.hasOwnProperty(name)) {
                                bucket = values[name];

                                if (!isArray(bucket)) {
                                    bucket = values[name] = [bucket];
                                }

                                if (isArray(val)) {
                                    values[name] = bucket.concat(val);
                                } else {
                                    bucket.push(val);
                                }
                            } else {
                                values[name] = val;
                            }
                        }
                    }
                }
            }
        }
        // begin new part
        for (g = 0; g < gLen; g++) {
            grid = grids[f];
            gridStore = grid.getStore();
            gridData = [];

            // You will need a identification variable to determine which data should be taken from the grid. Currently this demo implement three options
            // 0 only selected
            // 1 complete data within the store
            // 2 only modified records (this can be splitted to new and updated)
            var ditems = grid.submitData === 0 ? grid.getSelectionModel().getSelection() : 
                         grid.submitData === 1 ? gridStore.getStore().data.items : gridStore.getStore().getModifiedRecords(),
                dlen = ditems.length;
            for(d = 0; d < dLen; d++) {
                // push the model data to the current data list. It doesn't matter of which type the models (records) are, this will simply read the whole known data. Alternatively you may access the rawdata property if the reader does not know all fields.
                gridData.push(ditems[d].data);
            }
            // add the store data as array to the grid-name property
            data[grid.name] = gridData;
        }
        // end new part
        if (asString) {
            values = Ext.Object.toQueryString(values);
        }
        return values;
    }
});

接下来就是修改表单来使用这个基本的表单类型

Ext.define('Ext.ux.form.Panel', {
    extend:'Ext.form.Panel',
    requires: ['Ext.ux.form.Basic'],

    /**
     * @private
     */
    createForm: function() {
        var cfg = {},
            props = this.basicFormConfigs,
            len = props.length,
            i = 0,
            prop;

        for (; i < len; ++i) {
            prop = props[i];
            cfg[prop] = this[prop];
        }
        return new Ext.ux.form.Basic(this, cfg);
    }
});

注意:

这一切都未经测试!我已经为各种不同的东西做了类似的事情 客户来扩展表单的功能,我可以说 这种方式将非常有效和快速。至少它应该显示它是如何完成的,并且可以很容易地调整它以设置表单和/或加载/更新记录。

【讨论】:

    【解决方案2】:

    我自己没有使用过这个,但是有一个线程试图通过 hasMany 关系来处理关联模型。问题是每个人对记录的写操作过程中应该发生什么的期望略有不同。服务器端 ORM 以某种难以理解的方式处理此问题,并且通常是新开发人员的痛处。

    这是一个论坛主题,详细介绍了自定义 JSON 编写器以将父记录与其子记录一起保存。

    以下代码似乎至少对某些人有效:

    Ext.data.writer.Json.override({
    /*
     * This function overrides the default implementation of json writer. Any hasMany relationships will be submitted
     * as nested objects. When preparing the data, only children which have been newly created, modified or marked for
     * deletion will be added. To do this, a depth first bottom -> up recursive technique was used.
     */
    getRecordData: function(record) {
        //Setup variables
        var me = this, i, association, childStore, data = record.data;
    
        //Iterate over all the hasMany associations
        for (i = 0; i < record.associations.length; i++) {
            association = record.associations.get(i);
            data[association.name] = null;
            childStore = record[association.storeName];
    
            //Iterate over all the children in the current association
            childStore.each(function(childRecord) {
    
                if (!data[association.name]){
                    data[association.name] = [];
                }
    
                //Recursively get the record data for children (depth first)
                var childData = this.getRecordData.call(this, childRecord);
    
                /*
                 * If the child was marked dirty or phantom it must be added. If there was data returned that was neither
                 * dirty or phantom, this means that the depth first recursion has detected that it has a child which is
                 * either dirty or phantom. For this child to be put into the prepared data, it's parents must be in place whether
                 * they were modified or not.
                 */
                if (childRecord.dirty | childRecord.phantom | (childData != null)){
                    data[association.name].push(childData);
                    record.setDirty();
                }
            }, me);
    
            /*
             * Iterate over all the removed records and add them to the preparedData. Set a flag on them to show that
             * they are to be deleted
             */
            Ext.each(childStore.removed, function(removedChildRecord) {
                //Set a flag here to identify removed records
                removedChildRecord.set('forDeletion', true);
                var removedChildData = this.getRecordData.call(this, removedChildRecord);
                data[association.name].push(removedChildData);
                record.setDirty();
            }, me);
        }
    
        //Only return data if it was dirty, new or marked for deletion.
        if (record.dirty | record.phantom | record.get('forDeletion')){
            return data;
        }
    }
    });
    

    完整的线程位于此处: http://www.sencha.com/forum/showthread.php?141957-Saving-objects-that-are-linked-hasMany-relation-with-a-single-Store/page5

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多