Upgrade to ExtJS 3.2.1 - Released 04/27/2010
[extjs.git] / src / widgets / Resizable.js
1 /*!
2  * Ext JS Library 3.2.1
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.Resizable
9  * @extends Ext.util.Observable
10  * <p>Applies drag handles to an element to make it resizable. The drag handles are inserted into the element
11  * and positioned absolute. Some elements, such as a textarea or image, don't support this. To overcome that, you can wrap
12  * the textarea in a div and set 'resizeChild' to true (or to the id of the element), <b>or</b> set wrap:true in your config and
13  * the element will be wrapped for you automatically.</p>
14  * <p>Here is the list of valid resize handles:</p>
15  * <pre>
16 Value   Description
17 ------  -------------------
18  'n'     north
19  's'     south
20  'e'     east
21  'w'     west
22  'nw'    northwest
23  'sw'    southwest
24  'se'    southeast
25  'ne'    northeast
26  'all'   all
27 </pre>
28  * <p>Here's an example showing the creation of a typical Resizable:</p>
29  * <pre><code>
30 var resizer = new Ext.Resizable('element-id', {
31     handles: 'all',
32     minWidth: 200,
33     minHeight: 100,
34     maxWidth: 500,
35     maxHeight: 400,
36     pinned: true
37 });
38 resizer.on('resize', myHandler);
39 </code></pre>
40  * <p>To hide a particular handle, set its display to none in CSS, or through script:<br>
41  * resizer.east.setDisplayed(false);</p>
42  * @constructor
43  * Create a new resizable component
44  * @param {Mixed} el The id or element to resize
45  * @param {Object} config configuration options
46   */
47 Ext.Resizable = Ext.extend(Ext.util.Observable, {
48
49     constructor: function(el, config){
50         this.el = Ext.get(el);
51         if(config && config.wrap){
52             config.resizeChild = this.el;
53             this.el = this.el.wrap(typeof config.wrap == 'object' ? config.wrap : {cls:'xresizable-wrap'});
54             this.el.id = this.el.dom.id = config.resizeChild.id + '-rzwrap';
55             this.el.setStyle('overflow', 'hidden');
56             this.el.setPositioning(config.resizeChild.getPositioning());
57             config.resizeChild.clearPositioning();
58             if(!config.width || !config.height){
59                 var csize = config.resizeChild.getSize();
60                 this.el.setSize(csize.width, csize.height);
61             }
62             if(config.pinned && !config.adjustments){
63                 config.adjustments = 'auto';
64             }
65         }
66
67         /**
68          * The proxy Element that is resized in place of the real Element during the resize operation.
69          * This may be queried using {@link Ext.Element#getBox} to provide the new area to resize to.
70          * Read only.
71          * @type Ext.Element.
72          * @property proxy
73          */
74         this.proxy = this.el.createProxy({tag: 'div', cls: 'x-resizable-proxy', id: this.el.id + '-rzproxy'}, Ext.getBody());
75         this.proxy.unselectable();
76         this.proxy.enableDisplayMode('block');
77
78         Ext.apply(this, config);
79
80         if(this.pinned){
81             this.disableTrackOver = true;
82             this.el.addClass('x-resizable-pinned');
83         }
84         // if the element isn't positioned, make it relative
85         var position = this.el.getStyle('position');
86         if(position != 'absolute' && position != 'fixed'){
87             this.el.setStyle('position', 'relative');
88         }
89         if(!this.handles){ // no handles passed, must be legacy style
90             this.handles = 's,e,se';
91             if(this.multiDirectional){
92                 this.handles += ',n,w';
93             }
94         }
95         if(this.handles == 'all'){
96             this.handles = 'n s e w ne nw se sw';
97         }
98         var hs = this.handles.split(/\s*?[,;]\s*?| /);
99         var ps = Ext.Resizable.positions;
100         for(var i = 0, len = hs.length; i < len; i++){
101             if(hs[i] && ps[hs[i]]){
102                 var pos = ps[hs[i]];
103                 this[pos] = new Ext.Resizable.Handle(this, pos, this.disableTrackOver, this.transparent, this.handleCls);
104             }
105         }
106         // legacy
107         this.corner = this.southeast;
108
109         if(this.handles.indexOf('n') != -1 || this.handles.indexOf('w') != -1){
110             this.updateBox = true;
111         }
112
113         this.activeHandle = null;
114
115         if(this.resizeChild){
116             if(typeof this.resizeChild == 'boolean'){
117                 this.resizeChild = Ext.get(this.el.dom.firstChild, true);
118             }else{
119                 this.resizeChild = Ext.get(this.resizeChild, true);
120             }
121         }
122
123         if(this.adjustments == 'auto'){
124             var rc = this.resizeChild;
125             var hw = this.west, he = this.east, hn = this.north, hs = this.south;
126             if(rc && (hw || hn)){
127                 rc.position('relative');
128                 rc.setLeft(hw ? hw.el.getWidth() : 0);
129                 rc.setTop(hn ? hn.el.getHeight() : 0);
130             }
131             this.adjustments = [
132                 (he ? -he.el.getWidth() : 0) + (hw ? -hw.el.getWidth() : 0),
133                 (hn ? -hn.el.getHeight() : 0) + (hs ? -hs.el.getHeight() : 0) -1
134             ];
135         }
136
137         if(this.draggable){
138             this.dd = this.dynamic ?
139                 this.el.initDD(null) : this.el.initDDProxy(null, {dragElId: this.proxy.id});
140             this.dd.setHandleElId(this.resizeChild ? this.resizeChild.id : this.el.id);
141             if(this.constrainTo){
142                 this.dd.constrainTo(this.constrainTo);
143             }
144         }
145
146         this.addEvents(
147             /**
148              * @event beforeresize
149              * Fired before resize is allowed. Set {@link #enabled} to false to cancel resize.
150              * @param {Ext.Resizable} this
151              * @param {Ext.EventObject} e The mousedown event
152              */
153             'beforeresize',
154             /**
155              * @event resize
156              * Fired after a resize.
157              * @param {Ext.Resizable} this
158              * @param {Number} width The new width
159              * @param {Number} height The new height
160              * @param {Ext.EventObject} e The mouseup event
161              */
162             'resize'
163         );
164
165         if(this.width !== null && this.height !== null){
166             this.resizeTo(this.width, this.height);
167         }else{
168             this.updateChildSize();
169         }
170         if(Ext.isIE){
171             this.el.dom.style.zoom = 1;
172         }
173         Ext.Resizable.superclass.constructor.call(this);
174     },
175
176     /**
177      * @cfg {Array/String} adjustments String 'auto' or an array [width, height] with values to be <b>added</b> to the
178      * resize operation's new size (defaults to <tt>[0, 0]</tt>)
179      */
180     adjustments : [0, 0],
181     /**
182      * @cfg {Boolean} animate True to animate the resize (not compatible with dynamic sizing, defaults to false)
183      */
184     animate : false,
185     /**
186      * @cfg {Mixed} constrainTo Constrain the resize to a particular element
187      */
188     /**
189      * @cfg {Boolean} disableTrackOver True to disable mouse tracking. This is only applied at config time. (defaults to false)
190      */
191     disableTrackOver : false,
192     /**
193      * @cfg {Boolean} draggable Convenience to initialize drag drop (defaults to false)
194      */
195     draggable: false,
196     /**
197      * @cfg {Number} duration Animation duration if animate = true (defaults to 0.35)
198      */
199     duration : 0.35,
200     /**
201      * @cfg {Boolean} dynamic True to resize the element while dragging instead of using a proxy (defaults to false)
202      */
203     dynamic : false,
204     /**
205      * @cfg {String} easing Animation easing if animate = true (defaults to <tt>'easingOutStrong'</tt>)
206      */
207     easing : 'easeOutStrong',
208     /**
209      * @cfg {Boolean} enabled False to disable resizing (defaults to true)
210      */
211     enabled : true,
212     /**
213      * @property enabled Writable. False if resizing is disabled.
214      * @type Boolean
215      */
216     /**
217      * @cfg {String} handles String consisting of the resize handles to display (defaults to undefined).
218      * Specify either <tt>'all'</tt> or any of <tt>'n s e w ne nw se sw'</tt>.
219      */
220     handles : false,
221     /**
222      * @cfg {Boolean} multiDirectional <b>Deprecated</b>.  Deprecated style of adding multi-direction resize handles.
223      */
224     multiDirectional : false,
225     /**
226      * @cfg {Number} height The height of the element in pixels (defaults to null)
227      */
228     height : null,
229     /**
230      * @cfg {Number} width The width of the element in pixels (defaults to null)
231      */
232     width : null,
233     /**
234      * @cfg {Number} heightIncrement The increment to snap the height resize in pixels
235      * (only applies if <code>{@link #dynamic}==true</code>). Defaults to <tt>0</tt>.
236      */
237     heightIncrement : 0,
238     /**
239      * @cfg {Number} widthIncrement The increment to snap the width resize in pixels
240      * (only applies if <code>{@link #dynamic}==true</code>). Defaults to <tt>0</tt>.
241      */
242     widthIncrement : 0,
243     /**
244      * @cfg {Number} minHeight The minimum height for the element (defaults to 5)
245      */
246     minHeight : 5,
247     /**
248      * @cfg {Number} minWidth The minimum width for the element (defaults to 5)
249      */
250     minWidth : 5,
251     /**
252      * @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
253      */
254     maxHeight : 10000,
255     /**
256      * @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
257      */
258     maxWidth : 10000,
259     /**
260      * @cfg {Number} minX The minimum x for the element (defaults to 0)
261      */
262     minX: 0,
263     /**
264      * @cfg {Number} minY The minimum x for the element (defaults to 0)
265      */
266     minY: 0,
267     /**
268      * @cfg {Boolean} pinned True to ensure that the resize handles are always visible, false to display them only when the
269      * user mouses over the resizable borders. This is only applied at config time. (defaults to false)
270      */
271     pinned : false,
272     /**
273      * @cfg {Boolean} preserveRatio True to preserve the original ratio between height
274      * and width during resize (defaults to false)
275      */
276     preserveRatio : false,
277     /**
278      * @cfg {Boolean/String/Element} resizeChild True to resize the first child, or id/element to resize (defaults to false)
279      */
280     resizeChild : false,
281     /**
282      * @cfg {Boolean} transparent True for transparent handles. This is only applied at config time. (defaults to false)
283      */
284     transparent: false,
285     /**
286      * @cfg {Ext.lib.Region} resizeRegion Constrain the resize to a particular region
287      */
288     /**
289      * @cfg {Boolean} wrap True to wrap an element with a div if needed (required for textareas and images, defaults to false)
290      * in favor of the handles config option (defaults to false)
291      */
292     /**
293      * @cfg {String} handleCls A css class to add to each handle. Defaults to <tt>''</tt>.
294      */
295
296
297     /**
298      * Perform a manual resize and fires the 'resize' event.
299      * @param {Number} width
300      * @param {Number} height
301      */
302     resizeTo : function(width, height){
303         this.el.setSize(width, height);
304         this.updateChildSize();
305         this.fireEvent('resize', this, width, height, null);
306     },
307
308     // private
309     startSizing : function(e, handle){
310         this.fireEvent('beforeresize', this, e);
311         if(this.enabled){ // 2nd enabled check in case disabled before beforeresize handler
312
313             if(!this.overlay){
314                 this.overlay = this.el.createProxy({tag: 'div', cls: 'x-resizable-overlay', html: '&#160;'}, Ext.getBody());
315                 this.overlay.unselectable();
316                 this.overlay.enableDisplayMode('block');
317                 this.overlay.on({
318                     scope: this,
319                     mousemove: this.onMouseMove,
320                     mouseup: this.onMouseUp
321                 });
322             }
323             this.overlay.setStyle('cursor', handle.el.getStyle('cursor'));
324
325             this.resizing = true;
326             this.startBox = this.el.getBox();
327             this.startPoint = e.getXY();
328             this.offsets = [(this.startBox.x + this.startBox.width) - this.startPoint[0],
329                             (this.startBox.y + this.startBox.height) - this.startPoint[1]];
330
331             this.overlay.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
332             this.overlay.show();
333
334             if(this.constrainTo) {
335                 var ct = Ext.get(this.constrainTo);
336                 this.resizeRegion = ct.getRegion().adjust(
337                     ct.getFrameWidth('t'),
338                     ct.getFrameWidth('l'),
339                     -ct.getFrameWidth('b'),
340                     -ct.getFrameWidth('r')
341                 );
342             }
343
344             this.proxy.setStyle('visibility', 'hidden'); // workaround display none
345             this.proxy.show();
346             this.proxy.setBox(this.startBox);
347             if(!this.dynamic){
348                 this.proxy.setStyle('visibility', 'visible');
349             }
350         }
351     },
352
353     // private
354     onMouseDown : function(handle, e){
355         if(this.enabled){
356             e.stopEvent();
357             this.activeHandle = handle;
358             this.startSizing(e, handle);
359         }
360     },
361
362     // private
363     onMouseUp : function(e){
364         this.activeHandle = null;
365         var size = this.resizeElement();
366         this.resizing = false;
367         this.handleOut();
368         this.overlay.hide();
369         this.proxy.hide();
370         this.fireEvent('resize', this, size.width, size.height, e);
371     },
372
373     // private
374     updateChildSize : function(){
375         if(this.resizeChild){
376             var el = this.el;
377             var child = this.resizeChild;
378             var adj = this.adjustments;
379             if(el.dom.offsetWidth){
380                 var b = el.getSize(true);
381                 child.setSize(b.width+adj[0], b.height+adj[1]);
382             }
383             // Second call here for IE
384             // The first call enables instant resizing and
385             // the second call corrects scroll bars if they
386             // exist
387             if(Ext.isIE){
388                 setTimeout(function(){
389                     if(el.dom.offsetWidth){
390                         var b = el.getSize(true);
391                         child.setSize(b.width+adj[0], b.height+adj[1]);
392                     }
393                 }, 10);
394             }
395         }
396     },
397
398     // private
399     snap : function(value, inc, min){
400         if(!inc || !value){
401             return value;
402         }
403         var newValue = value;
404         var m = value % inc;
405         if(m > 0){
406             if(m > (inc/2)){
407                 newValue = value + (inc-m);
408             }else{
409                 newValue = value - m;
410             }
411         }
412         return Math.max(min, newValue);
413     },
414
415     /**
416      * <p>Performs resizing of the associated Element. This method is called internally by this
417      * class, and should not be called by user code.</p>
418      * <p>If a Resizable is being used to resize an Element which encapsulates a more complex UI
419      * component such as a Panel, this method may be overridden by specifying an implementation
420      * as a config option to provide appropriate behaviour at the end of the resize operation on
421      * mouseup, for example resizing the Panel, and relaying the Panel's content.</p>
422      * <p>The new area to be resized to is available by examining the state of the {@link #proxy}
423      * Element. Example:
424 <pre><code>
425 new Ext.Panel({
426     title: 'Resize me',
427     x: 100,
428     y: 100,
429     renderTo: Ext.getBody(),
430     floating: true,
431     frame: true,
432     width: 400,
433     height: 200,
434     listeners: {
435         render: function(p) {
436             new Ext.Resizable(p.getEl(), {
437                 handles: 'all',
438                 pinned: true,
439                 transparent: true,
440                 resizeElement: function() {
441                     var box = this.proxy.getBox();
442                     p.updateBox(box);
443                     if (p.layout) {
444                         p.doLayout();
445                     }
446                     return box;
447                 }
448            });
449        }
450     }
451 }).show();
452 </code></pre>
453      */
454     resizeElement : function(){
455         var box = this.proxy.getBox();
456         if(this.updateBox){
457             this.el.setBox(box, false, this.animate, this.duration, null, this.easing);
458         }else{
459             this.el.setSize(box.width, box.height, this.animate, this.duration, null, this.easing);
460         }
461         this.updateChildSize();
462         if(!this.dynamic){
463             this.proxy.hide();
464         }
465         if(this.draggable && this.constrainTo){
466             this.dd.resetConstraints();
467             this.dd.constrainTo(this.constrainTo);
468         }
469         return box;
470     },
471
472     // private
473     constrain : function(v, diff, m, mx){
474         if(v - diff < m){
475             diff = v - m;
476         }else if(v - diff > mx){
477             diff = v - mx;
478         }
479         return diff;
480     },
481
482     // private
483     onMouseMove : function(e){
484         if(this.enabled && this.activeHandle){
485             try{// try catch so if something goes wrong the user doesn't get hung
486
487             if(this.resizeRegion && !this.resizeRegion.contains(e.getPoint())) {
488                 return;
489             }
490
491             //var curXY = this.startPoint;
492             var curSize = this.curSize || this.startBox,
493                 x = this.startBox.x, y = this.startBox.y,
494                 ox = x,
495                 oy = y,
496                 w = curSize.width,
497                 h = curSize.height,
498                 ow = w,
499                 oh = h,
500                 mw = this.minWidth,
501                 mh = this.minHeight,
502                 mxw = this.maxWidth,
503                 mxh = this.maxHeight,
504                 wi = this.widthIncrement,
505                 hi = this.heightIncrement,
506                 eventXY = e.getXY(),
507                 diffX = -(this.startPoint[0] - Math.max(this.minX, eventXY[0])),
508                 diffY = -(this.startPoint[1] - Math.max(this.minY, eventXY[1])),
509                 pos = this.activeHandle.position,
510                 tw,
511                 th;
512
513             switch(pos){
514                 case 'east':
515                     w += diffX;
516                     w = Math.min(Math.max(mw, w), mxw);
517                     break;
518                 case 'south':
519                     h += diffY;
520                     h = Math.min(Math.max(mh, h), mxh);
521                     break;
522                 case 'southeast':
523                     w += diffX;
524                     h += diffY;
525                     w = Math.min(Math.max(mw, w), mxw);
526                     h = Math.min(Math.max(mh, h), mxh);
527                     break;
528                 case 'north':
529                     diffY = this.constrain(h, diffY, mh, mxh);
530                     y += diffY;
531                     h -= diffY;
532                     break;
533                 case 'west':
534                     diffX = this.constrain(w, diffX, mw, mxw);
535                     x += diffX;
536                     w -= diffX;
537                     break;
538                 case 'northeast':
539                     w += diffX;
540                     w = Math.min(Math.max(mw, w), mxw);
541                     diffY = this.constrain(h, diffY, mh, mxh);
542                     y += diffY;
543                     h -= diffY;
544                     break;
545                 case 'northwest':
546                     diffX = this.constrain(w, diffX, mw, mxw);
547                     diffY = this.constrain(h, diffY, mh, mxh);
548                     y += diffY;
549                     h -= diffY;
550                     x += diffX;
551                     w -= diffX;
552                     break;
553                case 'southwest':
554                     diffX = this.constrain(w, diffX, mw, mxw);
555                     h += diffY;
556                     h = Math.min(Math.max(mh, h), mxh);
557                     x += diffX;
558                     w -= diffX;
559                     break;
560             }
561
562             var sw = this.snap(w, wi, mw);
563             var sh = this.snap(h, hi, mh);
564             if(sw != w || sh != h){
565                 switch(pos){
566                     case 'northeast':
567                         y -= sh - h;
568                     break;
569                     case 'north':
570                         y -= sh - h;
571                         break;
572                     case 'southwest':
573                         x -= sw - w;
574                     break;
575                     case 'west':
576                         x -= sw - w;
577                         break;
578                     case 'northwest':
579                         x -= sw - w;
580                         y -= sh - h;
581                     break;
582                 }
583                 w = sw;
584                 h = sh;
585             }
586
587             if(this.preserveRatio){
588                 switch(pos){
589                     case 'southeast':
590                     case 'east':
591                         h = oh * (w/ow);
592                         h = Math.min(Math.max(mh, h), mxh);
593                         w = ow * (h/oh);
594                        break;
595                     case 'south':
596                         w = ow * (h/oh);
597                         w = Math.min(Math.max(mw, w), mxw);
598                         h = oh * (w/ow);
599                         break;
600                     case 'northeast':
601                         w = ow * (h/oh);
602                         w = Math.min(Math.max(mw, w), mxw);
603                         h = oh * (w/ow);
604                     break;
605                     case 'north':
606                         tw = w;
607                         w = ow * (h/oh);
608                         w = Math.min(Math.max(mw, w), mxw);
609                         h = oh * (w/ow);
610                         x += (tw - w) / 2;
611                         break;
612                     case 'southwest':
613                         h = oh * (w/ow);
614                         h = Math.min(Math.max(mh, h), mxh);
615                         tw = w;
616                         w = ow * (h/oh);
617                         x += tw - w;
618                         break;
619                     case 'west':
620                         th = h;
621                         h = oh * (w/ow);
622                         h = Math.min(Math.max(mh, h), mxh);
623                         y += (th - h) / 2;
624                         tw = w;
625                         w = ow * (h/oh);
626                         x += tw - w;
627                        break;
628                     case 'northwest':
629                         tw = w;
630                         th = h;
631                         h = oh * (w/ow);
632                         h = Math.min(Math.max(mh, h), mxh);
633                         w = ow * (h/oh);
634                         y += th - h;
635                         x += tw - w;
636                         break;
637
638                 }
639             }
640             this.proxy.setBounds(x, y, w, h);
641             if(this.dynamic){
642                 this.resizeElement();
643             }
644             }catch(ex){}
645         }
646     },
647
648     // private
649     handleOver : function(){
650         if(this.enabled){
651             this.el.addClass('x-resizable-over');
652         }
653     },
654
655     // private
656     handleOut : function(){
657         if(!this.resizing){
658             this.el.removeClass('x-resizable-over');
659         }
660     },
661
662     /**
663      * Returns the element this component is bound to.
664      * @return {Ext.Element}
665      */
666     getEl : function(){
667         return this.el;
668     },
669
670     /**
671      * Returns the resizeChild element (or null).
672      * @return {Ext.Element}
673      */
674     getResizeChild : function(){
675         return this.resizeChild;
676     },
677
678     /**
679      * Destroys this resizable. If the element was wrapped and
680      * removeEl is not true then the element remains.
681      * @param {Boolean} removeEl (optional) true to remove the element from the DOM
682      */
683     destroy : function(removeEl){
684         Ext.destroy(this.dd, this.overlay, this.proxy);
685         this.overlay = null;
686         this.proxy = null;
687
688         var ps = Ext.Resizable.positions;
689         for(var k in ps){
690             if(typeof ps[k] != 'function' && this[ps[k]]){
691                 this[ps[k]].destroy();
692             }
693         }
694         if(removeEl){
695             this.el.update('');
696             Ext.destroy(this.el);
697             this.el = null;
698         }
699         this.purgeListeners();
700     },
701
702     syncHandleHeight : function(){
703         var h = this.el.getHeight(true);
704         if(this.west){
705             this.west.el.setHeight(h);
706         }
707         if(this.east){
708             this.east.el.setHeight(h);
709         }
710     }
711 });
712
713 // private
714 // hash to map config positions to true positions
715 Ext.Resizable.positions = {
716     n: 'north', s: 'south', e: 'east', w: 'west', se: 'southeast', sw: 'southwest', nw: 'northwest', ne: 'northeast'
717 };
718
719 Ext.Resizable.Handle = Ext.extend(Object, {
720     constructor : function(rz, pos, disableTrackOver, transparent, cls){
721        if(!this.tpl){
722             // only initialize the template if resizable is used
723             var tpl = Ext.DomHelper.createTemplate(
724                 {tag: 'div', cls: 'x-resizable-handle x-resizable-handle-{0}'}
725             );
726             tpl.compile();
727             Ext.Resizable.Handle.prototype.tpl = tpl;
728         }
729         this.position = pos;
730         this.rz = rz;
731         this.el = this.tpl.append(rz.el.dom, [this.position], true);
732         this.el.unselectable();
733         if(transparent){
734             this.el.setOpacity(0);
735         }
736         if(!Ext.isEmpty(cls)){
737             this.el.addClass(cls);
738         }
739         this.el.on('mousedown', this.onMouseDown, this);
740         if(!disableTrackOver){
741             this.el.on({
742                 scope: this,
743                 mouseover: this.onMouseOver,
744                 mouseout: this.onMouseOut
745             });
746         }
747     },
748
749     // private
750     afterResize : function(rz){
751         // do nothing
752     },
753     // private
754     onMouseDown : function(e){
755         this.rz.onMouseDown(this, e);
756     },
757     // private
758     onMouseOver : function(e){
759         this.rz.handleOver(this, e);
760     },
761     // private
762     onMouseOut : function(e){
763         this.rz.handleOut(this, e);
764     },
765     // private
766     destroy : function(){
767         Ext.destroy(this.el);
768         this.el = null;
769     }
770 });