/**
* Plugin to add header resizing functionality to a HeaderContainer.
* Always resizing header to the left of the splitter you are resizing.
*/
Ext.define('Ext.grid.plugin.HeaderResizer', {
extend: 'Ext.plugin.Abstract',
alias: 'plugin.gridheaderresizer',
requires: [
'Ext.dd.DragTracker',
'Ext.util.Region'
],
disabled: false,
config: {
/**
* @cfg {Boolean} dynamic
* True to resize on the fly rather than using a proxy marker.
* @accessor
*/
dynamic: false
},
colHeaderCls: Ext.baseCSSPrefix + 'column-header',
minColWidth: 40,
maxColWidth: 1000,
eResizeCursor: 'col-resize',
init: function(headerCt) {
var me = this;
me.headerCt = headerCt;
headerCt.on('render', me.afterHeaderRender, me, { single: me });
// Pull minColWidth from the minWidth in the Column prototype
if (!me.minColWidth) {
me.self.prototype.minColWidth = Ext.grid.column.Column.prototype.minWidth;
}
},
destroy: function() {
var me = this,
tracker = me.tracker;
if (tracker) {
tracker.destroy();
me.tracker = null;
}
// The grid may happen to never render
me.headerCt.un('render', me.afterHeaderRender, me);
me.headerCt = null;
me.callParent();
},
afterHeaderRender: function() {
var me = this,
headerCt = me.headerCt,
el = headerCt.el;
headerCt.mon(el, 'mousemove', me.onHeaderCtMouseMove, me);
me.markerOwner = me.ownerGrid = me.headerCt.up('tablepanel').ownerGrid;
me.tracker = new Ext.dd.DragTracker({
disabled: me.disabled,
onBeforeStart: me.onBeforeStart.bind(me),
onStart: me.onStart.bind(me),
onDrag: me.onDrag.bind(me),
onEnd: me.onEnd.bind(me),
onCancel: me.onCancel.bind(me),
tolerance: 3,
autoStart: 300,
el: el
});
headerCt.setTouchAction({ panX: false });
},
// As we mouse over individual headers, change the cursor to indicate
// that resizing is available, and cache the resize target header for use
// if/when they mousedown.
onHeaderCtMouseMove: function(e) {
var me = this;
if (me.headerCt.dragging || me.disabled) {
if (me.activeHd) {
me.activeHd.el.dom.style.cursor = '';
delete me.activeHd;
}
}
else if (e.pointerType !== 'touch') {
me.findActiveHeader(e);
}
},
findActiveHeader: function(e) {
var me = this,
headerCt = me.headerCt,
headerEl = e.getTarget('.' + me.colHeaderCls, headerCt.el, true),
ownerGrid = me.ownerGrid,
ownerLockable = ownerGrid.ownerLockable,
overHeader, resizeHeader, headers, header;
me.activeHd = null;
if (headerEl) {
overHeader = Ext.getCmp(headerEl.id);
// If near the right edge, we're resizing the column we are over.
if (overHeader.isAtEndEdge(e)) {
// Cannot resize the only column in a forceFit grid.
if (headerCt.visibleColumnManager.getColumns().length === 1 && headerCt.forceFit) {
return;
}
resizeHeader = overHeader;
}
// Else... we might be near the right edge
else if (overHeader.isAtStartEdge(e)) {
// Extract previous visible leaf header
headers = headerCt.visibleColumnManager.getColumns();
header = overHeader.isGroupHeader ? overHeader.getGridColumns()[0] : overHeader;
resizeHeader = headers[Ext.Array.indexOf(headers, header) - 1];
// If there wasn't one, and we are the normal side of a lockable assembly then
// use the last visible leaf header of the locked side.
if (!resizeHeader && ownerLockable && !ownerGrid.isLocked) {
headers = ownerLockable.lockedGrid.headerCt.visibleColumnManager.getColumns();
resizeHeader = headers[headers.length - 1];
}
}
// We *are* resizing
if (resizeHeader) {
// If we're attempting to resize a group header, that cannot be resized,
// so find its last visible leaf header; Group headers are sized
// by the size of their child headers.
if (resizeHeader.isGroupHeader) {
headers = resizeHeader.getGridColumns();
resizeHeader = headers[headers.length - 1];
}
// Check if the header is resizable. Continue checking the old "fixed" property,
// but also check whether the resizable property is set to false.
if (resizeHeader && !(resizeHeader.fixed || (resizeHeader.resizable === false))) {
me.activeHd = resizeHeader;
overHeader.el.dom.style.cursor = me.eResizeCursor;
if (overHeader.triggerEl) {
overHeader.triggerEl.dom.style.cursor = me.eResizeCursor;
}
}
}
// reset
else {
overHeader.el.dom.style.cursor = '';
if (overHeader.triggerEl) {
overHeader.triggerEl.dom.style.cursor = '';
}
}
}
return me.activeHd;
},
// only start when there is an activeHd
onBeforeStart: function(e) {
var me = this;
// If on touch, we will have received no mouseover, so we have to
// decide whether the touchstart is in a resize zone, and if so, which header
// is to be sized. Cache any activeHd because it will be cleared on subsequent
// mousemoves outside the resize zone.
me.dragHd = me.activeHd || e.pointerType === 'touch' && me.findActiveHeader(e);
if (me.dragHd && !me.headerCt.dragging) {
// Prevent drag and longpress gestures being triggered by this mousedown
e.claimGesture();
// Calculate how far off the right marker line the mouse pointer is.
// This will be the xDelta during the following drag operation.
me.xDelta = me.dragHd.getX() + me.dragHd.getWidth() - me.tracker.getXY()[0];
me.tracker.constrainTo = me.getConstrainRegion();
return true;
}
else {
me.headerCt.dragging = false;
return false;
}
},
// When mouseup and we have not begun dragging.
// The setup done in onbeforeStart must be cleared.
onCancel: function(e) {
this.dragHd = this.activeHd = null;
this.headerCt.dragging = false;
},
// get the region to constrain to, takes into account max and min col widths
getConstrainRegion: function() {
var me = this,
dragHdEl = me.dragHd.el,
nextHd,
ownerGrid = me.ownerGrid,
widthModel = ownerGrid.getSizeModel().width,
// eslint-disable-next-line max-len
maxColWidth = widthModel.shrinkWrap ? me.headerCt.getWidth() - me.headerCt.visibleColumnManager.getColumns().length * me.minColWidth : me.maxColWidth,
result;
// If forceFit, then right constraint is based upon not being able to force the next header
// beyond the minColWidth. If there is no next header, then the header may not be expanded.
if (me.headerCt.forceFit) {
nextHd = me.dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
if (nextHd && me.headerInSameGrid(nextHd)) {
maxColWidth = dragHdEl.getWidth() + (nextHd.getWidth() - me.minColWidth);
}
}
// If resize header is in a locked grid, the maxWidth has to be 30px
// within the available locking grid's width.
// But only if the locked grid shrinkwraps its columns
else if (ownerGrid.isLocked && widthModel.shrinkWrap) {
maxColWidth =
me.dragHd.up('[scrollerOwner]').getTargetEl().getWidth(true) -
ownerGrid.getWidth() -
(ownerGrid.ownerLockable.normalGrid.visibleColumnManager.getColumns().length *
me.minColWidth + Ext.scrollbar.width());
}
result = me.adjustConstrainRegion(dragHdEl.getRegion(), 0, 0, 0, me.minColWidth);
result.right = dragHdEl.getX() + maxColWidth;
return result;
},
// initialize the left and right hand side markers around
// the header that we are resizing
onStart: function(e) {
var me = this,
dragHd = me.dragHd,
width = dragHd.el.getWidth(),
headerCt = dragHd.getRootHeaderCt(),
x, y, markerOwner, lhsMarker, rhsMarker, markerHeight;
me.headerCt.dragging = true;
me.origWidth = width;
// setup marker proxies
if (!me.dynamic) {
markerOwner = me.markerOwner;
// https://sencha.jira.com/browse/EXTJSIV-11299
// In Neptune (and other themes with wide frame borders), resize handles are embedded
// in borders, *outside* of the outer element's content area, therefore
// the outer element is set to overflow:visible.
// During column resize, we should not see the resize markers outside the grid,
// so set to overflow:hidden.
if (markerOwner.frame && markerOwner.resizable) {
me.gridOverflowSetting = markerOwner.el.dom.style.overflow;
markerOwner.el.dom.style.overflow = 'hidden';
}
x = me.getLeftMarkerX(markerOwner);
lhsMarker = markerOwner.getLhsMarker();
rhsMarker = markerOwner.getRhsMarker();
markerHeight = me.ownerGrid.body.getHeight() + headerCt.getHeight();
y = headerCt.getOffsetsTo(markerOwner)[1] - markerOwner.el.getBorderWidth('t');
// Ensure the markers have the correct cursor in case the cursor is *exactly* over
// this single pixel line, not just within the active resize zone
lhsMarker.dom.style.cursor = me.eResizeCursor;
rhsMarker.dom.style.cursor = me.eResizeCursor;
lhsMarker.setLocalY(y);
rhsMarker.setLocalY(y);
lhsMarker.setHeight(markerHeight);
rhsMarker.setHeight(markerHeight);
me.setMarkerX(lhsMarker, x);
me.setMarkerX(rhsMarker, x + width);
}
},
// synchronize the rhsMarker with the mouse movement
onDrag: function(e) {
var me = this;
if (me.dynamic) {
me.doResize();
}
else {
me.setMarkerX(me.getMovingMarker(me.markerOwner), me.calculateDragX(me.markerOwner));
}
},
getMovingMarker: function(markerOwner) {
return markerOwner.getRhsMarker();
},
onEnd: function(e) {
var me = this,
markerOwner = me.markerOwner;
me.headerCt.dragging = false;
if (me.dragHd) {
if (!me.dynamic) {
// If we had saved the gridOverflowSetting, restore it
if ('gridOverflowSetting' in me) {
markerOwner.el.dom.style.overflow = me.gridOverflowSetting;
}
// hide markers
me.setMarkerX(markerOwner.getLhsMarker(), -9999);
me.setMarkerX(markerOwner.getRhsMarker(), -9999);
}
me.doResize();
// On mouseup (a real mouseup), we must be ready to start dragging again immediately -
// Leave the activeHd active.
if (e.pointerType !== 'touch') {
me.dragHd = null;
me.activeHd.el.dom.style.cursor = me.eResizeCursor;
}
else {
me.dragHd = me.activeHd = null;
}
}
// Do not process the upcoming click after this mouseup. It's not a click gesture
me.headerCt.blockNextEvent();
},
doResize: function() {
var me = this,
dragHd = me.dragHd,
nextHd,
offset = me.tracker.getOffset('point');
// Only resize if we have dragged any distance in the X dimension...
if (dragHd && offset[0]) {
// resize the dragHd
if (dragHd.flex) {
delete dragHd.flex;
}
Ext.suspendLayouts();
// Set the new column width.
// Adjusted for the offset from the actual column border that the mousedown
// too place at.
me.adjustColumnWidth(offset[0] - me.xDelta);
// In the case of forceFit, change the following Header width.
// Constraining so that neither neighbour can be sized to below minWidth is handled
// in getConstrainRegion
if (me.headerCt.forceFit) {
nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
if (nextHd && !me.headerInSameGrid(nextHd)) {
nextHd = null;
}
if (nextHd) {
delete nextHd.flex;
nextHd.setWidth(nextHd.getWidth() - offset[0]);
}
}
// Apply the two width changes by laying out the owning HeaderContainer
Ext.resumeLayouts(true);
}
},
// nextNode can traverse out of this grid, possibly to others on the page, so limit it here
headerInSameGrid: function(header) {
var grid = this.dragHd.up('tablepanel');
return !!header.up(grid);
},
disable: function() {
var tracker = this.tracker;
this.disabled = true;
if (tracker) {
tracker.disable();
}
},
enable: function() {
var tracker = this.tracker;
this.disabled = false;
if (tracker) {
tracker.enable();
}
},
calculateDragX: function(markerOwner) {
return this.tracker.getXY('point')[0] + this.xDelta - markerOwner.getX() -
markerOwner.el.getBorderWidth('l');
},
getLeftMarkerX: function(markerOwner) {
return this.dragHd.getX() - markerOwner.getX() - markerOwner.el.getBorderWidth('l') - 1;
},
setMarkerX: function(marker, x) {
marker.setLocalX(x);
},
adjustConstrainRegion: function(region, t, r, b, l) {
return region.adjust(t, r, b, l);
},
adjustColumnWidth: function(offsetX) {
this.dragHd.setWidth(this.origWidth + offsetX);
}
});