4

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

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

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

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'
})
});
4

2 回答 2

6

更新的答案

经过一些澄清后,我认为答案应该很容易(至少我是这么认为的)对于以下答案,我假设您在要获取表单和网格数据时在表单内,并且只有一个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.Basic构造函数,其中重要部分是 this

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

因为我不太了解您的数据结构,所以我无法告诉您您可能需要哪些 gridevents,但您应该在 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);
    }
});

笔记:

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

于 2013-07-12T10:00:48.753 回答
0

我自己没有使用过这个,但是有一个线程试图通过 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

于 2013-07-11T20:59:21.577 回答