2013-07-09 3 views
4

У меня сложная форма, которую я не могу использовать в технике сериализации формы. Существует множество полей, а также динамическая сетка (сетка, динамически генерирующая при выборе пользователем некоторых критериев) внутри формы.ExtJS - Сложная форма сериализации

Что я хочу делать, собирать пользовательские входы/выборки + добавлять выбранные записи, доступные в сетке, а затем, наконец, создавать массив JSON с этими данными, чтобы иметь возможность отправлять серверные сообщения.

Мое предположение, я могу использовать функцию ExtJS для работы со всеми объектами, но, как вы могли догадаться, это немного сложно поддерживать. Кроме того, я понятия не имею, чтобы получить данные сетки таким образом!

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

Я предлагаю прочитать любые данные из записей в магазине, независимо от их типа. – sra

+0

Уважаемый @sra Мне любопытно, как мы можем обрабатывать выбранные записи сетки? Реально не решался в последние три дня. Когда пользователь задает критерии, я могу создать хранилище сетки «на лету», а затем показать панель сетки. Я не могу сделать это в последней части, которая делает JsonStore целыми данными. Там должен быть простой способ сделать это. Не имеет значения, какие данные доступны в JsonStore, которые я могу обрабатывать на стороне сервера. –

+0

Я не уверен, действительно ли я понимаю, какова ваша цель ... Вы хотите 1. получить только выбранные записи из сеток, 2. получить только измененные записи из сеток, 3. получить все записи или 4. что-то полностью другой. Какую структуру данных вы ожидаете? Я имею в виду, что вам все равно нужно будет вернуть данные обратно в объекты на бэкэнд ...? – sra

ответ

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 класса constructor, где важную часть этого

// 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 вам могут понадобиться, но вы должны зарегистрироваться/отменить их в ДобавитьОбработчик/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); 
    } 
}); 

Примечание:

Это все непроверено!Я сделал что-то подобное для разных клиентов , чтобы расширить возможности форм, и могу сказать, что этот способ будет работать очень хорошо и быстро. По крайней мере, он должен показать, как это можно сделать, и его можно легко настроить, чтобы также настроить формы и/или загружать/обновлять записи.

0

Я не использовал это сам, но есть поток, который пытается справиться с связанные модели через hasMany отношения. Проблема в том, что у каждого есть несколько разные ожидания того, что должно произойти во время записи записей. ORM Serever стороны справляются с этой проблемой в несколько трудном для понимания образом и часто являются больным местом для новых разработчиков.

Вот эта тема форума, в которой описывается пользовательский 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

Смежные вопросы