Ext.define('Ext.grid.selection.SelectionExtender', { extend: 'Ext.dd.DragTracker', maskBox: {}, constructor: function(config) { var me = this; // We can only initialize properly if there are elements to work with if (config.view.rendered) { me.initSelectionExtender(config); } else { me.view = config.view; config.view.on({ render: me.initSelectionExtender, args: [config], scope: me }); } }, initSelectionExtender: function(config) { var me = this, displayMode = Ext.dom.Element.DISPLAY; me.el = config.view.el; me.handle = config.view.ownerGrid.body.createChild({ cls: Ext.baseCSSPrefix + 'ssm-extender-drag-handle', style: 'display:none' }).setVisibilityMode(displayMode); me.handle.on({ contextmenu: function(e) { e.stopEvent(); } }); me.mask = me.el.createChild({ cls: Ext.baseCSSPrefix + 'ssm-extender-mask', style: 'display:none' }).setVisibilityMode(displayMode); me.superclass.constructor.call(me, config); // Mask and andle must survive being orphaned me.mask.skipGarbageCollection = me.handle.skipGarbageCollection = true; me.viewListeners = me.view.on({ scroll: me.onViewScroll, scope: me, destroyable: true }); me.gridListeners = me.view.ownerGrid.on({ columnResize: me.alignHandle, scope: me, destroyable: true }); me.extendX = !!(me.axes & 1); me.extendY = !!(me.axes & 2); }, setHandle: function(firstPos, lastPos) { var me = this; if (!me.view.rendered) { me.view.on({ render: me.initSelectionExtender, args: [firstPos, lastPos], scope: me }); return; } me.firstPos = firstPos; me.lastPos = lastPos; // If we've done a "select all rows" and there is buffered rendering, then // the cells might not be rendered, so we can't activate the replicator. if (firstPos && lastPos && firstPos.getCell(true) && lastPos.getCell(true)) { if (me.curPos) { me.curPos.setPosition(lastPos); } else { me.curPos = lastPos.clone(); } // Align centre of handle with bottom-right corner of last cell if possible. me.alignHandle(); } else { me.disable(); } }, alignHandle: function() { var me = this, firstCell = me.firstPos && me.firstPos.getCell(true), lastCell = me.lastPos && me.lastPos.getCell(true); // Cell corresponding to the position might not be rendered. // This will be called upon scroll if (firstCell && lastCell) { me.enable(); me.handle.alignTo(lastCell, 'c-br'); } else { me.disable(); } }, enable: function() { this.handle.show(); this.callParent(); }, disable: function() { this.handle.hide(); this.mask.hide(); this.callParent(); }, onDrag: function(e) { // pointer-events-none is not supported on IE10m. // So if shrinking the extension zone, the mousemove target may be the mask. // We have to retarget on the cell *below* that. if (e.target === this.mask.dom) { this.mask.hide(); e.target = document.elementFromPoint.apply(document, e.getXY()); this.mask.show(); } var me = this, view = me.view, viewTop = view.el.getY(), viewLeft = view.el.getX(), overCell = e.getTarget(me.view.getCellSelector()), scrollTask = me.scrollTask || (me.scrollTask = Ext.util.TaskManager.newTask({ run: me.doAutoScroll, scope: me, interval: 10 })), scrollBy = me.scrollBy || (me.scrollBy = []); // Dragged outside the view; stop scrolling. if (!me.el.contains(e.target)) { scrollBy[0] = scrollBy[1] = 0; return scrollTask.stop(); } // Neart bottom of view if (me.lastXY[1] > viewTop + view.el.getHeight(true) - 15) { if (me.extendY) { scrollBy[1] = 3; scrollTask.start(); } } // Near top of view else if (me.lastXY[1] < viewTop + 10) { if (me.extendY) { scrollBy[1] = -3; scrollTask.start(); } } // Near right edge of view else if (me.lastXY[0] > viewLeft + view.el.getWidth(true) - 15) { if (me.extendX) { scrollBy[0] = 3; scrollTask.start(); } } // Near left edge of view else if (me.lastXY[0] < viewLeft + 10) { if (me.extendX) { scrollBy[0] = -3; scrollTask.start(); } } // Not near an edge, cancel autoscrolling else { scrollBy[0] = scrollBy[1] = 0; scrollTask.stop(); } if (overCell && overCell !== me.lastOverCell) { me.lastOverCell = overCell; me.syncMaskOnCell(overCell); } }, doAutoScroll: function() { var me = this, view = me.view, scrollOverCell; // Bump the view in whatever direction was decided in the onDrag method. view.scrollBy.apply(view, me.scrollBy); // Mouseover does not fire on autoscroll so see where the mouse is over on each scroll scrollOverCell = document.elementFromPoint.apply(document, me.lastXY); if (scrollOverCell) { scrollOverCell = Ext.fly(scrollOverCell).up(view.cellSelector); if (scrollOverCell && scrollOverCell !== me.lastOverCell) { me.lastOverCell = scrollOverCell; me.syncMaskOnCell(scrollOverCell); } } }, onEnd: function(e) { var me = this; if (me.scrollTask) { me.scrollTask.stop(); } if (me.extensionDescriptor) { me.disable(); me.view.getSelectionModel().extendSelection(me.extensionDescriptor); } }, onViewScroll: function() { var me = this; // If being dragged if (me.active && me.lastOverCell) { me.syncMaskOnCell(me.lastOverCell); } // We have been applied to a selection block if (me.firstPos) { // Align centre of handle with bottom-right corner of last cell if possible. me.alignHandle(); } }, syncMaskOnCell: function(overCell) { var me = this, view = me.view, rows = view.all, curPos = me.curPos, maskBox = me.maskBox, selRegion, firstPos = me.firstPos.clone(), lastPos = me.lastPos.clone(), extensionStart = me.firstPos.clone(), extensionEnd = me.lastPos.clone(); // Constrain cell positions to be within rendered range. firstPos.setRow(Math.min(Math.max(firstPos.rowIdx, rows.startIndex), rows.endIndex)); lastPos.setRow( Math.min(Math.max(lastPos.rowIdx, rows.startIndex), rows.endIndex)); me.selectionRegion = selRegion = firstPos.getCell().getRegion().union(lastPos.getCell().getRegion()); curPos.setPosition(view.getRecord(overCell), view.getHeaderByCell(overCell)); // The above calls require the cell to be a DOM reference overCell = Ext.fly(overCell); // Reset border to default, which is the overall border setting from SASS // We disable the border which is contiguous to the selection. me.mask.dom.style.borderTopWidth = me.mask.dom.style.borderRightWidth = me.mask.dom.style.borderBottomWidth = me.mask.dom.style.borderLeftWidth = ''; // Dragged above the selection if (curPos.rowIdx < me.firstPos.rowIdx && me.extendY) { me.extensionDescriptor = { type: 'rows', start: extensionStart.setRow(curPos.rowIdx), end: extensionEnd.setRow(me.firstPos.rowIdx - 1), rows: curPos.rowIdx - me.firstPos.rowIdx, mousePosition: me.lastXY }; me.mask.dom.style.borderBottomWidth = '0'; maskBox.x = selRegion.x; maskBox.y = overCell.getY(); maskBox.width = selRegion.right - selRegion.left; maskBox.height = selRegion.top - overCell.getY(); } // Dragged below selection else if (curPos.rowIdx > me.lastPos.rowIdx && me.extendY) { me.extensionDescriptor = { type: 'rows', start: extensionStart.setRow(me.lastPos.rowIdx + 1), end: extensionEnd.setRow(curPos.rowIdx), rows: curPos.rowIdx - me.lastPos.rowIdx, mousePosition: me.lastXY }; me.mask.dom.style.borderTopWidth = '0'; maskBox.x = selRegion.x; maskBox.y = selRegion.bottom; maskBox.width = selRegion.right - selRegion.left; maskBox.height = overCell.getRegion().bottom - selRegion.bottom; } // row position is within selected row range else { // Dragged to left of selection if (curPos.colIdx < me.firstPos.colIdx && me.extendX) { me.extensionDescriptor = { type: 'columns', start: extensionStart.setColumn(curPos.colIdx), end: extensionEnd.setColumn(me.firstPos.colIdx - 1), columns: curPos.colIdx - me.firstPos.colIdx, mousePosition: me.lastXY }; me.mask.dom.style.borderRightWidth = '0'; maskBox.x = overCell.getX(); maskBox.y = selRegion.top; maskBox.width = selRegion.left - overCell.getX(); maskBox.height = selRegion.bottom - selRegion.top; } // Dragged to right of selection else if (curPos.colIdx > me.lastPos.colIdx && me.extendX) { me.extensionDescriptor = { type: 'columns', start: extensionStart.setColumn(me.lastPos.colIdx + 1), end: extensionEnd.setColumn(curPos.colIdx), columns: curPos.colIdx - me.lastPos.colIdx, mousePosition: me.lastXY }; me.mask.dom.style.borderLeftWidth = '0'; maskBox.x = selRegion.right; maskBox.y = selRegion.top; maskBox.width = overCell.getRegion().right - selRegion.right; maskBox.height = selRegion.bottom - selRegion.top; } else { me.extensionDescriptor = null; } } if (view.ownerGrid.hasListeners.selectionextenderdrag) { view.ownerGrid.fireEvent('selectionextenderdrag', view.ownerGrid, view.getSelectionModel().getSelected(), me.extensionDescriptor); } if (me.extensionDescriptor) { me.mask.show(); me.mask.setBox(maskBox); } else { me.mask.hide(); } }, destroy: function() { var me = this; Ext.destroy(me.gridListeners, me.viewListeners, me.mask, me.handle); me.callParent(); }});