/** * 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. * * 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. * * Here is the list of valid resize handles: * * Value Description * ------ ------------------- * 'n' north * 's' south * 'e' east * 'w' west * 'nw' northwest * 'sw' southwest * 'se' southeast * 'ne' northeast * 'all' all * * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component} * * Here's an example showing the creation of a typical Resizer: * * Ext.create('Ext.resizer.Resizer', { * el: 'elToResize', * handles: 'all', * minWidth: 200, * minHeight: 100, * maxWidth: 500, * maxHeight: 400, * pinned: true * }); */ 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', wrapCls: Ext.baseCSSPrefix + 'resizable-wrap', /** * @cfg {Boolean} dynamic * Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during * dragging. This is `true` by default, but the {@link Ext.Component Component} class passes `false` when it is * configured as {@link Ext.Component#resizable}. * * If specified as `false`, a proxy element is displayed during the resize operation, and the {@link #target} is * updated on mouseup. */ 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 `'all'` or any * of `'n s e w ne nw se sw'`. */ handles: 's e se', /** * @cfg {Number} height * Optional. The height to set target to in pixels */ height : null, /** * @cfg {Number} width * Optional. The width to set the target to in pixels */ width : null, /** * @cfg {Number} heightIncrement * The increment to snap the height resize in pixels. */ heightIncrement : 0, /** * @cfg {Number} widthIncrement * The increment to snap the width resize in pixels. */ widthIncrement : 0, /** * @cfg {Number} minHeight * The minimum height for the element */ minHeight : 20, /** * @cfg {Number} minWidth * The minimum width for the element */ minWidth : 20, /** * @cfg {Number} maxHeight * The maximum height for the element */ maxHeight : 10000, /** * @cfg {Number} maxWidth * The maximum width for the element */ maxWidth : 10000, /** * @cfg {Boolean} pinned * True to ensure that the resize handles are always visible, false indicates resizing by cursor changes only */ pinned: false, /** * @cfg {Boolean} preserveRatio * True to preserve the original ratio between height and width during resize */ preserveRatio: false, /** * @cfg {Boolean} transparent * True for transparent handles. This is only applied at config time. */ transparent: false, /** * @cfg {Ext.Element/Ext.util.Region} constrainTo * An element, or a {@link Ext.util.Region 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 {Ext.Element/Ext.Component} target * The Element or Component to resize. */ /** * @property {Ext.Element} el * Outer element for resizing behavior. */ constructor: function(config) { var me = this, target, targetEl, tag, handles = me.handles, handleCls, possibles, len, i = 0, pos, handleEls = [], eastWestStyle, style, box; me.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 (!me.hasOwnProperty('handles')) { me.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.toUpperCase(); if (tag == 'TEXTAREA' || tag == 'IMG' || tag == 'TABLE') { /** * @property {Ext.Element/Ext.Component} originalTarget * Reference to the original resize target if the element of the original resize target was a * {@link Ext.form.field.Field Field}, or an IMG or a TEXTAREA which must be wrapped in a DIV. */ me.originalTarget = me.target; targetEl = me.el; box = targetEl.getBox(); me.target = me.el = me.el.wrap({ cls: me.wrapCls, id: me.el.id + '-rzwrap', style: targetEl.getStyles('margin-top', 'margin-bottom') }); // Transfer originalTarget's positioning+sizing+margins me.el.setPositioning(targetEl.getPositioning()); targetEl.clearPositioning(); me.el.setBox(box); // Position the wrapped element absolute so that it does not stretch the wrapper targetEl.setStyle('position', 'absolute'); } // 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); } /** * @property {Ext.resizer.ResizeTracker} resizeTracker */ me.resizeTracker = new 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, heightIncrement: me.heightIncrement, widthIncrement: me.widthIncrement, 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, drag: me.onResize, dragend: me.onResizeEnd, scope: 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 + ' ' + (me.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-'; // Needs heighting on IE6! eastWestStyle = Ext.isIE6 ? ' style="height:' + me.el.getHeight() + 'px"' : ''; for (; i < len; i++){ // if specified and possible, create if (handles[i] && possibles[handles[i]]) { pos = possibles[handles[i]]; if (pos === 'east' || pos === 'west') { style = eastWestStyle; } else { style = ''; } handleEls.push('
'); } } Ext.DomHelper.append(me.el, handleEls.join('')); // store a reference to each handle elelemtn in this.east, this.west, etc for (i = 0; i < len; i++){ // if specified and possible, create if (handles[i] && possibles[handles[i]]) { pos = possibles[handles[i]]; me[pos] = me.el.getById(me.el.id + '-' + pos + '-handle'); me[pos].region = pos; me[pos].unselectable(); if (me.transparent) { me[pos].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 target (and originalTarget) 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 box = this.el.getBox(); return this.fireEvent('beforeresize', this, box.width, box.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, box = me.el.getBox(); me.forceHandlesHeight(); return me.fireEvent('resizedrag', me, box.width, box.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, box = me.el.getBox(); me.forceHandlesHeight(); return me.fireEvent('resize', me, box.width, box.height, e); }, /** * Perform a manual resize and fires the 'resize' event. * @param {Number} width * @param {Number} height */ resizeTo : function(width, height) { var me = this; me.target.setSize(width, height); me.fireEvent('resize', me, width, height, null); }, /** * 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. * * 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. * @return {Ext.Element} element */ getEl : function() { return this.el; }, /** * Returns the element or component that was configured with the target config property. * * 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. * @return {Ext.Element/Ext.Component} */ getTarget: function() { return this.target; }, destroy: function() { var i = 0, handles = this.handles, len = handles.length, positions = this.possiblePositions; for (; i < len; i++) { this[positions[handles[i]]].remove(); } }, /** * @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(); } } });