Реализация отношений главный-подчиненный на примере двух таблиц с одним исходным набором данных в формате JSON.
Иногда с виду простая задача для программиста на одном языке оказывается достаточно проблематичной на другом. Так, например, во фремворке ExtJS 4.1.3 для того чтобы реализовать между таблицами связку главная-подчиненная имея только один источник данных вложенной структуры от меня потребовалось немало терпения и изучения исходных кодов самого фреймворка.
Задача
Исходные данные в формате JSON:
1 2 3 4 5 6 7 8 9 10 11 12 |
[ { name: 'one', params:{}, items: [{item: 'item1', detail:{}}, {item: 'item2', detail: {id: '1'}}] }, { name: 'two', params:{param:'param2'}, items: [{item: 'item3', detail:{}}, {item: 'item4', detail: {id: '2'}}] } ] |
Чтобы упростить задачу и не решать одновременно проблему несоответствия данных и модели описанную в посте Ext.ux.data.reader.SafeJson – безопасный JSON все данные у нас будут соответствовать модели их описания.
Итак, требуется реализовать пример отношения главный-подчиненной таблицы на основе одного источника данных без приминения ассоциаций.
Проблемы
Основная проблема заключается в том, что Ext JS не очень хоршо работает с вложенными данными в рамках обычного хранилища данных Ext.data.Store. У меня получилось подобрать только одну работающую конфигурацию кода.
Проблемное место — загрузка данных в хранилище с детальной информацией. Стандартный Ext.data.reader.Json ведет себя крайне неадекватно, когда мы пытаемся в ручную загрузить данные в хранилище во время его создания через парамет data, как масив так и исходный json (raw).
Решение
Описываем модели данных:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Ext.define('modelMaster', { extend: 'Ext.data.Model', fields: [ {name: 'name'}, {name: 'params.param'}, {name: 'items'} ] }); Ext.define('modelDetail', { extend: 'Ext.data.Model', fields: ['item', 'detail.id'] }); |
При выборе строки записи на главной таблице в подчиненной таблице должны отображатся все данные поля items, в связи с этим мы не описываем детали поля items в главной модели, к тому же мы это сделать не сможем, так как это массив, а переносим это описание в модель для детальных данных.
Иными словами, для того чтобы в главном хранилище взять массив данных items, мы должны их описать в модели просто как поле «items«.
Поле params используется исключительно в проверочных целях для отображения в главной таблице.
Далее создаем главное хранилище:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Ext.create('Ext.data.Store', { model: modelMaster, autoLoad: true, data: [ {name: 'one', params:{}, items: [{item: 'item1', detail:{}}, {item: 'item2', detail: {id: '1'}}]}, {name: 'two', params:{param:'param2'}, items: [{item: 'item3', detail:{}}, {item: 'item4', detail: {id: '2'}}]} ], proxy: { type: 'memory', reader: { type: 'json' } }, listeners: { load: onLoad } }); |
И после того как все данные загрузятся выполняется главная для нас процедура onLoad:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
var onLoad = function(store, records, successful, eOpts) { var initRec = records[0]; // берем первую запись // создаем хранилище для отображения детальной информации var storeDetail = Ext.create('Ext.data.Store', { model: modelDetail, proxy: { type: 'memory', reader:{ type:'json' } }, // показываем по умолчанию исходные данные первой записи data: initRec.raw['items'] }); // создаем таблицу с детальной информацией var gridDetail = Ext.create('Ext.grid.Panel', { title: 'Detail', columns: [ {text: 'Item', dataIndex: 'item', flex: 1}, {text: 'Detail ID', dataIndex: 'detail.id', flex: 1} ], store: storeDetail }); // создаем таблицу с главными данными var gridMaster = Ext.create('Ext.grid.Panel', { title: 'Master', columns: [ {text: 'Name', dataIndex: 'name', flex: 1}, {text: 'Param', dataIndex: 'params.param', flex: 1} ], store: store, listeners: { // обрабатываем нажатие мышки на ячейки cellclick: function (grid, td, cellIndex, record, tr, rowIndex, e, eOpts) { //gridDetail.getStore().loadData(record.data.items); //<-- doesn't work! // загружаем данные 'items' в детальную таблицу gridDetail.getStore().loadRawData(record.raw['items']); }, afterrender: function (grid) { // выберем в главной таблице активную запись по умолчанию grid.getSelectionModel().select(initRec, true, false); } } }); Ext.create('Ext.container.Viewport', { renderTo: Ext.getBody(), layout: { type: 'hbox', align: 'stretch', padding: 10 }, defaults: { flex: 1 }, items: [gridMaster, { xtype: 'splitter', flex: 0, width: 10 }, gridDetail] }); }; |
Исходный код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
Ext.define('modelMaster', { extend: 'Ext.data.Model', fields: [ {name: 'name'}, {name: 'params.param'}, {name: 'items'} ] }); Ext.define('modelDetail', { extend: 'Ext.data.Model', fields: ['item', 'detail.id'] }); var onLoad = function(store, records, successful, eOpts) { var rec = [{item: 'item1', detail: {}}, {item: 'item2', detail: {id: '1'}}]; var initRec = records[0]; console.log(initRec.raw.items); console.log(rec); var storeDetail = Ext.create('Ext.data.Store', { model: modelDetail, proxy: { type: 'memory', reader:{ type:'json' } }, data: initRec.raw['items'] //data: rec }); var gridDetail = Ext.create('Ext.grid.Panel', { title: 'Detail', columns: [ {text: 'Item', dataIndex: 'item', flex: 1}, {text: 'Detail ID', dataIndex: 'detail.id', flex: 1} ], store: storeDetail }); var gridMaster = Ext.create('Ext.grid.Panel', { title: 'Master', columns: [ {text: 'Name', dataIndex: 'name', flex: 1}, {text: 'Param', dataIndex: 'params.param', flex: 1} ], store: store, listeners: { cellclick: function (grid, td, cellIndex, record, tr, rowIndex, e, eOpts) { //gridDetail.getStore().loadData(record.data.items); //<-- doesn't work! gridDetail.getStore().loadRawData(record.raw['items']); }, afterrender: function (grid) { grid.getSelectionModel().select(initRec, true, false); } } }); Ext.create('Ext.container.Viewport', { renderTo: Ext.getBody(), layout: { type: 'hbox', align: 'stretch', padding: 10 }, defaults: { flex: 1 }, items: [gridMaster, { xtype: 'splitter', flex: 0, width: 10 }, gridDetail] }); }; Ext.create('Ext.data.Store', { model: modelMaster, autoLoad: true, data: [ {name: 'one', params:{}, items: [{item: 'item1', detail:{}}, {item: 'item2', detail: {id: '1'}}]}, {name: 'two', params:{param:'param2'}, items: [{item: 'item3', detail:{}}, {item: 'item4', detail: {id: '2'}}]} ], proxy: { type: 'memory', reader: { type: 'json' } }, listeners: { load: onLoad } }); |