/** * @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(); } } });