Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / resizer / Resizer.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * Applies drag handles to an element or component to make it resizable. The drag handles are inserted into the element
17  * (or component's element) and positioned absolute.
18  *
19  * Textarea and img elements will be wrapped with an additional div because these elements do not support child nodes.
20  * The original element can be accessed through the originalTarget property.
21  *
22  * Here is the list of valid resize handles:
23  *
24  *     Value   Description
25  *     ------  -------------------
26  *      'n'     north
27  *      's'     south
28  *      'e'     east
29  *      'w'     west
30  *      'nw'    northwest
31  *      'sw'    southwest
32  *      'se'    southeast
33  *      'ne'    northeast
34  *      'all'   all
35  *
36  * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
37  *
38  * Here's an example showing the creation of a typical Resizer:
39  *
40  *     Ext.create('Ext.resizer.Resizer', {
41  *         el: 'elToResize',
42  *         handles: 'all',
43  *         minWidth: 200,
44  *         minHeight: 100,
45  *         maxWidth: 500,
46  *         maxHeight: 400,
47  *         pinned: true
48  *     });
49  */
50 Ext.define('Ext.resizer.Resizer', {
51     mixins: {
52         observable: 'Ext.util.Observable'
53     },
54     uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],
55
56     alternateClassName: 'Ext.Resizable',
57
58     handleCls: Ext.baseCSSPrefix + 'resizable-handle',
59     pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
60     overCls:   Ext.baseCSSPrefix + 'resizable-over',
61     wrapCls:   Ext.baseCSSPrefix + 'resizable-wrap',
62
63     /**
64      * @cfg {Boolean} dynamic
65      * Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during
66      * dragging. This is `true` by default, but the {@link Ext.Component Component} class passes `false` when it is
67      * configured as {@link Ext.Component#resizable}.
68      *
69      * If specified as `false`, a proxy element is displayed during the resize operation, and the {@link #target} is
70      * updated on mouseup.
71      */
72     dynamic: true,
73
74     /**
75      * @cfg {String} handles
76      * String consisting of the resize handles to display. Defaults to 's e se' for Elements and fixed position
77      * Components. Defaults to 8 point resizing for floating Components (such as Windows). Specify either `'all'` or any
78      * of `'n s e w ne nw se sw'`.
79      */
80     handles: 's e se',
81
82     /**
83      * @cfg {Number} height
84      * Optional. The height to set target to in pixels
85      */
86     height : null,
87
88     /**
89      * @cfg {Number} width
90      * Optional. The width to set the target to in pixels
91      */
92     width : null,
93
94     /**
95      * @cfg {Number} heightIncrement
96      * The increment to snap the height resize in pixels.
97      */
98     heightIncrement : 0,
99
100     /**
101      * @cfg {Number} widthIncrement
102      * The increment to snap the width resize in pixels.
103      */
104     widthIncrement : 0,
105
106     /**
107      * @cfg {Number} minHeight
108      * The minimum height for the element
109      */
110     minHeight : 20,
111
112     /**
113      * @cfg {Number} minWidth
114      * The minimum width for the element
115      */
116     minWidth : 20,
117
118     /**
119      * @cfg {Number} maxHeight
120      * The maximum height for the element
121      */
122     maxHeight : 10000,
123
124     /**
125      * @cfg {Number} maxWidth
126      * The maximum width for the element
127      */
128     maxWidth : 10000,
129
130     /**
131      * @cfg {Boolean} pinned
132      * True to ensure that the resize handles are always visible, false indicates resizing by cursor changes only
133      */
134     pinned: false,
135
136     /**
137      * @cfg {Boolean} preserveRatio
138      * True to preserve the original ratio between height and width during resize
139      */
140     preserveRatio: false,
141
142     /**
143      * @cfg {Boolean} transparent
144      * True for transparent handles. This is only applied at config time.
145      */
146     transparent: false,
147
148     /**
149      * @cfg {Ext.Element/Ext.util.Region} constrainTo
150      * An element, or a {@link Ext.util.Region Region} into which the resize operation must be constrained.
151      */
152
153     possiblePositions: {
154         n:  'north',
155         s:  'south',
156         e:  'east',
157         w:  'west',
158         se: 'southeast',
159         sw: 'southwest',
160         nw: 'northwest',
161         ne: 'northeast'
162     },
163
164     /**
165      * @cfg {Ext.Element/Ext.Component} target
166      * The Element or Component to resize.
167      */
168
169     /**
170      * @property {Ext.Element} el
171      * Outer element for resizing behavior.
172      */
173
174     constructor: function(config) {
175         var me = this,
176             target,
177             tag,
178             handles = me.handles,
179             handleCls,
180             possibles,
181             len,
182             i = 0,
183             pos;
184
185         this.addEvents(
186             /**
187              * @event beforeresize
188              * Fired before resize is allowed. Return false to cancel resize.
189              * @param {Ext.resizer.Resizer} this
190              * @param {Number} width The start width
191              * @param {Number} height The start height
192              * @param {Ext.EventObject} e The mousedown event
193              */
194             'beforeresize',
195             /**
196              * @event resizedrag
197              * Fires during resizing. Return false to cancel resize.
198              * @param {Ext.resizer.Resizer} this
199              * @param {Number} width The new width
200              * @param {Number} height The new height
201              * @param {Ext.EventObject} e The mousedown event
202              */
203             'resizedrag',
204             /**
205              * @event resize
206              * Fired after a resize.
207              * @param {Ext.resizer.Resizer} this
208              * @param {Number} width The new width
209              * @param {Number} height The new height
210              * @param {Ext.EventObject} e The mouseup event
211              */
212             'resize'
213         );
214
215         if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
216             target = config;
217             config = arguments[1] || {};
218             config.target = target;
219         }
220         // will apply config to this
221         me.mixins.observable.constructor.call(me, config);
222
223         // If target is a Component, ensure that we pull the element out.
224         // Resizer must examine the underlying Element.
225         target = me.target;
226         if (target) {
227             if (target.isComponent) {
228                 me.el = target.getEl();
229                 if (target.minWidth) {
230                     me.minWidth = target.minWidth;
231                 }
232                 if (target.minHeight) {
233                     me.minHeight = target.minHeight;
234                 }
235                 if (target.maxWidth) {
236                     me.maxWidth = target.maxWidth;
237                 }
238                 if (target.maxHeight) {
239                     me.maxHeight = target.maxHeight;
240                 }
241                 if (target.floating) {
242                     if (!this.hasOwnProperty('handles')) {
243                         this.handles = 'n ne e se s sw w nw';
244                     }
245                 }
246             } else {
247                 me.el = me.target = Ext.get(target);
248             }
249         }
250         // Backwards compatibility with Ext3.x's Resizable which used el as a config.
251         else {
252             me.target = me.el = Ext.get(me.el);
253         }
254
255         // Tags like textarea and img cannot
256         // have children and therefore must
257         // be wrapped
258         tag = me.el.dom.tagName;
259         if (tag == 'TEXTAREA' || tag == 'IMG') {
260             /**
261              * @property {Ext.Element/Ext.Component} originalTarget
262              * Reference to the original resize target if the element of the original resize target was an IMG or a
263              * TEXTAREA which must be wrapped in a DIV.
264              */
265             me.originalTarget = me.target;
266             me.target = me.el = me.el.wrap({
267                 cls: me.wrapCls,
268                 id: me.el.id + '-rzwrap'
269             });
270
271             // Transfer originalTarget's positioning/sizing
272             me.el.setPositioning(me.originalTarget.getPositioning());
273             me.originalTarget.clearPositioning();
274             var box = me.originalTarget.getBox();
275             me.el.setBox(box);
276         }
277
278         // Position the element, this enables us to absolute position
279         // the handles within this.el
280         me.el.position();
281         if (me.pinned) {
282             me.el.addCls(me.pinnedCls);
283         }
284
285         /**
286          * @property {Ext.resizer.ResizeTracker} resizeTracker
287          */
288         me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
289             disabled: me.disabled,
290             target: me.target,
291             constrainTo: me.constrainTo,
292             overCls: me.overCls,
293             throttle: me.throttle,
294             originalTarget: me.originalTarget,
295             delegate: '.' + me.handleCls,
296             dynamic: me.dynamic,
297             preserveRatio: me.preserveRatio,
298             heightIncrement: me.heightIncrement,
299             widthIncrement: me.widthIncrement,
300             minHeight: me.minHeight,
301             maxHeight: me.maxHeight,
302             minWidth: me.minWidth,
303             maxWidth: me.maxWidth
304         });
305
306         // Relay the ResizeTracker's superclass events as our own resize events
307         me.resizeTracker.on('mousedown', me.onBeforeResize, me);
308         me.resizeTracker.on('drag', me.onResize, me);
309         me.resizeTracker.on('dragend', me.onResizeEnd, me);
310
311         if (me.handles == 'all') {
312             me.handles = 'n s e w ne nw se sw';
313         }
314
315         handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
316         possibles = me.possiblePositions;
317         len = handles.length;
318         handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';
319
320         for(; i < len; i++){
321             // if specified and possible, create
322             if (handles[i] && possibles[handles[i]]) {
323                 pos = possibles[handles[i]];
324                 // store a reference in this.east, this.west, etc
325
326                 me[pos] = Ext.create('Ext.Component', {
327                     owner: this,
328                     region: pos,
329                     cls: handleCls + pos,
330                     renderTo: me.el
331                 });
332                 me[pos].el.unselectable();
333                 if (me.transparent) {
334                     me[pos].el.setOpacity(0);
335                 }
336             }
337         }
338
339         // Constrain within configured maxima
340         if (Ext.isNumber(me.width)) {
341             me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
342         }
343         if (Ext.isNumber(me.height)) {
344             me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
345         }
346
347         // Size the element
348         if (me.width != null || me.height != null) {
349             if (me.originalTarget) {
350                 me.originalTarget.setWidth(me.width);
351                 me.originalTarget.setHeight(me.height);
352             }
353             me.resizeTo(me.width, me.height);
354         }
355
356         me.forceHandlesHeight();
357     },
358
359     disable: function() {
360         this.resizeTracker.disable();
361     },
362
363     enable: function() {
364         this.resizeTracker.enable();
365     },
366
367     /**
368      * @private Relay the Tracker's mousedown event as beforeresize
369      * @param tracker The Resizer
370      * @param e The Event
371      */
372     onBeforeResize: function(tracker, e) {
373         var b = this.target.getBox();
374         return this.fireEvent('beforeresize', this, b.width, b.height, e);
375     },
376
377     /**
378      * @private Relay the Tracker's drag event as resizedrag
379      * @param tracker The Resizer
380      * @param e The Event
381      */
382     onResize: function(tracker, e) {
383         var me = this,
384             b = me.target.getBox();
385         me.forceHandlesHeight();
386         return me.fireEvent('resizedrag', me, b.width, b.height, e);
387     },
388
389     /**
390      * @private Relay the Tracker's dragend event as resize
391      * @param tracker The Resizer
392      * @param e The Event
393      */
394     onResizeEnd: function(tracker, e) {
395         var me = this,
396             b = me.target.getBox();
397         me.forceHandlesHeight();
398         return me.fireEvent('resize', me, b.width, b.height, e);
399     },
400
401     /**
402      * Perform a manual resize and fires the 'resize' event.
403      * @param {Number} width
404      * @param {Number} height
405      */
406     resizeTo : function(width, height){
407         this.target.setSize(width, height);
408         this.fireEvent('resize', this, width, height, null);
409     },
410
411     /**
412      * Returns the element that was configured with the el or target config property. If a component was configured with
413      * the target property then this will return the element of this component.
414      *
415      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
416      * nodes. The original element can be accessed through the originalTarget property.
417      * @return {Ext.Element} element
418      */
419     getEl : function() {
420         return this.el;
421     },
422
423     /**
424      * Returns the element or component that was configured with the target config property.
425      *
426      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
427      * nodes. The original element can be accessed through the originalTarget property.
428      * @return {Ext.Element/Ext.Component}
429      */
430     getTarget: function() {
431         return this.target;
432     },
433
434     destroy: function() {
435         var h;
436         for (var i = 0, l = this.handles.length; i < l; i++) {
437             h = this[this.possiblePositions[this.handles[i]]];
438             delete h.owner;
439             Ext.destroy(h);
440         }
441     },
442
443     /**
444      * @private
445      * Fix IE6 handle height issue.
446      */
447     forceHandlesHeight : function() {
448         var me = this,
449             handle;
450         if (Ext.isIE6) {
451             handle = me.east;
452             if (handle) {
453                 handle.setHeight(me.el.getHeight());
454             }
455             handle = me.west;
456             if (handle) {
457                 handle.setHeight(me.el.getHeight());
458             }
459             me.el.repaint();
460         }
461     }
462 });
463