/** * Internal utility class that provides default configuration for cell editing. * @private */Ext.define('Ext.grid.CellEditor', { extend: 'Ext.Editor', /** * @property {Boolean} isCellEditor * @readonly * `true` in this class to identify an object as an instantiated CellEditor, or subclass thereof. */ isCellEditor: true, alignment: 'l-l!', hideEl : false, cls: Ext.baseCSSPrefix + 'small-editor ' + Ext.baseCSSPrefix + 'grid-editor ' + Ext.baseCSSPrefix + 'grid-cell-editor', treeNodeSelector: '.' + Ext.baseCSSPrefix + 'tree-node-text', shim: false, shadow: false, // Set the grid that owns this editor. // Called by CellEditing#getEditor setGrid: function(grid) { var me = this, oldGrid = me.grid, viewListeners; if (grid !== oldGrid) { viewListeners = { beforeitemupdate: me.beforeItemUpdate, itemupdate: me.onItemUpdate, scope: me }; // Remove previous refresh listener if (oldGrid) { oldGrid.getView().un(viewListeners); } me.grid = grid; // On view refresh, we need to copy our DOM into the detached body to prevent it from being garbage collected. grid.getView().on(viewListeners); } }, beforeViewRefresh: function(view) { var me = this, dom = me.el && me.el.dom; if (dom) { me.wasAllowBlur = me.allowBlur; if (me.editing) { // Clear the Panel's cellFocused flag prior to removing it from the DOM // This will prevent the Panels onFocusLeave from processing the resulting blurring. view.cellFocused = false; // Set the Editor.allowBlur setting so that it does not process the upcoming field blur event and terminate the edit me.allowBlur = false; } // Remove the editor from the view to protect it from annihilation: https://sencha.jira.com/browse/EXTJSIV-11713 if (dom.parentNode) { // Set refreshing flag so that onFocusLeave caused by removing a focused element // does not exit actionableMode view.refreshing = true; dom.parentNode.removeChild(dom); } } }, onViewRefresh: function(view) { var me = this, dom = me.el && me.el.dom, cell, context = me.context; if (dom) { // Update the context with the possibly new contextual data // (refresh might have been caused by a sort or column move etc) cell = view.getCellByPosition(context, true); // If the refresh was caused by eg column removal, the cell will not exist. // In this case, terminate the edit. if (!cell) { me.allowBlur = me.wasAllowBlur; me.completeEdit(); Ext.getDetachedBody().dom.appendChild(dom); return; } context.node = view.getNode(context.record); context.row = view.getRow(context.record); context.cell = cell; context.rowIdx = view.indexOf(context.row); cell.insertBefore(dom, cell.firstChild); me.boundEl = me.container = Ext.get(cell); me.realign(true); // If the view was refreshed while we were editing, replace it. // On IE, the blur event will fire asynchronously, so we must leave // allowBlur as false for a very short while longer. // After which we reset it, and refocus the field. if (me.editing) { if (Ext.isIE) { Ext.defer(function() { // May have been destroyed immediately after refreshing!? if (!me.destroyed) { me.allowBlur = me.wasAllowBlur; me.field.focus(); } }, 10); } else { me.allowBlur = me.wasAllowBlur; me.field.focus(); } } } }, beforeItemUpdate: function(record, recordIndex, oldItemDom, columnsToUpdate) { var me = this, context = me.context, l = columnsToUpdate.length, i; // If this CellEditor's row is to be updated, we *may* have to restore this editor // due to cell content possibly being changed. if (record === context.record) { for (i = 0; i < l; i++) { // If the cell is scheduled for update, we definitely will need restoration. if (columnsToUpdate[i] === context.column) { me.needsFixOnItemUpdate = true; me.beforeViewRefresh(context.view); return; } } } }, onItemUpdate: function(record, recordIndex, oldItemDom) { var view = this.context.view; if (this.needsFixOnItemUpdate) { // The refreshing flag was set to indicate to the onFocusLeave listener that it // should ignore focusleave caused by this Editor blurring. this.needsFixOnItemUpdate = view.refreshing = false; this.onViewRefresh(view); } }, startEdit: function(boundEl, value, doFocus) { this.context = this.editingPlugin.context; this.callParent([boundEl, value, doFocus]); }, /** * @private * Shows the editor, end ensures that it is rendered into the correct view * Hides the grid cell inner element when a cell editor is shown. */ onShow: function() { var me = this, innerCell = me.boundEl.down(me.context.view.innerSelector); if (innerCell) { if (me.isForTree) { innerCell = innerCell.child(me.treeNodeSelector); } innerCell.hide(); } me.callParent(arguments); }, onFocusEnter: function() { var context = this.context, view = context.view; // Focus restoration after a refresh may require realignment and correction // of the context because it could have been due to a or filter operation and // the context may have changed position. context.node = view.getNode(context.record); context.row = view.getRow(context.record); context.cell = context.getCell(true); context.rowIdx = view.indexOf(context.row); this.realign(true); this.callParent(arguments); // Ensure that hide processing does not throw focus back to the previously focused element. this.focusEnterEvent = null; }, onEditComplete: function(remainVisible) { // When being asked to process edit completion, if we are not hiding, restore the cell now if (remainVisible) { this.restoreCell(); } this.callParent(arguments); }, /** * @private * Shows the grid cell inner element when a cell editor is hidden */ onHide: function() { this.restoreCell(); this.callParent(arguments); }, onSpecialKey: function(field, event) { var me = this, key = event.getKey(), complete = me.completeOnEnter && key === event.ENTER, cancel = me.cancelOnEsc && key === event.ESC, view = me.editingPlugin.view; if (complete || cancel) { // Do not let the key event bubble into the NavigationModel after we're don processing it. // We control the navigation action here; we focus the cell. event.stopEvent(); // Maintain visibility so that focus doesn't leak. // We need to direct focusback to the owning cell. if (complete) { me.completeEdit(true); } else if (cancel) { me.cancelEdit(true); } view.getNavigationModel().setPosition(me.context, null, event); view.ownerGrid.setActionableMode(false); } }, getRefOwner: function() { return this.column && this.column.getView(); }, restoreCell: function() { var me = this, innerCell = me.boundEl.down(me.context.view.innerSelector); if (innerCell) { if (me.isForTree) { innerCell = innerCell.child(me.treeNodeSelector); } innerCell.show(); } }, /** * @private * Fix checkbox blur when it is clicked. */ afterRender: function() { var me = this, field = me.field; me.callParent(arguments); if (field.isCheckbox) { field.mon(field.inputEl, { mousedown: me.onCheckBoxMouseDown, click: me.onCheckBoxClick, scope: me }); } }, /** * @private * Because when checkbox is clicked it loses focus completeEdit is bypassed. */ onCheckBoxMouseDown: function() { this.completeEdit = Ext.emptyFn; }, /** * @private * Restore checkbox focus and completeEdit method. */ onCheckBoxClick: function() { delete this.completeEdit; this.field.focus(false, 10); }, /** * @private * Realigns the Editor to the grid cell, or to the text node in the grid inner cell * if the inner cell contains multiple child nodes. */ realign: function(autoSize) { var me = this, boundEl = me.boundEl, innerCell = boundEl.down(me.context.view.innerSelector), innerCellTextNode = innerCell.dom.firstChild, width = boundEl.getWidth(), offsets = Ext.Array.clone(me.offsets), grid = me.grid, xOffset, v = '', // innerCell is empty if there are no children, or there is one text node, and it contains whitespace isEmpty = !innerCellTextNode || (innerCellTextNode.nodeType === 3 && !(Ext.String.trim(v = innerCellTextNode.data).length)); if (me.isForTree) { // When editing a tree, adjust the width and offsets of the editor to line // up with the tree cell's text element xOffset = me.getTreeNodeOffset(innerCell); width -= Math.abs(xOffset); offsets[0] += xOffset; } if (grid.columnLines) { // Subtract the column border width so that the editor displays inside the // borders. The column border could be either on the left or the right depending // on whether the grid is RTL - using the sum of both borders works in both modes. width -= boundEl.getBorderWidth('rl'); } if (autoSize === true) { me.field.setWidth(width); } // https://sencha.jira.com/browse/EXTJSIV-10871 Ensure the data bearing element has a height from text. if (isEmpty) { innerCell.dom.innerHTML = 'X'; } me.alignTo(boundEl, me.alignment, offsets); if (isEmpty) { innerCell.dom.firstChild.data = v; } }, getTreeNodeOffset: function(innerCell) { return innerCell.child(this.treeNodeSelector).getOffsetsTo(innerCell)[0]; }});