/**
* This class functions **between siblings of a {@link Ext.layout.container.VBox VBox} or
* {@link Ext.layout.container.HBox HBox} layout** to resize both immediate siblings.
*
* A Splitter will preserve the flex ratio of any flexed siblings it is required to resize.
* It does this by setting the `flex` property of *all* flexed siblings to equal their pixel size.
* The actual numerical `flex` property in the Components will change, but the **ratio**
* to the total flex value will be preserved.
*
* A Splitter may be configured to show a centered mini-collapse tool orientated to collapse
* the {@link #collapseTarget}. The Splitter will then call that sibling Panel's
* {@link Ext.panel.Panel#method-collapse collapse} or {@link Ext.panel.Panel#method-expand expand}
* method to perform the appropriate operation (depending on the sibling collapse state).
* To create the mini-collapse tool but take care of collapsing yourself, configure the splitter
* with `{@link #performCollapse}: false`.
*/
Ext.define('Ext.resizer.Splitter', {
extend: 'Ext.Component',
xtype: 'splitter',
requires: ['Ext.XTemplate'],
uses: ['Ext.resizer.SplitterTracker'],
childEls: [
'collapseEl'
],
/* eslint-disable indent, max-len */
renderTpl: [
'<tpl if="collapsible===true">',
'<div id="{id}-collapseEl" data-ref="collapseEl" role="presentation" class="', Ext.baseCSSPrefix, 'collapse-el ',
Ext.baseCSSPrefix, 'layout-split-{collapseDir}{childElCls}">',
'</div>',
'</tpl>'
],
/* eslint-enable indent, max-len */
isSplitter: true,
baseCls: Ext.baseCSSPrefix + 'splitter',
collapsedClsInternal: Ext.baseCSSPrefix + 'splitter-collapsed',
// Default to tree, allow internal classes to disable resizing
canResize: true,
/**
* @cfg {Boolean} [collapsible]
* True to show a mini-collapse tool in the Splitter to toggle expand and collapse
* on the {@link #collapseTarget} Panel.
* Defaults to the {@link Ext.panel.Panel#collapsible collapsible} setting of the Panel.
*/
collapsible: null,
/**
* @cfg {Boolean} performCollapse
* Set to false to prevent this Splitter's mini-collapse tool from managing the collapse
* state of the {@link #collapseTarget}.
*/
/**
* @cfg {Boolean} collapseOnDblClick
* True to enable dblclick to toggle expand and collapse on the {@link #collapseTarget} Panel.
*/
collapseOnDblClick: true,
/**
* @cfg {Number} defaultSplitMin
* Provides a default minimum width or height for the two components
* that the splitter is between.
*/
defaultSplitMin: 40,
/**
* @cfg {Number} defaultSplitMax
* Provides a default maximum width or height for the two components
* that the splitter is between.
*/
defaultSplitMax: 1000,
/**
* @cfg {String} collapsedCls
* A class to add to the splitter when it is collapsed. See {@link #collapsible}.
*/
/**
* @cfg {String/Ext.panel.Panel} collapseTarget
* A string describing the relative position of the immediate sibling Panel to collapse.
* May be 'prev' or 'next'.
*
* Or the immediate sibling Panel to collapse.
*
* The orientation of the mini-collapse tool will be inferred from this setting.
*
* **Note that only Panels may be collapsed.**
*/
collapseTarget: 'next',
/**
* @property {String} orientation
* Orientation of this Splitter. `'vertical'` when used in an hbox layout, `'horizontal'`
* when used in a vbox layout.
*/
horizontal: false,
vertical: false,
touchAction: undefined, // so applier/updater always run
/**
* @cfg {Number} size
* The size of the splitter. This becomes the height for vertical splitters and
* width for horizontal splitters.
*/
size: 5,
/**
* @cfg {Object} [tracker]
* Any configuration options to be passed to the underlying {@link Ext.resizer.SplitterTracker}.
*/
tracker: null,
ariaRole: 'separator',
focusable: true,
tabIndex: 0,
applyTouchAction: function(touchAction, oldTouchAction) {
if (touchAction === undefined) {
touchAction = this.vertical ? { panX: false } : { panY: false };
}
return this.callParent([touchAction, oldTouchAction]);
},
/**
* Returns the config object (with an `xclass` property) for the splitter tracker. This
* is overridden by {@link Ext.resizer.BorderSplitter BorderSplitter} to create a
* {@link Ext.resizer.BorderSplitterTracker BorderSplitterTracker}.
* @protected
*/
getTrackerConfig: function() {
return Ext.apply({
xclass: 'Ext.resizer.SplitterTracker',
el: this.el,
splitter: this
}, this.tracker);
},
beforeRender: function() {
var me = this,
target = me.getCollapseTarget(),
collapsible = me.collapsible;
me.callParent();
if (target.collapsed) {
me.addCls(me.collapsedClsInternal);
}
if (!me.canResize) {
me.addCls(me.baseCls + '-noresize');
}
Ext.applyIf(me.renderData, {
collapseDir: me.getCollapseDirection(),
collapsible: (collapsible !== null) ? collapsible : target.collapsible
});
me.ariaRenderAttributes = me.ariaRenderAttributes || {};
// Calling getCollapseDirection() above will set the orientation property
me.ariaRenderAttributes['aria-orientation'] = me.orientation;
me.protoEl.unselectable();
},
onRender: function() {
var me = this,
target, collapseEl;
me.callParent(arguments);
// Add listeners on the mini-collapse tool unless performCollapse is set to false
if (me.performCollapse !== false) {
if (me.renderData.collapsible) {
me.mon(me.collapseEl, 'click', me.toggleTargetCmp, me);
}
if (me.collapseOnDblClick) {
me.mon(me.el, 'dblclick', me.toggleTargetCmp, me);
}
}
// Ensure the mini collapse icon is set to the correct direction
// when the target is collapsed/expanded by any means.
// Make sure we're only listening to collapse/expand events on Panels!
target = me.getCollapseTarget();
if (target && target.isPanel) {
target.on({
collapse: me.onTargetCollapse,
expand: me.onTargetExpand,
beforeexpand: me.onBeforeTargetExpand,
beforecollapse: me.onBeforeTargetCollapse,
scope: me
});
}
if (me.canResize) {
me.tracker = Ext.create(me.getTrackerConfig());
// Relay the most important events to our owner (could open wider later):
me.relayEvents(me.tracker, [ 'beforedragstart', 'dragstart', 'dragend' ]);
}
collapseEl = me.collapseEl;
if (collapseEl) {
collapseEl.lastCollapseDirCls = me.collapseDirProps[me.collapseDirection].cls;
}
},
getCollapseDirection: function() {
var me = this,
dir = me.collapseDirection,
collapseTarget, idx, items, type;
if (!dir) {
collapseTarget = me.collapseTarget;
if (collapseTarget.isComponent) {
dir = collapseTarget.collapseDirection;
}
if (!dir) {
// Avoid duplication of string tests.
// Create a two bit truth table of the configuration of the Splitter:
// Collapse Target | orientation
// 0 0 = next, horizontal
// 0 1 = next, vertical
// 1 0 = prev, horizontal
// 1 1 = prev, vertical
type = me.ownerCt.layout.type;
if (collapseTarget.isComponent) {
items = me.ownerCt.items;
idx = Number(items.indexOf(collapseTarget) === items.indexOf(me) - 1) << 1 |
Number(type === 'hbox');
}
else {
idx = Number(me.collapseTarget === 'prev') << 1 | Number(type === 'hbox');
}
// Read the data out the truth table
dir = ['bottom', 'right', 'top', 'left'][idx];
}
me.collapseDirection = dir;
}
me.setOrientation((dir === 'top' || dir === 'bottom') ? 'horizontal' : 'vertical');
return dir;
},
getCollapseTarget: function() {
var me = this;
return me.collapseTarget.isComponent
? me.collapseTarget
: me.collapseTarget === 'prev' ? me.previousSibling() : me.nextSibling();
},
setCollapseEl: function(display) {
var el = this.collapseEl;
if (el) {
el.setDisplayed(display);
}
},
onBeforeTargetExpand: function(target) {
this.setCollapseEl('none');
},
onBeforeTargetCollapse: function() {
this.setCollapseEl('none');
},
onTargetCollapse: function(target) {
var me = this;
// Only add the collapsed class if the collapse was from our target
// (not bubbled from below as in a Dashboard Column) and was in the dimension
// which this Splitter controls.
if (target === me.getCollapseTarget() &&
target[me.orientation === 'vertical' ? 'collapsedHorizontal' : 'collapsedVertical']()) {
me.el.addCls(me.collapsedClsInternal + ' ' + (me.collapsedCls || ''));
}
me.setCollapseEl('');
},
onTargetExpand: function(target) {
var me = this;
me.el.removeCls(me.collapsedClsInternal + ' ' + (me.collapsedCls || ''));
me.setCollapseEl('');
},
collapseDirProps: {
top: {
cls: Ext.baseCSSPrefix + 'layout-split-top'
},
right: {
cls: Ext.baseCSSPrefix + 'layout-split-right'
},
bottom: {
cls: Ext.baseCSSPrefix + 'layout-split-bottom'
},
left: {
cls: Ext.baseCSSPrefix + 'layout-split-left'
}
},
orientationProps: {
horizontal: {
opposite: 'vertical',
fixedAxis: 'height',
stretchedAxis: 'width'
},
vertical: {
opposite: 'horizontal',
fixedAxis: 'width',
stretchedAxis: 'height'
}
},
applyCollapseDirection: function() {
var me = this,
collapseEl = me.collapseEl,
collapseDirProps = me.collapseDirProps[me.collapseDirection],
cls;
if (collapseEl) {
cls = collapseEl.lastCollapseDirCls;
if (cls) {
collapseEl.removeCls(cls);
}
collapseEl.addCls(collapseEl.lastCollapseDirCls = collapseDirProps.cls);
}
},
applyOrientation: function() {
var me = this,
orientation = me.orientation,
orientationProps = me.orientationProps[orientation],
defaultSize = me.size,
fixedSizeProp = orientationProps.fixedAxis,
stretchSizeProp = orientationProps.stretchedAxis,
cls = me.baseCls + '-';
me[orientation] = true;
me[orientationProps.opposite] = false;
if (!me.hasOwnProperty(fixedSizeProp) || me[fixedSizeProp] === '100%') {
me[fixedSizeProp] = defaultSize;
}
if (!me.hasOwnProperty(stretchSizeProp) || me[stretchSizeProp] === defaultSize) {
me[stretchSizeProp] = '100%';
}
me.removeCls(cls + orientationProps.opposite);
me.addCls(cls + orientation);
},
setOrientation: function(orientation) {
var me = this;
if (me.orientation !== orientation) {
me.orientation = orientation;
me.applyOrientation();
}
},
updateOrientation: function() {
delete this.collapseDirection; // recompute
this.getCollapseDirection();
this.applyCollapseDirection();
},
toggleTargetCmp: function(e, t) {
var cmp = this.getCollapseTarget(),
placeholder = cmp.placeholder,
toggle;
// We can only toggle the target if it offers the expand/collapse API
if (Ext.isFunction(cmp.expand) && Ext.isFunction(cmp.collapse)) {
if (placeholder && !placeholder.hidden) {
toggle = true;
}
else {
toggle = !cmp.hidden;
}
if (toggle) {
if (cmp.collapsed || cmp.floatedFromCollapse) {
cmp.expand();
}
else if (cmp.collapseDirection) {
cmp.collapse();
}
else {
cmp.collapse(this.renderData.collapseDir);
}
}
}
},
/*
* Work around IE bug. %age margins do not get recalculated on element resize
* unless repaint called.
*/
setSize: function() {
var me = this;
me.callParent(arguments);
if (Ext.isIE && me.el) {
me.el.repaint();
}
},
doDestroy: function() {
Ext.destroy(this.tracker);
this.callParent();
}
});