- JSON with nested hierarchy: https://fiddle.sencha.com/#view/editor&fiddle/332p
- JSON with flat hierarchy: https://fiddle.sencha.com/#view/editor&fiddle/333b
Last active
February 4, 2020 05:30
-
-
Save andrewsantarin/18355256af37aea58711a8d12e1183f6 to your computer and use it in GitHub Desktop.
Grid-in-Grid for Ext.js v5.0.1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"id": 4, | |
"no": "001122", | |
"description": "Order description", | |
"customerId": 1, | |
"parentId": 4 | |
}, | |
{ | |
"id": 41, | |
"no": "001122-E", | |
"description": "Order description", | |
"customerId": 1, | |
"parentId": 4 | |
}, | |
{ | |
"id": 5, | |
"no": "334455", | |
"description": "Order description", | |
"customerId": 2, | |
"parentId": 5 | |
}, | |
{ | |
"id": 51, | |
"no": "334455-E", | |
"description": "Order description", | |
"customerId": 2, | |
"parentId": 5 | |
}, | |
{ | |
"id": 6, | |
"no": "667788", | |
"description": "Order description", | |
"customerId": 2, | |
"parentId": "" | |
}, | |
{ | |
"id": 7, | |
"no": "889900", | |
"description": "Order description", | |
"customerId": 1 | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"id": 1, | |
"no": "112233", | |
"description": "Order description", | |
"customerId": 1, | |
"parentId": 1 | |
}, | |
{ | |
"id": 11, | |
"no": "112233-A", | |
"description": "Order description", | |
"customerId": 1, | |
"parentId": 1 | |
}, | |
{ | |
"id": 12, | |
"no": "112233-B", | |
"description": "Order description", | |
"customerId": 1, | |
"parentId": 1 | |
}, | |
{ | |
"id": 2, | |
"no": "445566", | |
"description": "Order description", | |
"customerId": 2, | |
"parentId": 2 | |
}, | |
{ | |
"id": 21, | |
"no": "445566-C", | |
"description": "Order description", | |
"customerId": 2, | |
"parentId": 2 | |
}, | |
{ | |
"id": 22, | |
"no": "445566-D", | |
"description": "Order description", | |
"customerId": 2, | |
"parentId": 2 | |
}, | |
{ | |
"id": 3, | |
"no": "778899", | |
"description": "Order description", | |
"customerId": 1, | |
"parentId": null | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"id": 4, | |
"no": "001122", | |
"description": "Order description", | |
"customerId": 1, | |
"children": [ | |
{ | |
"id": 41, | |
"no": "001122-E", | |
"description": "Order description", | |
"customerId": 1 | |
} | |
], | |
}, | |
{ | |
"id": 5, | |
"no": "334455", | |
"description": "Order description", | |
"customerId": 2, | |
"children": [ | |
{ | |
"id": 51, | |
"no": "334455-E", | |
"description": "Order description", | |
"customerId": 2 | |
} | |
], | |
}, | |
{ | |
"id": 6, | |
"no": "667788", | |
"description": "Order description", | |
"customerId": 2 | |
}, | |
{ | |
"id": 7, | |
"no": "889900", | |
"description": "Order description", | |
"customerId": 1 | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"id": 1, | |
"no": "112233", | |
"description": "Order description", | |
"customerId": 1, | |
"children": [ | |
{ | |
"id": 11, | |
"no": "112233-A", | |
"description": "Order description", | |
"customerId": 1 | |
}, | |
{ | |
"id": 12, | |
"no": "112233-B", | |
"description": "Order description", | |
"customerId": 1 | |
} | |
] | |
}, | |
{ | |
"id": 2, | |
"no": "445566", | |
"description": "Order description", | |
"customerId": 2, | |
"children": [ | |
{ | |
"id": 21, | |
"no": "445566-C", | |
"description": "Order description", | |
"customerId": 2 | |
}, | |
{ | |
"id": 22, | |
"no": "445566-D", | |
"description": "Order description", | |
"customerId": 2 | |
} | |
] | |
}, | |
{ | |
"id": 3, | |
"no": "778899", | |
"description": "Order description", | |
"customerId": 1 | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Ext.application({ | |
name: 'Fiddle', | |
loaded: false, | |
added: false, | |
launch: function () { | |
var me = this; | |
var storeConfig = { | |
title: 'Orders', | |
fields: [ | |
'id', | |
'no', | |
'customerId', | |
'description' | |
], | |
groupField: 'customerId', | |
proxy: { | |
type: 'memory', | |
reader: { | |
type: 'json' | |
} | |
} | |
}; | |
var columns = [ | |
{ | |
text: 'ID', | |
dataIndex: 'id' | |
}, | |
{ | |
text: 'Order Number', | |
dataIndex: 'no' | |
}, | |
{ | |
text: 'Order Description', | |
dataIndex: 'description', | |
flex: 1 | |
} | |
]; | |
var store = Ext.create('Ext.data.Store', Ext.merge({}, storeConfig, { | |
listeners: { | |
datachanged: function updateStores(thisStore) { | |
var records = thisStore.getRange(); | |
var childRecords = records.filter(function (record) { | |
var parentId = record.get('parentId'); | |
var id = record.get('id'); | |
return parentId === id | |
|| parentId === '' | |
|| parentId == null; | |
}); | |
me.grid.store.loadData(childRecords); | |
} | |
} | |
})); | |
var grid = new GridInGrid({ | |
columns: columns, | |
store: Ext.create('Ext.data.Store', storeConfig), | |
getSubGridViewConfig: function (rowNode, record, expandRow, eOpts) { | |
var _id = record.get('id'); | |
var records = store.getRange(); | |
var childStore = Ext.create('Ext.data.Store', storeConfig); | |
var childData = records.filter(function (record) { | |
var parentId = record.get('parentId'); | |
var id = record.get('id'); | |
return parentId === _id | |
&& parentId !== id; | |
}); | |
childStore.loadData(childData); | |
var config = { | |
columns: columns, | |
store: childStore | |
}; | |
return config; | |
}, | |
getSubGridViewConfigChildren: function (record, store) { | |
var children = store.getRange(); | |
return children; | |
}, | |
shouldRowExpand: function requireRows(record, rowIndex, rowParams) { | |
var _id = record.get('id'); | |
var _records = store.getRange(); | |
var records = _records.filter(function (record) { | |
var parentId = record.get('parentId'); | |
var id = record.get('id'); | |
return parentId === _id | |
&& parentId !== id; | |
}); | |
var hasRecords = records.length > 0; | |
return hasRecords; | |
}, | |
features: [ | |
{ | |
ftype: 'grouping' | |
} | |
], | |
height: '100%', | |
width: '100%', | |
title: 'Orders' | |
}); | |
this.grid = grid; | |
window.grid = grid; | |
window.store = store; | |
var app = this; | |
this.loadOrdersButton = Ext.create('Ext.Button', { | |
text: 'Load initial orders', | |
renderTo: Ext.getBody(), | |
handler: function() { | |
if (app.loaded) { | |
return; | |
} | |
store.loadData(orders); | |
app.loaded = true; | |
app.resetButton.setDisabled(false); | |
this.setDisabled(true); | |
} | |
}); | |
this.loadNewOrdersButton = Ext.create('Ext.Button', { | |
text: 'Add more orders', | |
renderTo: Ext.getBody(), | |
handler: function() { | |
if (app.added || !app.loaded) { | |
return; | |
} | |
var data = store.getData(); | |
var orders = data.items.map(function (o) { | |
return o.data; | |
}); | |
store.loadData(orders.concat(newOrders)); | |
app.added = true; | |
app.resetButton.setDisabled(false); | |
this.setDisabled(true); | |
} | |
}); | |
this.resetButton = Ext.create('Ext.Button', { | |
text: 'Reset', | |
renderTo: Ext.getBody(), | |
disabled: true, | |
handler: function() { | |
if (!app.loaded) { | |
return; | |
} | |
store.removeAll(); | |
app.loaded = false; | |
app.loadOrdersButton.setDisabled(false); | |
app.added = false; | |
app.loadNewOrdersButton.setDisabled(false); | |
this.setDisabled(true); | |
} | |
}) | |
var win = Ext.create('Ext.window.Window', { | |
title: 'Grid in Grid', | |
titleAlign: 'center', | |
resizable: true, | |
maximizable: true, | |
layout: 'fit', | |
height: 600, | |
width: 600, | |
items: [grid] | |
}); | |
win.show(); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Ext.application({ | |
name: 'Fiddle', | |
loaded: false, | |
added: false, | |
launch: function () { | |
var me = this; | |
var storeConfig = { | |
title: 'Orders', | |
fields: [ | |
'id', | |
'no', | |
'customerId', | |
'description' | |
], | |
groupField: 'customerId', | |
proxy: { | |
type: 'memory', | |
reader: { | |
type: 'json' | |
} | |
} | |
}; | |
var columns = [ | |
{ | |
text: 'ID', | |
dataIndex: 'id' | |
}, | |
{ | |
text: 'Order Number', | |
dataIndex: 'no' | |
}, | |
{ | |
text: 'Order Description', | |
dataIndex: 'description', | |
flex: 1 | |
} | |
]; | |
var store = Ext.create('Ext.data.Store', Ext.merge({}, storeConfig, { | |
listeners: { | |
datachanged: function updateStores(thisStore) { | |
var records = thisStore.getRange(); | |
var childRecords = records.filter(function (record) { | |
var parentId = record.get('parentId'); | |
var id = record.get('id'); | |
return parentId === id | |
|| parentId === '' | |
|| parentId == null; | |
}); | |
me.grid.store.loadData(childRecords); | |
} | |
} | |
})); | |
var grid = new GridInGrid({ | |
columns: columns, | |
store: Ext.create('Ext.data.Store', storeConfig), | |
getSubGridViewConfig: function (rowNode, record, expandRow, eOpts) { | |
var children = record.get('children'); | |
var store = Ext.create('Ext.data.Store', Ext.apply({}, storeConfig, { | |
data: children | |
})); | |
var viewConfig = { | |
columns: columns, | |
store: store | |
}; | |
return viewConfig; | |
}, | |
getSubGridViewConfigChildren: function (record, store) { | |
var children = record.get('children'); | |
return children; | |
}, | |
features: [ | |
{ | |
ftype: 'grouping' | |
} | |
], | |
height: '100%', | |
width: '100%', | |
title: 'Orders' | |
}); | |
this.grid = grid; | |
window.grid = grid; | |
window.store = store; | |
var app = this; | |
this.loadOrdersButton = Ext.create('Ext.Button', { | |
text: 'Load initial orders', | |
renderTo: Ext.getBody(), | |
handler: function() { | |
if (app.loaded) { | |
return; | |
} | |
store.loadData(orders); | |
app.loaded = true; | |
app.resetButton.setDisabled(false); | |
this.setDisabled(true); | |
} | |
}); | |
this.loadNewOrdersButton = Ext.create('Ext.Button', { | |
text: 'Add more orders', | |
renderTo: Ext.getBody(), | |
handler: function() { | |
if (app.added || !app.loaded) { | |
return; | |
} | |
var data = store.getData(); | |
var orders = data.items.map(function (o) { | |
return o.data; | |
}); | |
store.loadData(orders.concat(newOrders)); | |
app.added = true; | |
app.resetButton.setDisabled(false); | |
this.setDisabled(true); | |
} | |
}); | |
this.resetButton = Ext.create('Ext.Button', { | |
text: 'Reset', | |
renderTo: Ext.getBody(), | |
disabled: true, | |
handler: function() { | |
if (!app.loaded) { | |
return; | |
} | |
store.removeAll(); | |
app.loaded = false; | |
app.loadOrdersButton.setDisabled(false); | |
app.added = false; | |
app.loadNewOrdersButton.setDisabled(false); | |
this.setDisabled(true); | |
} | |
}) | |
var win = Ext.create('Ext.window.Window', { | |
title: 'Grid in Grid', | |
titleAlign: 'center', | |
resizable: true, | |
maximizable: true, | |
layout: 'fit', | |
height: 600, | |
width: 600, | |
items: [grid] | |
}); | |
win.show(); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.ux-row-expander-hidden .x-grid-row-expander { | |
visibility: hidden; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Grid-in-Grid for single-level nested grids. | |
* | |
* @class GridInGrid | |
* @param {Partial<GridInGrid>} options Additional options. | |
*/ | |
var GridInGrid = Ext.define('grid.GridInGrid', { | |
extend: 'Ext.grid.Panel', | |
alias: 'widget.gridingrid', | |
/** | |
* (optional) The CSS class to assign to the expandable subgrid content. | |
* | |
* @default "ux-row-expander-box" | |
*/ | |
rowBodyTplCss: 'ux-row-expander-box', | |
/** | |
* (optional) The CSS class to assign to the row subgrid toggler when no subgrid data is available. | |
* | |
* @default "ux-row-expander-hidden" | |
*/ | |
rowHideExpanderCss: 'ux-row-expander-hidden', | |
/** | |
* (optional) A callback which generates the configuration for the subgrid. If not specified, the subgrid will not be rendered. | |
* | |
* @default null | |
* @param {HTMLElement} rowNode The `<tr>` element which owns the expanded row. | |
* @param {Ext.data.Model} record The record providing the data for the row as modeled for the store. | |
* @param {HTMLElement} expandRow The `<tr>` element containing the expanded data. | |
* @param {Object} eOpts Options passed to the subgrid. | |
* @returns {Partial<Ext.grid.Panel>} A configuration object for the subgrid. | |
*/ | |
getSubGridViewConfig: null, | |
/** | |
* (optional) A callback which generates a custom list of row data for the subgrid. If not specified, the subgrid will use the parent row data's `children` attribute. | |
* | |
* @default null | |
* @param {Ext.data.Model} record The individual row data entry context from the maingrid. | |
* @param {Ext.data.Store} store The row data's store. Can also be accessed from `record.store`. | |
* @returns {Ext.data.Store[]} A list of row data. | |
*/ | |
getSubGridViewConfigChildren: null, | |
/** | |
* (optional) A callback which determines, **in context of one row and its subgrid**: | |
* | |
* - whether to show or hide the toggling control which expands the row subgrid area when the control is clicked. | |
* - whether to expand or collapse the row subgrid area when the toggling control is clicked. | |
* - whether to expand or collapse the row subgrid area when the `[Enter]` key is pressed while the row is highlighted. | |
* | |
* If not specified, the subgrid will only be toggled when the row contains children data (see `getSubGridViewConfigChildren(record, store)`). | |
* | |
* @default null | |
* @param {Ext.data.Model} record The record corresponding to the current row. | |
* @param {Number} index The row index. | |
* @param {Object} rowParams - **DEPRECATED.** For row body, use the `getAdditionalData` method of the rowbody feature. | |
* @param {Ext.data.Store} store The store this grid is bound to. | |
* @returns {Boolean} An indicator which determines whether to show or hide the individual row subgrids. | |
*/ | |
shouldRowExpand: null, | |
/** | |
* @see https://fiddle.sencha.com/#fiddle/jqi&view/editor | |
*/ | |
initComponent: function GridInGrid_initComponent() { | |
// NOTE: It's necessary to use the extended RowExpander plugin to use the smart toggler element show-hide condition. | |
var moduleName = 'grid.plugin.RowContentExpander'; | |
var me = this; | |
me.requires = mergeRequires(me.requires, [moduleName]); | |
me.plugins = mergePlugins(me.plugins, { | |
rowcontentexpander: Ext.create(moduleName, { | |
rowBodyTpl: '<div class="' + me.rowBodyTplCss + '"></div>', | |
shouldToggleRow: function GridInGrid_shouldToggleRow(plugin, rowIndex, record) { | |
// HACK: Since `rowParams` is said to be deprecated, `null` might as well be used, which is the value seen during testing `getRowClass`. | |
var rowParams = null; | |
var store = me.getStore(); | |
var isExpandable = me.rowIsExpandable(record, rowIndex, rowParams, store); | |
return isExpandable; | |
} | |
}) | |
}); | |
// TODO: Improve the flexibility of this segment. | |
me.viewConfig = { | |
getRowClass: function GridInGrid_getRowClass(record, rowIndex, rowParams, store) { | |
var isExpandable = me.rowIsExpandable(record, rowIndex, rowParams, store); | |
if (!isExpandable) { | |
return me.rowHideExpanderCss; | |
} | |
}, | |
listeners: { | |
expandbody: function GridInGrid_handleExpandBody(rowNode, record, expandRow, eOpts) { | |
var element = Ext.get(expandRow) | |
.child('.x-grid-cell-rowbody') | |
.child('.x-grid-rowbody') | |
.child('.' + me.rowBodyTplCss); | |
var container = element.child('.x-panel', true); | |
if (container != null) { | |
return; | |
} | |
var viewConfig = typeof me.getSubGridViewConfig === 'function' ? me.getSubGridViewConfig(rowNode, record, expandRow, eOpts) : null; | |
if (viewConfig == null) { | |
return; | |
} | |
var children = typeof me.getSubGridViewConfigChildren === 'function' ? me.getSubGridViewConfigChildren(record, viewConfig.store) : record.get('children'); | |
if (children == null || children.length === 0) { | |
return; | |
} | |
// Prevent event bubbling errors. | |
// https://stackoverflow.com/questions/29717913/extjs-error-in-nested-grids-cannot-read-property-isgroupheader-of-null | |
var swallowedEvents = [ | |
'contextmenu', | |
'click', | |
'dblclick', | |
'mousedown', | |
'mouseup', | |
'mouseover', | |
'mouseout', | |
'mousemove' | |
]; | |
// If a nested grid is necessary, perhaps recursive component creation is necessary. | |
var subGrid = Ext.create('Ext.grid.Panel', viewConfig); | |
subGrid.render(element); | |
subGrid.getEl().swallowEvent(swallowedEvents); | |
} | |
} | |
}; | |
me.callParent(arguments); | |
}, | |
rowIsExpandable: function GridInGrid_rowIsExpandable(record, rowIndex, rowParams, store) { | |
var me = this; | |
var children = typeof me.getSubGridViewConfigChildren === 'function' ? me.getSubGridViewConfigChildren(record, store) : record.get('children'); | |
var hasChildren = children != null && children.length > 0; | |
var isExpandable = typeof me.shouldRowExpand === 'function' ? me.shouldRowExpand(record, rowIndex, rowParams, store) : hasChildren; | |
return isExpandable; | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Row Expander with conditional toggling per row. | |
* | |
* @class RowContentExpander | |
* @param {Partial<RowContentExpander>} options Additional options. | |
*/ | |
var RowContentExpander = Ext.define('grid.plugin.RowContentExpander', { | |
extend: 'Ext.grid.plugin.RowExpander', | |
alias: 'plugin.rowcontentexpander', | |
/** | |
* Assigns grid, the plugin name and calls the parent instance. | |
*/ | |
init: function RowContentExpander_init(grid) { | |
var me = this; | |
// Provide the module name so that it can be searched in other | |
me.pluginModuleName = 'App.grid.plugin.RowContentExpander'; | |
me.grid = grid; | |
me.callParent(arguments); | |
}, | |
/** | |
* Handles row expansion and collapse. | |
* | |
* @see https://fiddle.sencha.com/#fiddle/10ta&view/editor | |
*/ | |
toggleRow: function RowContentExpander_toggleRow(index, record) { | |
var me = this; | |
var isToggleable = typeof me.shouldToggleRow === 'function' ? me.shouldToggleRow(me, index, record) : true; | |
if (!isToggleable) { | |
return; | |
} | |
me.callParent(arguments); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Merges a plugin map into a list of plugins. | |
* | |
* @param {*} [plugins] The original list of plugins. | |
* @param {*} [pluginMap] An object map of additional plugins to add. | |
* @returns {(Ext.enums.Plugin | Ext.plugin.Abstract | Object)[]} A merged list of instantiatable plugins. | |
*/ | |
function mergePlugins(plugins, pluginMap) { | |
var finalPlugins = plugins; | |
if (finalPlugins == null) { | |
finalPlugins = []; | |
} | |
if (Ext.isObject(finalPlugins)) { | |
finalPlugins = [finalPlugins]; | |
} | |
if (!Ext.isArray(finalPlugins)) { | |
finalPlugins = [finalPlugins]; | |
} | |
Ext.Object.getKeys(pluginMap).forEach(function (key) { | |
var isFound = finalPlugins.filter(function (plugin) { | |
if (plugin == null) { | |
return false; | |
} | |
return plugin.ptype === key || plugin === key; | |
}).length > 0; | |
if (!isFound) { | |
finalPlugins.push(pluginMap[key]); | |
} | |
}); | |
return finalPlugins; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Merges two "requires" lists together. | |
* | |
* @param {String[]} [requires] The original list of modules. | |
* @param {String[]} [newRequires] An additional list of modules to add. | |
* @returns {String[]} A merged list of required modules. | |
*/ | |
function mergeRequires(requires, newRequires) { | |
var oldRequires = Ext.isArray(requires) ? requires : []; | |
if (newRequires == null) { | |
return oldRequires; | |
} | |
if (!Ext.isArray(newRequires)) { | |
return oldRequires; | |
} | |
return Ext.Array.merge(oldRequires, newRequires); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment