/** * This class remodels the grid store when required. * * @private */Ext.define('Ext.pivot.feature.PivotStore', { config: { store: null, grid: null, matrix: null, clsGrandTotal: '', clsGroupTotal: '' }, totalRowEvent: 'pivottotal', groupRowEvent: 'pivotgroup', itemRowEvent: 'pivotitem', constructor: function(config) { this.initConfig(config); return this.callParent(arguments); }, destroy: function() { var me = this; Ext.destroy(me.storeListeners, me.matrixListeners); me.setConfig({ store: null, matrix: null, grid: null }); me.storeInfo = me.storeListeners = null; me.callParent(arguments); }, updateStore: function(store) { var me = this; Ext.destroy(me.storeListeners); if (store) { me.storeListeners = store.on({ // this event is fired by the pivot grid for private use pivotstoreremodel: me.processStore, scope: me, destroyable: true }); } }, updateMatrix: function(matrix) { var me = this; Ext.destroy(me.matrixListeners); if (matrix) { me.matrixListeners = matrix.on({ // this event is fired by the pivot grid for private use groupexpand: me.onGroupExpand, groupcollapse: me.onGroupCollapse, scope: me, destroyable: true }); } }, processStore: function() { var me = this, store = me.getStore(), matrix = me.getMatrix(), records = [], items, length, i, group, fields; if (!matrix || !store) { return; } fields = matrix.getColumns(); store.suspendEvents(); store.model.replaceFields(fields, true); me.storeInfo = {}; if (matrix.rowGrandTotalsPosition === 'first') { records.push.apply(records, me.processGrandTotal() || []); } items = matrix.leftAxis.getTree(); length = items.length; for (i = 0; i < length; i++) { group = items[i]; records.push.apply(records, me.processGroup({ group: group, previousExpanded: (i > 0 ? items[i - 1].expanded : false) }) || []); } if (matrix.rowGrandTotalsPosition === 'last') { records.push.apply(records, me.processGrandTotal() || []); } // loadRecords called by loadData removes existing records by default. store.loadData(records); store.resumeEvents(); store.fireEvent('refresh', store); }, processGroup: function(config) { var me = this, fn = me['processGroup' + Ext.String.capitalize(me.getMatrix().viewLayoutType)]; if (!Ext.isFunction(fn)) { // specified view type doesn't exist so let's use the outline view fn = me.processGroupOutline; } return fn.call(me, config); }, processGrandTotal: function() { var me = this, found = false, matrix = me.getMatrix(), group = { key: matrix.grandTotalKey }, records = [], lenT = matrix.totals.length, dimensions = matrix.leftAxis.dimensions.items, lenD = dimensions.length, i, j, k, total, column, record, key; for (i = 0; i < lenT; i++) { total = matrix.totals[i]; record = total.record; k = lenD; if (record && record.isModel) { me.storeInfo[record.internalId] = { leftKey: group.key, rowStyle: '', rowClasses: [me.getClsGrandTotal()], rowEvent: me.totalRowEvent, rendererParams: {} }; for (j = 0; j < lenD; j++) { column = dimensions[j]; if (matrix.viewLayoutType === 'compact' || j === 0) { if (matrix.viewLayoutType === 'compact') { key = matrix.compactViewKey; k = 1; } else { key = column.getId(); } record.data[key] = total.title; me.storeInfo[record.internalId].rendererParams[key] = { fn: 'groupOutlineRenderer', group: group, colspan: k, hidden: false, subtotalRow: true }; found = true; } else { me.storeInfo[record.internalId].rendererParams[column.id] = { fn: 'groupOutlineRenderer', group: group, colspan: 0, hidden: found, subtotalRow: true }; k--; } } // for all top axis columns use a new renderer me.storeInfo[record.internalId].rendererParams.topaxis = { fn: 'topAxisRenderer' }; records.push(record); } } return records; }, // Outline view functions processGroupOutline: function(config) { var me = this, group = config.group, results = []; if (group.record) { me.processRecordOutline({ results: results, group: group }); } else { me.processGroupOutlineWithChildren({ results: results, group: group, previousExpanded: config.previousExpanded }); } return results; }, processGroupOutlineWithChildren: function(config) { var me = this, matrix = me.getMatrix(), group = config.group, previousExpanded = config.previousExpanded, hasSummaryData, record, i, len; hasSummaryData = (!group.expanded || (group.expanded && matrix.rowSubTotalsPosition === 'first')); record = group.expanded ? group.records.expanded : group.records.collapsed; me.processGroupHeaderRecordOutline({ results: config.results, group: group, record: record, previousExpanded: previousExpanded, hasSummaryData: hasSummaryData }); if (group.expanded) { if (group.children) { len = group.children.length; for (i = 0; i < len; i++) { if (group.children[i].children) { me.processGroupOutlineWithChildren({ results: config.results, group: group.children[i] }); } else { me.processRecordOutline({ results: config.results, group: group.children[i] }); } } } if (matrix.rowSubTotalsPosition === 'last') { record = group.records.footer; me.processGroupHeaderRecordOutline({ results: config.results, group: group, record: record, previousExpanded: previousExpanded, subtotalRow: true, hasSummaryData: true }); } } }, processGroupHeaderRecordOutline: function(config) { var me = this, matrix = me.getMatrix(), group = config.group, record = config.record, previousExpanded = config.previousExpanded, subtotalRow = config.subtotalRow, hasSummaryData = config.hasSummaryData, items = matrix.leftAxis.dimensions.items, len = items.length, k = len, found = false, i, column; me.storeInfo[record.internalId] = { leftKey: group.key, rowStyle: '', rowClasses: [me.getClsGroupTotal()], rowEvent: me.groupRowEvent, rendererParams: {} }; for (i = 0; i < len; i++) { column = items[i]; if (column.id === group.dimension.id) { me.storeInfo[record.internalId].rendererParams[column.id] = { fn: 'groupOutlineRenderer', group: group, colspan: k, hidden: false, previousExpanded: previousExpanded, subtotalRow: subtotalRow }; found = true; } else { me.storeInfo[record.internalId].rendererParams[column.id] = { fn: 'groupOutlineRenderer', group: group, colspan: 0, hidden: found, previousExpanded: previousExpanded, subtotalRow: subtotalRow }; k--; } } // for all top axis columns use a new renderer me.storeInfo[record.internalId].rendererParams.topaxis = { fn: (hasSummaryData ? 'topAxisRenderer' : 'topAxisNoRenderer'), group: group }; config.results.push(record); }, processRecordOutline: function(config) { var me = this, group = config.group, found = false, record = group.record, items = me.getMatrix().leftAxis.dimensions.items, len = items.length, i, column; me.storeInfo[record.internalId] = { leftKey: group.key, rowStyle: '', rowClasses: [], rowEvent: me.itemRowEvent, rendererParams: {} }; for (i = 0; i < len; i++) { column = items[i]; if (column.id === group.dimension.id) { found = true; } me.storeInfo[record.internalId].rendererParams[column.id] = { fn: 'recordOutlineRenderer', group: group, hidden: !found }; } // for all top axis columns use a new renderer me.storeInfo[record.internalId].rendererParams.topaxis = { fn: 'topAxisRenderer', group: group }; config.results.push(record); }, // Compact view functions processGroupCompact: function(config) { var me = this, group = config.group, previousExpanded = config.previousExpanded, results = []; if (group.record) { me.processRecordCompact({ results: results, group: group }); } else { me.processGroupCompactWithChildren({ results: results, group: group, previousExpanded: previousExpanded }); } return results; }, processGroupCompactWithChildren: function(config) { var me = this, matrix = me.getMatrix(), group = config.group, previousExpanded = config.previousExpanded, hasSummaryData, i, len; hasSummaryData = (!group.expanded || (group.expanded && matrix.rowSubTotalsPosition === 'first')); me.processGroupHeaderRecordCompact({ results: config.results, group: group, record: group.expanded ? group.records.expanded : group.records.collapsed, previousExpanded: previousExpanded, hasSummaryData: hasSummaryData }); if (group.expanded) { if (group.children) { len = group.children.length; for (i = 0; i < len; i++) { if (group.children[i].children) { me.processGroupCompactWithChildren({ results: config.results, group: group.children[i] }); } else { me.processRecordCompact({ results: config.results, group: group.children[i] }); } } } if (matrix.rowSubTotalsPosition === 'last') { me.processGroupHeaderRecordCompact({ results: config.results, group: group, record: group.records.footer, previousExpanded: previousExpanded, subtotalRow: true, hasSummaryData: true }); } } }, processGroupHeaderRecordCompact: function(config) { var me = this, matrix = me.getMatrix(), group = config.group, record = config.record, previousExpanded = config.previousExpanded, subtotalRow = config.subtotalRow, hasSummaryData = config.hasSummaryData; me.storeInfo[record.internalId] = { leftKey: group.key, rowStyle: '', rowClasses: [me.getClsGroupTotal()], rowEvent: me.groupRowEvent, rendererParams: {} }; me.storeInfo[record.internalId].rendererParams[matrix.compactViewKey] = { fn: 'groupCompactRenderer', group: group, colspan: 0, previousExpanded: previousExpanded, subtotalRow: subtotalRow }; // for all top axis columns use a new renderer me.storeInfo[record.internalId].rendererParams.topaxis = { fn: (hasSummaryData ? 'topAxisRenderer' : 'topAxisNoRenderer'), group: group }; config.results.push(record); }, processRecordCompact: function(config) { var me = this, group = config.group, record = group.record; me.storeInfo[record.internalId] = { leftKey: group.key, rowStyle: '', rowClasses: [], rowEvent: me.itemRowEvent, rendererParams: {} }; me.storeInfo[record.internalId].rendererParams[me.getMatrix().compactViewKey] = { fn: 'recordCompactRenderer', group: group }; // for all top axis columns use a new renderer me.storeInfo[record.internalId].rendererParams.topaxis = { fn: 'topAxisRenderer', group: group }; config.results.push(record); }, // Tabular view functions processGroupTabular: function(config) { var me = this, group = config.group, results = []; if (group.record) { me.processRecordTabular({ results: results, group: group }); } else { me.processGroupTabularWithChildren({ results: results, group: group, previousExpanded: config.previousExpanded }); } return results; }, processGroupTabularWithChildren: function(config, noFirstRecord) { var me = this, matrix = me.getMatrix(), group = config.group, previousExpanded = config.previousExpanded, record, i, child, len; if (!noFirstRecord) { me.processGroupHeaderRecordTabular({ results: config.results, group: group, previousExpanded: previousExpanded, hasSummaryData: !group.expanded }); } if (group.expanded) { if (group.children) { len = group.children.length; for (i = 0; i < len; i++) { child = group.children[i]; if (i === 0 && child.children && child.expanded) { me.processGroupTabularWithChildren({ results: config.results, group: child }, true); } else if (i > 0) { if (child.children) { me.processGroupTabularWithChildren({ results: config.results, group: child }); } else { me.processRecordTabular({ results: config.results, group: child }); } } } } if (matrix.rowSubTotalsPosition !== 'none') { record = group.records.footer; me.processGroupHeaderRecordTabular({ results: config.results, group: group, record: record, previousExpanded: previousExpanded, subtotalRow: true, hasSummaryData: true }); } } }, // used in PivotEvents getTabularGroupRecord: function(group) { var record = group.record; if (!record) { if (!group.expanded) { record = group.records.collapsed; } else { record = this.getTabularGroupRecord(group.children[0]); } } return record; }, processGroupHeaderRecordTabular: function(config) { var me = this, matrix = me.getMatrix(), group = config.group, record = config.record, previousExpanded = config.previousExpanded, subtotalRow = config.subtotalRow, hasSummaryData = config.hasSummaryData, dimensions = matrix.leftAxis.dimensions, len = dimensions.length, k = len, found = false, rendererParams = {}, rowClasses = [], rowEvent = me.itemRowEvent, i, dim, item, keys, parentGroup, prevGroup; if (!record) { item = group; record = item.record; while (!record) { rendererParams[item.dimension.id] = { fn: 'groupTabularRenderer', group: item, colspan: 0, hidden: false, previousExpanded: previousExpanded, subtotalRow: subtotalRow }; if (item.children) { if (!item.expanded) { record = item.records.collapsed; rendererParams[item.dimension.id].colspan = len - item.level; } else { item = item.children[0]; } } else { record = item.record; } } // Ext.apply(record.data, item.data); found = false; for (i = 0; i < len; i++) { dim = matrix.leftAxis.dimensions.items[i]; if (rendererParams[dim.id]) { record.data[dim.id] = rendererParams[dim.id].group.name; found = true; } else if (found) { rendererParams[dim.id] = { fn: 'groupTabularRenderer', group: group, colspan: 0, hidden: true, previousExpanded: previousExpanded, subtotalRow: subtotalRow }; } else { record.data[dim.id] = ''; } } if (group.level > 0) { prevGroup = group; keys = prevGroup.key.split(matrix.keysSeparator); keys.length--; parentGroup = matrix.leftAxis.items.getByKey(keys.join(matrix.keysSeparator)); while (parentGroup && parentGroup.children[0] === prevGroup) { rendererParams[parentGroup.dimension.id] = { fn: 'groupTabularRenderer', group: parentGroup, colspan: 0, hidden: false, previousExpanded: previousExpanded, subtotalRow: subtotalRow }; record.data[parentGroup.dimension.id] = parentGroup.name; prevGroup = parentGroup; keys = prevGroup.key.split(matrix.keysSeparator); keys.length--; parentGroup = matrix.leftAxis.items.getByKey(keys.join(matrix.keysSeparator)); } } } else { for (i = 0; i < len; i++) { dim = matrix.leftAxis.dimensions.items[i]; rendererParams[dim.id] = { fn: 'groupTabularRenderer', group: group, colspan: 0, hidden: false, previousExpanded: previousExpanded, subtotalRow: subtotalRow }; if (dim.id === group.dimension.id) { rendererParams[dim.id].colspan = k; found = true; } else { rendererParams[dim.id].hidden = found; k--; } } item = group; } if (hasSummaryData) { rowClasses.push(me.getClsGroupTotal()); rowEvent = me.groupRowEvent; } me.storeInfo[record.internalId] = { leftKey: group.key, rowStyle: '', rowClasses: rowClasses, rowEvent: rowEvent, rendererParams: rendererParams }; // for all top axis columns use a new renderer me.storeInfo[record.internalId].rendererParams.topaxis = { fn: 'topAxisRenderer', group: item }; config.results.push(record); }, processRecordTabular: function(config) { var me = this, group = config.group, found = false, record = group.record, items = me.getMatrix().leftAxis.dimensions.items, len = items.length, i, column; me.storeInfo[record.internalId] = { leftKey: group.key, rowStyle: '', rowClasses: [], rowEvent: me.itemRowEvent, rendererParams: {} }; for (i = 0; i < len; i++) { column = items[i]; if (column.id === group.dimension.id) { found = true; } else { record.data[column.id] = null; } me.storeInfo[record.internalId].rendererParams[column.id] = { fn: 'recordTabularRenderer', group: group, hidden: !found }; } // for all top axis columns use a new renderer me.storeInfo[record.internalId].rendererParams.topaxis = { fn: 'topAxisRenderer', group: group }; config.results.push(record); }, // various functions doExpandCollapse: function(key, oldRecord) { var me = this, gridMaster = me.getGrid(), group; group = me.getMatrix().leftAxis.findTreeElement('key', key); if (!group) { return; } me.doExpandCollapseInternal(group.node, oldRecord); gridMaster.fireEvent( (group.node.expanded ? 'pivotgroupexpand' : 'pivotgroupcollapse'), gridMaster, 'row', group.node ); }, doExpandCollapseInternal: function(group, oldRecord) { var me = this, store = me.getStore(), isClassic = Ext.isClassic, items, oldItems, startIdx; group.expanded = !group.expanded; oldItems = me.processGroup({ group: group, previousExpanded: false }); group.expanded = !group.expanded; items = me.processGroup({ group: group, previousExpanded: false }); if (items.length && oldItems.length && (startIdx = store.indexOf(oldItems[0])) !== -1) { store.suspendEvents(); if (group.expanded) { store.remove(store.getAt(startIdx)); store.insert(startIdx, items); } else { store.remove(oldItems); store.insert(startIdx, items); } me.removeStoreInfoData(oldItems); store.resumeEvents(); if (isClassic) { // For Classic, the replace event is better than remove and inserts store.fireEvent('replace', store, startIdx, oldItems, items); } else { // For Modern, the refresh event is better than remove and inserts store.fireEvent('refresh', store); } } }, removeStoreInfoData: function(records) { var len = records.length, record, i; for (i = 0; i < len; i++) { record = records[i]; if (this.storeInfo[record.internalId]) { delete this.storeInfo[record.internalId]; } } }, onGroupExpand: function(matrix, type, item) { if (type === 'row') { if (item) { this.doExpandCollapseInternal(item, item.records.collapsed); } else { this.processStore(); } } }, onGroupCollapse: function(matrix, type, item) { if (type === 'row') { if (item) { this.doExpandCollapseInternal(item, item.records.expanded); } else { this.processStore(); } } }});