/**
* @class Ext.resizer.Resizer
* <p>Applies drag handles to an element or component to make it resizable. The
* drag handles are inserted into the element (or component's element) and
* positioned absolute.</p>
*
* <p>Textarea and img elements will be wrapped with an additional div because
* these elements do not support child nodes. The original element can be accessed
* through the originalTarget property.</p>
*
* <p>Here is the list of valid resize handles:</p>
* <pre>
Value Description
------ -------------------
'n' north
's' south
'e' east
'w' west
'nw' northwest
'sw' southwest
'se' southeast
'ne' northeast
'all' all
</pre>
* {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
* <p>Here's an example showing the creation of a typical Resizer:</p>
* <pre><code>
<div id="elToResize" style="width:200px; height:100px; background-color:#000000;"></div>
Ext.create('Ext.resizer.Resizer', {
el: 'elToResize',
handles: 'all',
minWidth: 200,
minHeight: 100,
maxWidth: 500,
maxHeight: 400,
pinned: true
});
</code></pre>
*/
Ext.define('Ext.resizer.Resizer', {
mixins: {
observable: 'Ext.util.Observable'
},
uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],
alternateClassName: 'Ext.Resizable',
handleCls: Ext.baseCSSPrefix + 'resizable-handle',
pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
overCls: Ext.baseCSSPrefix + 'resizable-over',
proxyCls: Ext.baseCSSPrefix + 'resizable-proxy',
wrapCls: Ext.baseCSSPrefix + 'resizable-wrap',
/**
* @cfg {Boolean} dynamic
* <p>Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during dragging.
* This is <code>true</code> by default, but the {@link Ext.Component Component} class passes <code>false</code> when it
* is configured as {@link Ext.Component#resizable}.</p>
* <p>If specified as <code>false</code>, a proxy element is displayed during the resize operation, and the {@link #target}
* is updated on mouseup.</p>
*/
dynamic: true,
/**
* @cfg {String} handles String consisting of the resize handles to display. Defaults to 's e se' for
* Elements and fixed position Components. Defaults to 8 point resizing for floating Components (such as Windows).
* Specify either <code>'all'</code> or any of <code>'n s e w ne nw se sw'</code>.
*/
handles: 's e se',
/**
* @cfg {Number} height Optional. The height to set target to in pixels (defaults to null)
*/
height : null,
/**
* @cfg {Number} width Optional. The width to set the target to in pixels (defaults to null)
*/
width : null,
/**
* @cfg {Number} minHeight The minimum height for the element (defaults to 20)
*/
minHeight : 20,
/**
* @cfg {Number} minWidth The minimum width for the element (defaults to 20)
*/
minWidth : 20,
/**
* @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
*/
maxHeight : 10000,
/**
* @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
*/
maxWidth : 10000,
/**
* @cfg {Boolean} pinned True to ensure that the resize handles are always
* visible, false indicates resizing by cursor changes only (defaults to false)
*/
pinned: false,
/**
* @cfg {Boolean} preserveRatio True to preserve the original ratio between height
* and width during resize (defaults to false)
*/
preserveRatio: false,
/**
* @cfg {Boolean} transparent True for transparent handles. This is only applied at config time. (defaults to false)
*/
transparent: false,
/**
* @cfg {Mixed} constrainTo Optional. An element, or a {@link Ext.util.Region} into which the resize operation
* must be constrained.
*/
possiblePositions: {
n: 'north',
s: 'south',
e: 'east',
w: 'west',
se: 'southeast',
sw: 'southwest',
nw: 'northwest',
ne: 'northeast'
},
/**
* @cfg {Mixed} target The Element or Component to resize.
*/
/**
* Outer element for resizing behavior.
* @type Ext.core.Element
* @property el
*/
constructor: function(config) {
var me = this,
target,
tag,
handles = me.handles,
handleCls,
possibles,
len,
i = 0,
pos;
this.addEvents(
/**
* @event beforeresize
* Fired before resize is allowed. Return false to cancel resize.
* @param {Ext.resizer.Resizer} this
* @param {Number} width The start width
* @param {Number} height The start height
* @param {Ext.EventObject} e The mousedown event
*/
'beforeresize',
/**
* @event resizedrag
* Fires during resizing. Return false to cancel resize.
* @param {Ext.resizer.Resizer} this
* @param {Number} width The new width
* @param {Number} height The new height
* @param {Ext.EventObject} e The mousedown event
*/
'resizedrag',
/**
* @event resize
* Fired after a resize.
* @param {Ext.resizer.Resizer} this
* @param {Number} width The new width
* @param {Number} height The new height
* @param {Ext.EventObject} e The mouseup event
*/
'resize'
);
if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
target = config;
config = arguments[1] || {};
config.target = target;
}
// will apply config to this
me.mixins.observable.constructor.call(me, config);
// If target is a Component, ensure that we pull the element out.
// Resizer must examine the underlying Element.
target = me.target;
if (target) {
if (target.isComponent) {
me.el = target.getEl();
if (target.minWidth) {
me.minWidth = target.minWidth;
}
if (target.minHeight) {
me.minHeight = target.minHeight;
}
if (target.maxWidth) {
me.maxWidth = target.maxWidth;
}
if (target.maxHeight) {
me.maxHeight = target.maxHeight;
}
if (target.floating) {
if (!this.hasOwnProperty('handles')) {
this.handles = 'n ne e se s sw w nw';
}
}
} else {
me.el = me.target = Ext.get(target);
}
}
// Backwards compatibility with Ext3.x's Resizable which used el as a config.
else {
me.target = me.el = Ext.get(me.el);
}
// Tags like textarea and img cannot
// have children and therefore must
// be wrapped
tag = me.el.dom.tagName;
if (tag == 'TEXTAREA' || tag == 'IMG') {
/**
* Reference to the original resize target if the element of the original
* resize target was an IMG or a TEXTAREA which must be wrapped in a DIV.
* @type Mixed
* @property originalTarget
*/
me.originalTarget = me.target;
me.target = me.el = me.el.wrap({
cls: me.wrapCls,
id: me.el.id + '-rzwrap'
});
// Transfer originalTarget's positioning/sizing
me.el.setPositioning(me.originalTarget.getPositioning());
me.originalTarget.clearPositioning();
var box = me.originalTarget.getBox();
me.el.setBox(box);
}
// Position the element, this enables us to absolute position
// the handles within this.el
me.el.position();
if (me.pinned) {
me.el.addCls(me.pinnedCls);
}
/**
* @type Ext.resizer.ResizeTracker
* @property resizeTracker
*/
me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
disabled: me.disabled,
target: me.target,
constrainTo: me.constrainTo,
overCls: me.overCls,
throttle: me.throttle,
originalTarget: me.originalTarget,
delegate: '.' + me.handleCls,
dynamic: me.dynamic,
preserveRatio: me.preserveRatio,
minHeight: me.minHeight,
maxHeight: me.maxHeight,
minWidth: me.minWidth,
maxWidth: me.maxWidth
});
// Relay the ResizeTracker's superclass events as our own resize events
me.resizeTracker.on('mousedown', me.onBeforeResize, me);
me.resizeTracker.on('drag', me.onResize, me);
me.resizeTracker.on('dragend', me.onResizeEnd, me);
if (me.handles == 'all') {
me.handles = 'n s e w ne nw se sw';
}
handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
possibles = me.possiblePositions;
len = handles.length;
handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';
for(; i < len; i++){
// if specified and possible, create
if (handles[i] && possibles[handles[i]]) {
pos = possibles[handles[i]];
// store a reference in this.east, this.west, etc
me[pos] = Ext.create('Ext.Component', {
owner: this,
region: pos,
cls: handleCls + pos,
renderTo: me.el
});
me[pos].el.unselectable();
if (me.transparent) {
me[pos].el.setOpacity(0);
}
}
}
// Constrain within configured maxima
if (Ext.isNumber(me.width)) {
me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
}
if (Ext.isNumber(me.height)) {
me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
}
// Size the element
if (me.width != null || me.height != null) {
if (me.originalTarget) {
me.originalTarget.setWidth(me.width);
me.originalTarget.setHeight(me.height);
}
me.resizeTo(me.width, me.height);
}
me.forceHandlesHeight();
},
disable: function() {
this.resizeTracker.disable();
},
enable: function() {
this.resizeTracker.enable();
},
/**
* @private Relay the Tracker's mousedown event as beforeresize
* @param tracker The Resizer
* @param e The Event
*/
onBeforeResize: function(tracker, e) {
var b = this.target.getBox();
return this.fireEvent('beforeresize', this, b.width, b.height, e);
},
/**
* @private Relay the Tracker's drag event as resizedrag
* @param tracker The Resizer
* @param e The Event
*/
onResize: function(tracker, e) {
var me = this,
b = me.target.getBox();
me.forceHandlesHeight();
return me.fireEvent('resizedrag', me, b.width, b.height, e);
},
/**
* @private Relay the Tracker's dragend event as resize
* @param tracker The Resizer
* @param e The Event
*/
onResizeEnd: function(tracker, e) {
var me = this,
b = me.target.getBox();
me.forceHandlesHeight();
return me.fireEvent('resize', me, b.width, b.height, e);
},
/**
* Perform a manual resize and fires the 'resize' event.
* @param {Number} width
* @param {Number} height
*/
resizeTo : function(width, height){
this.target.setSize(width, height);
this.fireEvent('resize', this, width, height, null);
},
/**
* <p>Returns the element that was configured with the el or target config property.
* If a component was configured with the target property then this will return the
* element of this component.<p>
* <p>Textarea and img elements will be wrapped with an additional div because
* these elements do not support child nodes. The original element can be accessed
* through the originalTarget property.</p>
* @return {Element} element
*/
getEl : function() {
return this.el;
},
/**
* <p>Returns the element or component that was configured with the target config property.<p>
* <p>Textarea and img elements will be wrapped with an additional div because
* these elements do not support child nodes. The original element can be accessed
* through the originalTarget property.</p>
* @return {Element/Component}
*/
getTarget: function() {
return this.target;
},
destroy: function() {
var h;
for (var i = 0, l = this.handles.length; i < l; i++) {
h = this[this.possiblePositions[this.handles[i]]];
delete h.owner;
Ext.destroy(h);
}
},
/**
* @private
* Fix IE6 handle height issue.
*/
forceHandlesHeight : function() {
var me = this,
handle;
if (Ext.isIE6) {
handle = me.east;
if (handle) {
handle.setHeight(me.el.getHeight());
}
handle = me.west;
if (handle) {
handle.setHeight(me.el.getHeight());
}
me.el.repaint();
}
}
});