/** * This class implements the configurator panel. */Ext.define('Ext.pivot.plugin.configurator.Panel', { extend: 'Ext.panel.Panel', requires: [ 'Ext.pivot.plugin.configurator.Container', 'Ext.pivot.plugin.configurator.DragZone', 'Ext.pivot.plugin.configurator.DropZone', 'Ext.layout.container.HBox', 'Ext.layout.container.VBox', 'Ext.pivot.plugin.configurator.window.Settings' ], mixins: [ 'Ext.mixin.FocusableContainer' ], alias: 'widget.pivotconfigpanel', // the column header container has a weight of 100 so we want to dock it before that. weight: 50, defaultMinHeight: 70, defaultMinWidth: 250, headerWidth: 100, dock: 'right', header: false, title: 'Configurator', collapsible: true, collapseMode: 'placeholder', /** * @cfg {String} panelAllFieldsText Text displayed in the container reserved for all available * fields when docked to top or bottom. */ panelAllFieldsText: 'Drop Unused Fields Here', /** * @cfg {String} panelAllFieldsTitle Text displayed in the container reserved for all available * fields when docked to left or right. */ panelAllFieldsTitle: 'All fields', /** * @cfg {String} panelTopFieldsText Text displayed in the container reserved for all top axis * fields when docked to top or bottom. */ panelTopFieldsText: 'Drop Column Fields Here', /** * @cfg {String} panelTopFieldsTitle Text displayed in the container reserved for all top axis * fields when docked to left or right. */ panelTopFieldsTitle: 'Column labels', /** * @cfg {String} panelLeftFieldsText Text displayed in the container reserved for all left axis * fields when docked to top or bottom. */ panelLeftFieldsText: 'Drop Row Fields Here', /** * @cfg {String} panelLeftFieldsTitle Text displayed in the container reserved for all left axis * fields when docked to left or right. */ panelLeftFieldsTitle: 'Row labels', /** * @cfg {String} panelAggFieldsText Text displayed in the container reserved for all aggregate * fields when docked to top or bottom. */ panelAggFieldsText: 'Drop Agg Fields Here', /** * @cfg {String} panelAggFieldsTitle Text displayed in the container reserved for all aggregate * fields when docked to left or right. */ panelAggFieldsTitle: 'Values', /** * @cfg {String} addToText Text displayed in the field menu */ addToText: 'Add to {0}', /** * @cfg {String} moveToText Text displayed in the field menu */ moveToText: 'Move to {0}', /** * @cfg {String} removeFieldText Text displayed in the field menu */ removeFieldText: 'Remove field', /** * @cfg {String} moveUpText Text displayed in the field menu */ moveUpText: 'Move up', /** * @cfg {String} moveDownText Text displayed in the field menu */ moveDownText: 'Move down', /** * @cfg {String} moveBeginText Text displayed in the field menu */ moveBeginText: 'Move to beginning', /** * @cfg {String} moveEndText Text displayed in the field menu */ moveEndText: 'Move to end', /** * @cfg {String} fieldSettingsText Text displayed in the field menu */ fieldSettingsText: 'Field settings', headerContainerCls: Ext.baseCSSPrefix + 'pivot-grid-config-container-header', keyEventRe: /^key/, config: { fields: [], refreshDelay: 300, pivot: null }, initComponent: function() { var me = this, listeners = { configchange: me.applyChanges, scope: me }; Ext.apply( me, Ext.Array.indexOf(['top', 'bottom'], me.dock) >= 0 ? me.getHorizontalConfig() : me.getVerticalConfig() ); me.callParent(arguments); me.getAllFieldsContainer().on(listeners); me.getLeftAxisContainer().on(listeners); me.getTopAxisContainer().on(listeners); me.getAggregateContainer().on(listeners); me.pivotListeners = me.getPivot().getMatrix().on({ done: me.onPivotDone, scope: me, destroyable: true }); me.task = new Ext.util.DelayedTask(me.reconfigurePivot, me); }, destroy: function() { var me = this, toDestroy = [ 'relayers', 'pivotListeners', 'menu', 'dragZone', 'dropZone' ], length = toDestroy.length, i; for (i = 0; i < length; i++) { Ext.destroy(me[toDestroy[i]]); me[toDestroy[i]] = null; } me.task.cancel(); me.task = me.lastFocusedField = null; me.callParent(); }, enable: function() { var me = this; if (me.rendered) { me.dragZone.enable(); me.dropZone.enable(); me.initPivotFields(); } me.show(); }, disable: function() { var me = this; if (me.rendered) { me.dragZone.disable(); me.dropZone.disable(); } me.hide(); }, afterRender: function() { var me = this, el = me.getEl(); me.callParent(arguments); me.mon(el, { scope: me, delegate: '.' + Ext.baseCSSPrefix + 'pivot-grid-config-column', click: me.handleEvent, keypress: me.handleEvent }); me.dragZone = new Ext.pivot.plugin.configurator.DragZone(me); me.dropZone = new Ext.pivot.plugin.configurator.DropZone(me); el.unselectable(); }, handleEvent: function(e) { var me = this, isKeyEvent = me.keyEventRe.test(e.type), pivot = me.getPivot(), fly, cmp, menuCfg, options; if ((isKeyEvent && e.getKey() === e.SPACE) || (e.button === 0)) { fly = Ext.fly(e.target); if (fly && (cmp = fly.component)) { e.stopEvent(); cmp.focus(); Ext.destroy(me.menu); menuCfg = me.getMenuConfig(cmp); if (menuCfg) { me.menu = new Ext.menu.Menu(menuCfg); options = { menu: me.menu, field: cmp.getField(), container: cmp.getFieldType() }; if (pivot.fireEvent('beforeshowconfigfieldmenu', me, options) !== false) { me.menu.showBy(cmp); me.menu.focus(); pivot.fireEvent('showconfigfieldmenu', me, options); } else { Ext.destroy(me.menu); } } } } }, getPanelConfigHeader: function(config) { return Ext.apply({ xtype: 'header', // make it look like a panel header but with a different padding baseCls: Ext.baseCSSPrefix + 'panel-header', cls: this.headerContainerCls, border: 1, width: this.headerWidth }, config || {}); }, getHorizontalConfig: function() { var me = this, tools = [{ type: 'gear', handler: me.showSettings, scope: me }]; if (me.collapsible) { tools.push({ type: me.dock === 'top' ? 'up' : 'down', handler: me.collapseMe, scope: me }); } return { minHeight: me.defaultMinHeight, headerPosition: me.dock === 'top' ? 'bottom' : 'top', collapseDirection: me.dock, defaults: { xtype: 'container', layout: { type: 'hbox', align: 'stretchmax' }, minHeight: me.defaultMinHeight / 3 }, items: [{ items: [me.getPanelConfigHeader({ title: me.panelAllFieldsTitle, tools: tools }), { itemId: 'fieldsCt', xtype: 'pivotconfigcontainer', fieldType: 'all', dragDropText: me.panelAllFieldsText, position: me.dock, flex: 1 }] }, { items: [me.getPanelConfigHeader({ title: me.panelAggFieldsTitle }), { itemId: 'fieldsAggCt', xtype: 'pivotconfigcontainer', fieldType: 'aggregate', dragDropText: me.panelAggFieldsText, position: me.dock, flex: 1 }] }, { defaults: { xtype: 'pivotconfigcontainer', minHeight: me.defaultMinHeight / 3, position: me.dock }, items: [me.getPanelConfigHeader({ title: me.panelLeftFieldsTitle }), { itemId: 'fieldsLeftCt', fieldType: 'leftAxis', dragDropText: me.panelLeftFieldsText, flex: 1 }, me.getPanelConfigHeader({ title: me.panelTopFieldsTitle }), { itemId: 'fieldsTopCt', fieldType: 'topAxis', dragDropText: me.panelTopFieldsText, flex: 1 }] }] }; }, getVerticalConfig: function() { var me = this, tools = [{ type: 'gear', handler: me.showSettings, scope: me }]; if (me.collapsible) { tools.push({ type: me.dock, handler: me.collapseMe, scope: me }); } return { layout: { type: 'hbox', align: 'stretch' }, width: me.defaultMinWidth, minWidth: me.defaultMinWidth, headerPosition: me.dock === 'right' ? 'left' : 'right', collapseDirection: me.dock, defaults: { flex: 1 }, items: [{ itemId: 'fieldsCt', xtype: 'pivotconfigcontainer', position: me.dock, title: me.panelAllFieldsTitle, fieldType: 'all', dragDropText: me.panelAllFieldsText, autoScroll: true, header: { cls: me.headerContainerCls }, tools: tools }, { xtype: 'container', defaults: { xtype: 'pivotconfigcontainer', flex: 1, autoScroll: true, position: me.dock, header: { cls: me.headerContainerCls } }, layout: { type: 'vbox', align: 'stretch' }, items: [{ itemId: 'fieldsAggCt', title: me.panelAggFieldsTitle, fieldType: 'aggregate', dragDropText: me.panelAggFieldsText }, { itemId: 'fieldsLeftCt', title: me.panelLeftFieldsTitle, fieldType: 'leftAxis', dragDropText: me.panelLeftFieldsText }, { itemId: 'fieldsTopCt', title: me.panelTopFieldsTitle, fieldType: 'topAxis', dragDropText: me.panelTopFieldsText }] }] }; }, /** * Returns the container that stores all unused fields. * * @return {Ext.pivot.plugin.configurator.Container} */ getAllFieldsContainer: function() { return this.down('#fieldsCt'); }, /** * Returns the header of the container that stores all unused fields. * * @return {Ext.panel.Header} */ getAllFieldsHeader: function() { var dock = this.dock, ct = this.getAllFieldsContainer(); return (dock === 'top' || dock === 'bottom') ? ct.prev() : ct.getHeader(); }, /** * Set visibility of the "All fields" header and container * @param {Boolean} visible */ setAllFieldsContainerVisible: function(visible) { this.getAllFieldsContainer().setVisible(visible); this.getAllFieldsHeader().setVisible(visible); }, /** * Returns the container that stores all fields configured on the left axis. * * @return {Ext.pivot.plugin.configurator.Container} */ getLeftAxisContainer: function() { return this.down('#fieldsLeftCt'); }, /** * Returns the header of the container that stores all fields configured on the left axis. * * @return {Ext.panel.Header} */ getLeftAxisHeader: function() { var dock = this.dock, ct = this.getLeftAxisContainer(); return (dock === 'top' || dock === 'bottom') ? ct.prev() : ct.getHeader(); }, /** * Set visibility of the "Row labels" header and container * @param {Boolean} visible */ setLeftAxisContainerVisible: function(visible) { this.getLeftAxisContainer().setVisible(visible); this.getLeftAxisHeader().setVisible(visible); }, /** * Returns the container that stores all fields configured on the top axis. * * @return {Ext.pivot.plugin.configurator.Container} */ getTopAxisContainer: function() { return this.down('#fieldsTopCt'); }, /** * Returns the header of the container that stores all fields configured on the top axis. * * @return {Ext.panel.Header} */ getTopAxisHeader: function() { var dock = this.dock, ct = this.getTopAxisContainer(); return (dock === 'top' || dock === 'bottom') ? ct.prev() : ct.getHeader(); }, /** * Set visibility of the "Column labels" header and container * @param {Boolean} visible */ setTopAxisContainerVisible: function(visible) { this.getTopAxisContainer().setVisible(visible); this.getTopAxisHeader().setVisible(visible); }, /** * Returns the container that stores all fields configured on the aggregate. * * @return {Ext.pivot.plugin.configurator.Container} */ getAggregateContainer: function() { return this.down('#fieldsAggCt'); }, /** * Returns the header of the container that stores all fields configured on the aggregate. * * @return {Ext.panel.Header} */ getAggregateHeader: function() { var dock = this.dock, ct = this.getAggregateContainer(); return (dock === 'top' || dock === 'bottom') ? ct.prev() : ct.getHeader(); }, /** * Set visibility of the "Values" header and container * @param {Boolean} visible */ setAggregateContainerVisible: function(visible) { this.getAggregateContainer().setVisible(visible); this.getAggregateHeader().setVisible(visible); }, /** * Apply configurator changes to the pivot component. * * This function will trigger the delayed task which is actually reconfiguring * the pivot component with the new configuration. * */ applyChanges: function(field) { var me = this; if (me.disabled) { // if the plugin is disabled don't do anything return; } if (field) { me.lastFocusedField = field; } me.task.delay(me.getRefreshDelay()); }, collapseMe: function() { this.collapse(this.dock); }, showSettings: function() { var pivot = this.getPivot(), win = new Ext.pivot.plugin.configurator.window.Settings({ listeners: { applysettings: Ext.bind(this.applyPivotSettings, this) } }), settings = pivot.getMatrix().serialize(); delete(settings.leftAxis); delete(settings.topAxis); delete(settings.aggregate); if (pivot.fireEvent('beforeshowpivotsettings', this, { container: win, settings: settings }) !== false) { win.loadSettings(settings); win.show(); pivot.fireEvent('showpivotsettings', this, { container: win, settings: settings }); } else { Ext.destroy(win); } }, applyPivotSettings: function(win, settings) { var pivot = this.getPivot(); if (pivot.fireEvent('beforeapplypivotsettings', this, { container: win, settings: settings }) !== false) { pivot.fireEvent('applypivotsettings', this, { container: win, settings: settings }); pivot.getMatrix().reconfigure(settings); } else { return false; } }, /** * This function is used to retrieve all configured fields in a fields container. * * @private */ getFieldsFromContainer: function(ct, justConfigs) { var fields = [], len = ct.items.getCount(), i, item; for (i = 0; i < len; i++) { item = ct.items.getAt(i); fields.push( justConfigs === true ? item.getField().getConfiguration() : item.getField() ); } return fields; }, /** * Initialize all container fields fetching the configuration from the pivot grid. * * @private */ initPivotFields: function() { var me = this, matrix = me.getPivot().getMatrix(), fieldsAllCt = me.getAllFieldsContainer(), fieldsLeftCt = me.getLeftAxisContainer(), fieldsTopCt = me.getTopAxisContainer(), fieldsAggCt = me.getAggregateContainer(), fieldsTop, fieldsLeft, fieldsAgg, fields; fields = me.getFields().clone(); Ext.suspendLayouts(); // remove all previously created columns fieldsAllCt.removeAll(); fieldsTopCt.removeAll(); fieldsLeftCt.removeAll(); fieldsAggCt.removeAll(); fieldsTop = me.getConfigFields(matrix.topAxis.dimensions.getRange()); fieldsLeft = me.getConfigFields(matrix.leftAxis.dimensions.getRange()); fieldsAgg = me.getConfigFields(matrix.aggregate.getRange()); // the "All fields" will always contain all available fields (both defined on the plugin // and existing in the matrix configuration) me.addFieldsToConfigurator(fields.getRange(), fieldsAllCt); me.addFieldsToConfigurator(fieldsTop, fieldsTopCt); me.addFieldsToConfigurator(fieldsLeft, fieldsLeftCt); me.addFieldsToConfigurator(fieldsAgg, fieldsAggCt); Ext.resumeLayouts(true); }, /** * Easy function for assigning fields to a container. * * @private */ addFieldsToConfigurator: function(fields, fieldsCt) { var len = fields.length, i; for (i = 0; i < len; i++) { fieldsCt.addField(fields[i], -1); } }, /** * Build the fields array for each container by parsing all given fields or from the pivot * config. * * @private */ getConfigFields: function(items) { var len = items.length, fields = this.getFields(), list = [], i, field, item; for (i = 0; i < len; i++) { item = items[i]; field = fields.byDataIndex.get(item.dataIndex); if (field) { // we need to clone the field that includes all constraints // and apply the configs from the original field field = field.clone(); field.setConfig(item.getInitialConfig()); list.push(field); } } return list; }, getMenuConfig: function(field) { var me = this, fieldType = field.getFieldType(), items = [], menu = field.getMenuConfig() || {}, container = field.up('pivotconfigcontainer'), siblings = container.items.getCount(), fieldIdx = container.items.indexOf(field), dimension = field.getField(), settings = dimension.getSettings(), fieldsLeftCt = me.getLeftAxisContainer(), fieldsTopCt = me.getTopAxisContainer(), fieldsAggCt = me.getAggregateContainer(), titleLeft = me.getLeftAxisHeader().getTitle().getText(), titleTop = me.getTopAxisHeader().getTitle().getText(), titleAgg = me.getAggregateHeader().getTitle().getText(); menu.items = menu.items || []; if (fieldType === 'all') { items.push({ text: Ext.String.format(me.addToText, titleLeft), disabled: !settings.isAllowed(fieldsLeftCt), handler: Ext.bind(me.dragDropField, me, [fieldsLeftCt, field, 'after']), hidden: fieldsLeftCt.isHidden() }, { text: Ext.String.format(me.addToText, titleTop), disabled: !settings.isAllowed(fieldsTopCt), handler: Ext.bind(me.dragDropField, me, [fieldsTopCt, field, 'after']), hidden: fieldsTopCt.isHidden() }, { text: Ext.String.format(me.addToText, titleAgg), disabled: !settings.isAllowed(fieldsAggCt), handler: Ext.bind(me.dragDropField, me, [fieldsAggCt, field, 'after']), hidden: fieldsAggCt.isHidden() }); } else { items.push({ text: me.moveUpText, disabled: (siblings === 1 || fieldIdx === 0), handler: Ext.bind(me.dragDropField, me, [field.previousSibling(), field, 'before']) }, { text: me.moveDownText, disabled: (siblings === 1 || fieldIdx === siblings - 1), handler: Ext.bind(me.dragDropField, me, [field.nextSibling(), field, 'after']) }, { text: me.moveBeginText, disabled: (siblings === 1 || fieldIdx === 0), handler: Ext.bind(me.dragDropField, me, [container.items.first(), field, 'before']) }, { text: me.moveEndText, disabled: (siblings === 1 || fieldIdx === siblings - 1), handler: Ext.bind(me.dragDropField, me, [container.items.last(), field, 'after']) }, { xtype: 'menuseparator' }, { text: Ext.String.format(me.moveToText, me.panelLeftFieldsTitle), disabled: (fieldType === 'leftAxis' || !settings.isAllowed(fieldsLeftCt) || settings.isFixed(container)), handler: Ext.bind(me.dragDropField, me, [fieldsLeftCt, field, 'after']) }, { text: Ext.String.format(me.moveToText, me.panelTopFieldsTitle), disabled: (fieldType === 'topAxis' || !settings.isAllowed(fieldsTopCt) || settings.isFixed(container)), handler: Ext.bind(me.dragDropField, me, [fieldsTopCt, field, 'after']) }, { text: Ext.String.format(me.moveToText, me.panelAggFieldsTitle), disabled: (fieldType === 'aggregate' || !settings.isAllowed(fieldsAggCt) || settings.isFixed(container)), handler: Ext.bind(me.dragDropField, me, [fieldsAggCt, field, 'after']) }, { xtype: 'menuseparator' }, { text: me.removeFieldText, disabled: settings.isFixed(container), handler: Ext.bind( me.dragDropField, me, [me.getAllFieldsContainer(), field, 'after'] ) }); } if (fieldType === 'aggregate') { items.push({ xtype: 'menuseparator' }, { text: me.fieldSettingsText, handler: Ext.bind(me.openFieldSettings, me, [field]) }); } if (menu.items.length) { items.push({ xtype: 'menuseparator' }); } Ext.Array.insert(menu.items, 0, items); return Ext.apply({ ownerCmp: me, floating: true }, menu); }, openFieldSettings: function(field) { var pivot = this.getPivot(), win = new Ext.pivot.plugin.configurator.window.FieldSettings({ field: field.getField(), listeners: { applysettings: Ext.bind(this.applyFieldSettings, this, [field], 0) } }), settings = field.getField().getConfig(); if (pivot.fireEvent('beforeshowconfigfieldsettings', this, { container: win, settings: settings }) !== false) { win.loadSettings(settings); win.show(); pivot.fireEvent('showconfigfieldsettings', this, { container: win, settings: settings }); } else { Ext.destroy(win); } }, applyFieldSettings: function(field, win, settings) { var pivot = this.getPivot(), fieldCfg = field.getField(); if (pivot.fireEvent('beforeapplyconfigfieldsettings', this, { container: win, settings: settings }) !== false) { fieldCfg.setConfig(settings || {}); if (field.rendered) { field.textCol.setHtml(fieldCfg.getFieldText()); field.textCol.dom.setAttribute('data-qtip', fieldCfg.getFieldText()); } pivot.fireEvent('applyconfigfieldsettings', this, { container: win, settings: settings }); this.applyChanges(field); } else { return false; } }, /* eslint-disable max-len */ /** * This function either moves or copies the dragged field from one container to another. * * @param {Ext.pivot.plugin.configurator.Container/Ext.pivot.plugin.configurator.Column} toTarget * @param {Ext.pivot.plugin.configurator.Column} column * @param {String} pos Position: `after` or `before` * * @private */ dragDropField: function(toTarget, column, pos) { /* eslint-enable max-len */ var me = this, pivot = me.getPivot(), field = column.getField(), fromContainer = column.ownerCt, toContainer = toTarget.isConfiguratorContainer ? toTarget : toTarget.ownerCt, toField = toTarget.isConfiguratorField ? toTarget : toTarget.items.last(), fromFieldType = fromContainer.getFieldType(), toFieldType = toContainer.getFieldType(), topAxisCt = me.getTopAxisContainer(), leftAxisCt = me.getLeftAxisContainer(), newPos, item, toFocus; if (pivot.fireEvent('beforemoveconfigfield', this, { fromContainer: fromContainer, toContainer: toContainer, field: field }) !== false) { if (fromContainer !== toContainer) { if (toField) { newPos = toContainer.items.findIndex('id', toField.id); newPos = (pos === 'before') ? newPos : newPos + 1; } else { newPos = -1; } if (toFieldType === 'all') { // source is "Row labels"/"Column labels"/"Values" // destination is "All fields" // we just remove the field from the source toField.focus(); fromContainer.removeField(column); } else if (toFieldType === 'aggregate') { // source is "Row labels"/"Column labels"/"All fields" // destination is "Values" // we copy the field to destination toFocus = toContainer.addField(field.clone(), newPos, true); if (fromFieldType !== 'all') { // remove the field from the left/top axis fromContainer.remove(column); } } else { // source is "Row labels"/"Column labels"/"Values"/"All fields" // destination is "Row labels"/"Column labels" // first let's check if the field is already in the destination container item = me.findFieldInContainer(field, toContainer); if (item) { // the destination has the field already return; } // See if it was on another axis. if (toFieldType === 'leftAxis') { item = me.findFieldInContainer(field, topAxisCt); } else { item = me.findFieldInContainer(field, leftAxisCt); } // If so, move it here. if (item) { toContainer.add(item); return me.applyChanges(item); } else { if (fromFieldType === 'aggregate') { // we need to remove the dragged field because it was found // on one of the axis fromContainer.remove(column); } toFocus = toContainer.addField(field.clone(), newPos, true); } } } else { toContainer.moveField(column.id, toField.id, pos); } // Ensure that any removal does not allow focus to escape to the body if (toFocus) { toFocus.focus(); toFocus.el.resumeFocusEvents(); } } }, isAllowed: function(toTarget, column) { var allowed = true, field = column.getField(), fromContainer = column.ownerCt, toContainer = toTarget.isConfiguratorContainer ? toTarget : toTarget.ownerCt, fromFieldType = fromContainer.getFieldType(), toFieldType = toContainer.getFieldType(); if (fromFieldType === 'aggregate' && (toFieldType === 'leftAxis' || toFieldType === 'topAxis')) { allowed = !this.findFieldInContainer(field, toContainer); } return allowed; }, /** * * @param {Ext.pivot.plugin.configurator.Field} field * @param {Ext.pivot.plugin.configurator.Container} container * @returns {Ext.pivot.plugin.configurator.Column} * * @private */ findFieldInContainer: function(field, container) { var length = container.items.getCount(), i, item; for (i = 0; i < length; i++) { item = container.items.getAt(i); if (item.getField().getDataIndex() === field.getDataIndex()) { return item; } } }, /** * Listener for the 'pivotdone' event. Initialize configurator fields or restore last field * focus. * * @private */ onPivotDone: function() { var me = this, field = me.lastFocusedField; if (me.internalReconfiguration) { me.internalReconfiguration = false; // restore focus if (field && field.isConfiguratorContainer) { field = field.items.first(); } if (!field) { field = me.getAllFieldsContainer().items.first(); } if (field) { field.focus(); } else { me.getPivot().focus(); } } else { me.initPivotFields(); } }, /** * Collect configurator changes and reconfigure the pivot component * * @private */ reconfigurePivot: function() { var me = this, pivot = me.getPivot(), obj = { topAxis: me.getFieldsFromContainer(me.getTopAxisContainer(), true), leftAxis: me.getFieldsFromContainer(me.getLeftAxisContainer(), true), aggregate: me.getFieldsFromContainer(me.getAggregateContainer(), true) }; me.internalReconfiguration = true; if (pivot.fireEvent('beforeconfigchange', me, obj) !== false) { pivot.getMatrix().reconfigure(obj); pivot.fireEvent('configchange', me, obj); } }, /** * This function is temporarily added here until the placeholder expanding/collpasing * is fixed for docked panels. * * @param direction * @param animate * @returns {Ext.pivot.plugin.configurator.Panel} * @private */ placeholderCollapse: function(direction, animate) { var me = this, ownerCt = me.ownerCt, collapseDir = direction || me.collapseDirection, floatCls = Ext.panel.Panel.floatCls, placeholder = me.getPlaceholder(collapseDir), slideInDirection; me.isCollapsingOrExpanding = 1; // Upcoming layout run will ignore this Component me.setHiddenState(true); me.collapsed = collapseDir; if (placeholder.rendered) { // We may have been added to another Container from that in which we rendered // the placeholder if (placeholder.el.dom.parentNode !== me.el.dom.parentNode) { me.el.dom.parentNode.insertBefore(placeholder.el.dom, me.el.dom); } placeholder.hidden = false; placeholder.setHiddenState(false); placeholder.el.show(); ownerCt.updateLayout(); } else { // ATE - this is the fix if (me.dock) { placeholder.dock = me.dock; ownerCt.addDocked(placeholder); } else { ownerCt.insert(ownerCt.items.indexOf(me), placeholder); } } if (me.rendered) { // We assume that if collapse was caused by keyboard action // on focused collapse tool, the logical focus transition // is to placeholder's expand tool. Note that it may not be // the case when the user *clicked* collapse tool while focus // was elsewhere; in that case we dare not touch focus // to avoid sudden jumps. if (Ext.ComponentManager.getActiveComponent() === me.collapseTool) { me.focusPlaceholderExpandTool = true; } // We MUST NOT hide using display because that resets all scroll information. me.el.setVisibilityMode(me.placeholderCollapseHideMode); if (animate) { me.el.addCls(floatCls); placeholder.el.hide(); slideInDirection = me.convertCollapseDir(collapseDir); me.el.slideOut(slideInDirection, { preserveScroll: true, duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration), listeners: { scope: me, afteranimate: function() { var me = this; me.el.removeCls(floatCls); /* We need to show the element so that slideIn will work correctly. * However, if we leave it visible then it can be seen before * the animation starts, causing a flicker. The solution, * borrowed from date picker, is to hide it using display:none. * The slideIn effect includes a call to fixDisplay() that will * undo the display none at the appropriate time. */ me.placeholder.el.show().setStyle('display', 'none').slideIn( slideInDirection, { easing: 'linear', duration: 100, listeners: { afteranimate: me.doPlaceholderCollapse, scope: me } } ); } } }); } else { me.el.hide(); me.doPlaceholderCollapse(); } } else { me.isCollapsingOrExpanding = 0; if (!me.preventCollapseFire) { me.fireEvent('collapse', me); } } return me; }, /** * This function is temporarily added here until the placeholder expanding/collpasing * is fixed for docked panels. * * @param animate * @returns {Ext.pivot.plugin.configurator.Panel} * @private */ placeholderExpand: function(animate) { var me = this, collapseDir = me.collapsed, expandTool = me.placeholder.expandTool, floatCls = Ext.panel.Panel.floatCls, center = me.ownerLayout ? me.ownerLayout.centerRegion : null, finalPos, floatedPos; // Layouts suspended - don't bother with animation shenanigans if (Ext.Component.layoutSuspendCount) { animate = false; } if (me.floatedFromCollapse) { floatedPos = me.getPosition(true); // these are the same cleanups performed by the normal slideOut mechanism: me.slideOutFloatedPanelBegin(); me.slideOutFloatedPanelEnd(); me.floated = false; } // We assume that if expand was caused by keyboard action on focused // placeholder expand tool, the logical focus transition is to the // panel header's collapse tool. // Note that it may not be the case when the user *clicked* expand tool // while focus was elsewhere; in that case we dare not touch focus to avoid // sudden jumps. if (Ext.ComponentManager.getActiveComponent() === expandTool) { me.focusHeaderCollapseTool = true; // There is an odd issue with JAWS screen reader: when expanding a panel, // it will announce Expand tool again before focus is forced to Collapse // tool. I'm not sure why that happens since focus does not move from // Expand tool during animation; this hack should work around // the problem until we come up with more understanding and a proper // solution. The attributes are restored below in doPlaceholderExpand. expandTool._ariaRole = expandTool.ariaEl.dom.getAttribute('role'); expandTool._ariaLabel = expandTool.ariaEl.dom.getAttribute('aria-label'); expandTool.ariaEl.dom.setAttribute('role', 'presentation'); expandTool.ariaEl.dom.removeAttribute('aria-label'); } if (animate) { // Expand me and hide the placeholder Ext.suspendLayouts(); me.placeholder.hide(); me.el.show(); me.collapsed = false; me.setHiddenState(false); // Stop the center region from moving when laid out without the placeholder there. // Unless we are expanding from a floated out situation. In that case, it's laid out // immediately. if (center && !floatedPos) { center.hidden = true; } Ext.resumeLayouts(true); // ATE - this is the fix if (center) { center.hidden = false; } me.el.addCls(floatCls); // At this point, this Panel is arranged in its correct, expanded layout. // The center region has not been affected because it has been flagged as hidden. // // If we are proceeding from floated, the center region has also been arranged // in its new layout to accommodate this expansion, so no further layout is needed, just // element animation. // // If we are proceeding from fully collapsed, the center region has *not* been relayed // out because the UI look and feel dictates that it stays stable until the expanding // panel has slid in all the way, and *then* it snaps into place. me.isCollapsingOrExpanding = 2; // Floated, move it back to the floated pos, and thence into the correct place if (floatedPos) { finalPos = me.getXY(); me.setLocalXY(floatedPos[0], floatedPos[1]); me.setXY([finalPos[0], finalPos[1]], { duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration), listeners: { scope: me, afteranimate: function() { var me = this; me.el.removeCls(floatCls); me.isCollapsingOrExpanding = 0; me.fireEvent('expand', me); } } }); } // Not floated, slide it in to the correct place else { me.el.hide(); me.placeholder.el.show(); me.placeholder.hidden = false; // Slide this Component's el back into place, after which we lay out AGAIN me.setHiddenState(false); me.el.slideIn(me.convertCollapseDir(collapseDir), { preserveScroll: true, duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration), listeners: { afteranimate: me.doPlaceholderExpand, scope: me } }); } } else { me.floated = me.collapsed = false; me.doPlaceholderExpand(true); } return me; }});