Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / src / core / src / dom / Element.style.js
1 /**
2  * @class Ext.core.Element
3  */
4 (function(){
5     Ext.core.Element.boxMarkup = '<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div><div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div><div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';
6     // local style camelizing for speed
7     var supports = Ext.supports,
8         view = document.defaultView,
9         opacityRe = /alpha\(opacity=(.*)\)/i,
10         trimRe = /^\s+|\s+$/g,
11         spacesRe = /\s+/,
12         wordsRe = /\w/g,
13         adjustDirect2DTableRe = /table-row|table-.*-group/,
14         INTERNAL = '_internal',
15         PADDING = 'padding',
16         MARGIN = 'margin',
17         BORDER = 'border',
18         LEFT = '-left',
19         RIGHT = '-right',
20         TOP = '-top',
21         BOTTOM = '-bottom',
22         WIDTH = '-width',
23         MATH = Math,
24         HIDDEN = 'hidden',
25         ISCLIPPED = 'isClipped',
26         OVERFLOW = 'overflow',
27         OVERFLOWX = 'overflow-x',
28         OVERFLOWY = 'overflow-y',
29         ORIGINALCLIP = 'originalClip',
30         // special markup used throughout Ext when box wrapping elements
31         borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
32         paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
33         margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
34         data = Ext.core.Element.data;
35
36     Ext.override(Ext.core.Element, {
37         
38         /**
39          * TODO: Look at this
40          */
41         // private  ==> used by Fx
42         adjustWidth : function(width) {
43             var me = this,
44                 isNum = (typeof width == 'number');
45                 
46             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
47                width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
48             }
49             return (isNum && width < 0) ? 0 : width;
50         },
51
52         // private   ==> used by Fx
53         adjustHeight : function(height) {
54             var me = this,
55                 isNum = (typeof height == "number");
56                 
57             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
58                height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
59             }
60             return (isNum && height < 0) ? 0 : height;
61         },
62
63
64         /**
65          * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
66          * @param {String/Array} className The CSS classes to add separated by space, or an array of classes
67          * @return {Ext.core.Element} this
68          */
69         addCls : function(className){
70             var me = this,
71                 cls = [],
72                 space = ((me.dom.className.replace(trimRe, '') == '') ? "" : " "),
73                 i, len, v;
74             if (!Ext.isDefined(className)) {
75                 return me;
76             }
77             // Separate case is for speed
78             if (!Ext.isArray(className)) {
79                 if (typeof className === 'string') {
80                     className = className.replace(trimRe, '').split(spacesRe);
81                     if (className.length === 1) {
82                         className = className[0];
83                         if (!me.hasCls(className)) {
84                             me.dom.className += space + className;
85                         }
86                     } else {
87                         this.addCls(className);
88                     }
89                 }
90             } else {
91                 for (i = 0, len = className.length; i < len; i++) {
92                     v = className[i];
93                     if (typeof v == 'string' && (' ' + me.dom.className + ' ').indexOf(' ' + v + ' ') == -1) {
94                         cls.push(v);
95                     }
96                 }
97                 if (cls.length) {
98                     me.dom.className += space + cls.join(" ");
99                 }
100             }
101             return me;
102         },
103
104         /**
105          * Removes one or more CSS classes from the element.
106          * @param {String/Array} className The CSS classes to remove separated by space, or an array of classes
107          * @return {Ext.core.Element} this
108          */
109         removeCls : function(className){
110             var me = this,
111                 i, idx, len, cls, elClasses;
112             if (!Ext.isDefined(className)) {
113                 return me;
114             }
115             if (!Ext.isArray(className)){
116                 className = className.replace(trimRe, '').split(spacesRe);
117             }
118             if (me.dom && me.dom.className) {
119                 elClasses = me.dom.className.replace(trimRe, '').split(spacesRe);
120                 for (i = 0, len = className.length; i < len; i++) {
121                     cls = className[i];
122                     if (typeof cls == 'string') {
123                         cls = cls.replace(trimRe, '');
124                         idx = Ext.Array.indexOf(elClasses, cls);
125                         if (idx != -1) {
126                             elClasses.splice(idx, 1);
127                         }
128                     }
129                 }
130                 me.dom.className = elClasses.join(" ");
131             }
132             return me;
133         },
134
135         /**
136          * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
137          * @param {String/Array} className The CSS class to add, or an array of classes
138          * @return {Ext.core.Element} this
139          */
140         radioCls : function(className){
141             var cn = this.dom.parentNode.childNodes,
142                 v, i, len;
143             className = Ext.isArray(className) ? className : [className];
144             for (i = 0, len = cn.length; i < len; i++) {
145                 v = cn[i];
146                 if (v && v.nodeType == 1) {
147                     Ext.fly(v, '_internal').removeCls(className);
148                 }
149             }
150             return this.addCls(className);
151         },
152
153         /**
154          * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
155          * @param {String} className The CSS class to toggle
156          * @return {Ext.core.Element} this
157          */
158         toggleCls : Ext.supports.ClassList ?
159             function(className) {
160                 this.dom.classList.toggle(Ext.String.trim(className));
161                 return this;
162             } :
163             function(className) {
164                 return this.hasCls(className) ? this.removeCls(className) : this.addCls(className);
165             },
166
167         /**
168          * Checks if the specified CSS class exists on this element's DOM node.
169          * @param {String} className The CSS class to check for
170          * @return {Boolean} True if the class exists, else false
171          */
172         hasCls : Ext.supports.ClassList ?
173             function(className) {
174                 if (!className) {
175                     return false;
176                 }
177                 className = className.split(spacesRe);
178                 var ln = className.length,
179                     i = 0;
180                 for (; i < ln; i++) {
181                     if (className[i] && this.dom.classList.contains(className[i])) {
182                         return true;
183                     }
184                 }
185                 return false;
186             } :
187             function(className){
188                 return className && (' ' + this.dom.className + ' ').indexOf(' ' + className + ' ') != -1;
189             },
190
191         /**
192          * Replaces a CSS class on the element with another.  If the old name does not exist, the new name will simply be added.
193          * @param {String} oldClassName The CSS class to replace
194          * @param {String} newClassName The replacement CSS class
195          * @return {Ext.core.Element} this
196          */
197         replaceCls : function(oldClassName, newClassName){
198             return this.removeCls(oldClassName).addCls(newClassName);
199         },
200
201         isStyle : function(style, val) {
202             return this.getStyle(style) == val;
203         },
204
205         /**
206          * Normalizes currentStyle and computedStyle.
207          * @param {String} property The style property whose value is returned.
208          * @return {String} The current value of the style property for this element.
209          */
210         getStyle : function(){
211             return view && view.getComputedStyle ?
212                 function(prop){
213                     var el = this.dom,
214                         v, cs, out, display;
215
216                     if(el == document){
217                         return null;
218                     }
219                     prop = Ext.core.Element.normalize(prop);
220                     out = (v = el.style[prop]) ? v :
221                            (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
222                            
223                     // Ignore cases when the margin is correctly reported as 0, the bug only shows
224                     // numbers larger.
225                     if(prop == 'marginRight' && out != '0px' && !supports.RightMargin){
226                         display = this.getStyle('display');
227                         el.style.display = 'inline-block';
228                         out = view.getComputedStyle(el, '').marginRight;
229                         el.style.display = display;
230                     }
231                     
232                     if(prop == 'backgroundColor' && out == 'rgba(0, 0, 0, 0)' && !supports.TransparentColor){
233                         out = 'transparent';
234                     }
235                     return out;
236                 } :
237                 function(prop){
238                     var el = this.dom,
239                         m, cs;
240
241                     if (el == document) {
242                         return null;
243                     }
244                     
245                     if (prop == 'opacity') {
246                         if (el.style.filter.match) {
247                             m = el.style.filter.match(opacityRe);
248                             if(m){
249                                 var fv = parseFloat(m[1]);
250                                 if(!isNaN(fv)){
251                                     return fv ? fv / 100 : 0;
252                                 }
253                             }
254                         }
255                         return 1;
256                     }
257                     prop = Ext.core.Element.normalize(prop);
258                     return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
259                 };
260         }(),
261
262         /**
263          * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
264          * are convert to standard 6 digit hex color.
265          * @param {String} attr The css attribute
266          * @param {String} defaultValue The default value to use when a valid color isn't found
267          * @param {String} prefix (optional) defaults to #. Use an empty string when working with
268          * color anims.
269          */
270         getColor : function(attr, defaultValue, prefix){
271             var v = this.getStyle(attr),
272                 color = prefix || prefix === '' ? prefix : '#',
273                 h;
274
275             if(!v || (/transparent|inherit/.test(v))) {
276                 return defaultValue;
277             }
278             if(/^r/.test(v)){
279                 Ext.each(v.slice(4, v.length -1).split(','), function(s){
280                     h = parseInt(s, 10);
281                     color += (h < 16 ? '0' : '') + h.toString(16);
282                 });
283             }else{
284                 v = v.replace('#', '');
285                 color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
286             }
287             return(color.length > 5 ? color.toLowerCase() : defaultValue);
288         },
289
290         /**
291          * Wrapper for setting style properties, also takes single object parameter of multiple styles.
292          * @param {String/Object} property The style property to be set, or an object of multiple styles.
293          * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
294          * @return {Ext.core.Element} this
295          */
296         setStyle : function(prop, value){
297             var me = this,
298                 tmp, style;
299
300             if (!me.dom) {
301                 return me;
302             }
303
304             if (!Ext.isObject(prop)) {
305                 tmp = {};
306                 tmp[prop] = value;
307                 prop = tmp;
308             }
309             for (style in prop) {
310                 if (prop.hasOwnProperty(style)) {
311                     value = Ext.value(prop[style], '');
312                     if (style == 'opacity') {
313                         me.setOpacity(value);
314                     }
315                     else {
316                         me.dom.style[Ext.core.Element.normalize(style)] = value;
317                     }
318                 }
319             }
320             return me;
321         },
322
323         /**
324          * Set the opacity of the element
325          * @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
326          * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
327          * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
328          * @return {Ext.core.Element} this
329          */
330         setOpacity: function(opacity, animate) {
331             var me = this,
332                 dom = me.dom,
333                 val,
334                 style;
335
336             if (!me.dom) {
337                 return me;
338             }
339
340             style = me.dom.style;
341
342             if (!animate || !me.anim) {
343                 if (!Ext.supports.Opacity) {
344                     opacity = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')': '';
345                     val = style.filter.replace(opacityRe, '').replace(trimRe, '');
346
347                     style.zoom = 1;
348                     style.filter = val + (val.length > 0 ? ' ': '') + opacity;
349                 }
350                 else {
351                     style.opacity = opacity;
352                 }
353             }
354             else {
355                 if (!Ext.isObject(animate)) {
356                     animate = {
357                         duration: 350,
358                         easing: 'ease-in'
359                     };
360                 }
361                 me.animate(Ext.applyIf({
362                     to: {
363                         opacity: opacity
364                     }
365                 },
366                 animate));
367             }
368             return me;
369         },
370
371
372         /**
373          * Clears any opacity settings from this element. Required in some cases for IE.
374          * @return {Ext.core.Element} this
375          */
376         clearOpacity : function(){
377             var style = this.dom.style;
378             if(!Ext.supports.Opacity){
379                 if(!Ext.isEmpty(style.filter)){
380                     style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
381                 }
382             }else{
383                 style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
384             }
385             return this;
386         },
387         
388         /**
389          * @private
390          * Returns 1 if the browser returns the subpixel dimension rounded to the lowest pixel.
391          * @return {Number} 0 or 1 
392          */
393         adjustDirect2DDimension: function(dimension) {
394             var me = this,
395                 dom = me.dom,
396                 display = me.getStyle('display'),
397                 inlineDisplay = dom.style['display'],
398                 inlinePosition = dom.style['position'],
399                 originIndex = dimension === 'width' ? 0 : 1,
400                 floating;
401                 
402             if (display === 'inline') {
403                 dom.style['display'] = 'inline-block';
404             }
405
406             dom.style['position'] = display.match(adjustDirect2DTableRe) ? 'absolute' : 'static';
407
408             // floating will contain digits that appears after the decimal point
409             // if height or width are set to auto we fallback to msTransformOrigin calculation
410             floating = (parseFloat(me.getStyle(dimension)) || parseFloat(dom.currentStyle.msTransformOrigin.split(' ')[originIndex]) * 2) % 1;
411             
412             dom.style['position'] = inlinePosition;
413             
414             if (display === 'inline') {
415                 dom.style['display'] = inlineDisplay;
416             }
417
418             return floating;
419         },
420         
421         /**
422          * Returns the offset height of the element
423          * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
424          * @return {Number} The element's height
425          */
426         getHeight: function(contentHeight, preciseHeight) {
427             var me = this,
428                 dom = me.dom,
429                 hidden = Ext.isIE && me.isStyle('display', 'none'),
430                 height, overflow, style, floating;
431
432             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
433             // We will put the overflow back to it's original value when we are done measuring.
434             if (Ext.isIEQuirks) {
435                 style = dom.style;
436                 overflow = style.overflow;
437                 me.setStyle({ overflow: 'hidden'});
438             }
439
440             height = dom.offsetHeight;
441
442             height = MATH.max(height, hidden ? 0 : dom.clientHeight) || 0;
443
444             // IE9 Direct2D dimension rounding bug
445             if (!hidden && Ext.supports.Direct2DBug) {
446                 floating = me.adjustDirect2DDimension('height');
447                 if (preciseHeight) {
448                     height += floating;
449                 }
450                 else if (floating > 0 && floating < 0.5) {
451                     height++;
452                 }
453             }
454
455             if (contentHeight) {
456                 height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
457             }
458
459             if (Ext.isIEQuirks) {
460                 me.setStyle({ overflow: overflow});
461             }
462
463             if (height < 0) {
464                 height = 0;
465             }
466             return height;
467         },
468                 
469         /**
470          * Returns the offset width of the element
471          * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
472          * @return {Number} The element's width
473          */
474         getWidth: function(contentWidth, preciseWidth) {
475             var me = this,
476                 dom = me.dom,
477                 hidden = Ext.isIE && me.isStyle('display', 'none'),
478                 rect, width, overflow, style, floating, parentPosition;
479
480             // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
481             // We will put the overflow back to it's original value when we are done measuring.
482             if (Ext.isIEQuirks) {
483                 style = dom.style;
484                 overflow = style.overflow;
485                 me.setStyle({overflow: 'hidden'});
486             }
487             
488             // Fix Opera 10.5x width calculation issues 
489             if (Ext.isOpera10_5) {
490                 if (dom.parentNode.currentStyle.position === 'relative') {
491                     parentPosition = dom.parentNode.style.position;
492                     dom.parentNode.style.position = 'static';
493                     width = dom.offsetWidth;
494                     dom.parentNode.style.position = parentPosition;
495                 }
496                 width = Math.max(width || 0, dom.offsetWidth);
497             
498             // Gecko will in some cases report an offsetWidth that is actually less than the width of the
499             // text contents, because it measures fonts with sub-pixel precision but rounds the calculated
500             // value down. Using getBoundingClientRect instead of offsetWidth allows us to get the precise
501             // subpixel measurements so we can force them to always be rounded up. See
502             // https://bugzilla.mozilla.org/show_bug.cgi?id=458617
503             } else if (Ext.supports.BoundingClientRect) {
504                 rect = dom.getBoundingClientRect();
505                 width = rect.right - rect.left;
506                 width = preciseWidth ? width : Math.ceil(width);
507             } else {
508                 width = dom.offsetWidth;
509             }
510
511             width = MATH.max(width, hidden ? 0 : dom.clientWidth) || 0;
512
513             // IE9 Direct2D dimension rounding bug
514             if (!hidden && Ext.supports.Direct2DBug) {
515                 floating = me.adjustDirect2DDimension('width');
516                 if (preciseWidth) {
517                     width += floating;
518                 }
519                 else if (floating > 0 && floating < 0.5) {
520                     width++;
521                 }
522             }
523             
524             if (contentWidth) {
525                 width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
526             }
527             
528             if (Ext.isIEQuirks) {
529                 me.setStyle({ overflow: overflow});
530             }
531
532             if (width < 0) {
533                 width = 0;
534             }
535             return width;
536         },
537
538         /**
539          * Set the width of this Element.
540          * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
541          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
542          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
543          * </ul></div>
544          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
545          * @return {Ext.core.Element} this
546          */
547         setWidth : function(width, animate){
548             var me = this;
549             width = me.adjustWidth(width);
550             if (!animate || !me.anim) {
551                 me.dom.style.width = me.addUnits(width);
552             }
553             else {
554                 if (!Ext.isObject(animate)) {
555                     animate = {};
556                 }
557                 me.animate(Ext.applyIf({
558                     to: {
559                         width: width
560                     }
561                 }, animate));
562             }
563             return me;
564         },
565
566         /**
567          * Set the height of this Element.
568          * <pre><code>
569 // change the height to 200px and animate with default configuration
570 Ext.fly('elementId').setHeight(200, true);
571
572 // change the height to 150px and animate with a custom configuration
573 Ext.fly('elId').setHeight(150, {
574     duration : .5, // animation will have a duration of .5 seconds
575     // will change the content to "finished"
576     callback: function(){ this.{@link #update}("finished"); }
577 });
578          * </code></pre>
579          * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
580          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
581          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
582          * </ul></div>
583          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
584          * @return {Ext.core.Element} this
585          */
586          setHeight : function(height, animate){
587             var me = this;
588             height = me.adjustHeight(height);
589             if (!animate || !me.anim) {
590                 me.dom.style.height = me.addUnits(height);
591             }
592             else {
593                 if (!Ext.isObject(animate)) {
594                     animate = {};
595                 }
596                 me.animate(Ext.applyIf({
597                     to: {
598                         height: height
599                     }
600                 }, animate));
601             }
602             return me;
603         },
604
605         /**
606          * Gets the width of the border(s) for the specified side(s)
607          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
608          * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
609          * @return {Number} The width of the sides passed added together
610          */
611         getBorderWidth : function(side){
612             return this.addStyles(side, borders);
613         },
614
615         /**
616          * Gets the width of the padding(s) for the specified side(s)
617          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
618          * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
619          * @return {Number} The padding of the sides passed added together
620          */
621         getPadding : function(side){
622             return this.addStyles(side, paddings);
623         },
624
625         /**
626          *  Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
627          * @return {Ext.core.Element} this
628          */
629         clip : function(){
630             var me = this,
631                 dom = me.dom;
632
633             if(!data(dom, ISCLIPPED)){
634                 data(dom, ISCLIPPED, true);
635                 data(dom, ORIGINALCLIP, {
636                     o: me.getStyle(OVERFLOW),
637                     x: me.getStyle(OVERFLOWX),
638                     y: me.getStyle(OVERFLOWY)
639                 });
640                 me.setStyle(OVERFLOW, HIDDEN);
641                 me.setStyle(OVERFLOWX, HIDDEN);
642                 me.setStyle(OVERFLOWY, HIDDEN);
643             }
644             return me;
645         },
646
647         /**
648          *  Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
649          * @return {Ext.core.Element} this
650          */
651         unclip : function(){
652             var me = this,
653                 dom = me.dom,
654                 clip;
655
656             if(data(dom, ISCLIPPED)){
657                 data(dom, ISCLIPPED, false);
658                 clip = data(dom, ORIGINALCLIP);
659                 if(o.o){
660                     me.setStyle(OVERFLOW, o.o);
661                 }
662                 if(o.x){
663                     me.setStyle(OVERFLOWX, o.x);
664                 }
665                 if(o.y){
666                     me.setStyle(OVERFLOWY, o.y);
667                 }
668             }
669             return me;
670         },
671
672         // private
673         addStyles : function(sides, styles){
674             var totalSize = 0,
675                 sidesArr = sides.match(wordsRe),
676                 i = 0,
677                 len = sidesArr.length,
678                 side, size;
679             for (; i < len; i++) {
680                 side = sidesArr[i];
681                 size = side && parseInt(this.getStyle(styles[side]), 10);
682                 if (size) {
683                     totalSize += MATH.abs(size);
684                 }
685             }
686             return totalSize;
687         },
688
689         margins : margins,
690         
691         /**
692          * More flexible version of {@link #setStyle} for setting style properties.
693          * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
694          * a function which returns such a specification.
695          * @return {Ext.core.Element} this
696          */
697         applyStyles : function(style){
698             Ext.core.DomHelper.applyStyles(this.dom, style);
699             return this;
700         },
701
702         /**
703          * Returns an object with properties matching the styles requested.
704          * For example, el.getStyles('color', 'font-size', 'width') might return
705          * {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
706          * @param {String} style1 A style name
707          * @param {String} style2 A style name
708          * @param {String} etc.
709          * @return {Object} The style object
710          */
711         getStyles : function(){
712             var styles = {},
713                 len = arguments.length,
714                 i = 0, style;
715                 
716             for(; i < len; ++i) {
717                 style = arguments[i];
718                 styles[style] = this.getStyle(style);
719             }
720             return styles;
721         },
722
723        /**
724         * <p>Wraps the specified element with a special 9 element markup/CSS block that renders by default as
725         * a gray container with a gradient background, rounded corners and a 4-way shadow.</p>
726         * <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.button.Button},
727         * {@link Ext.panel.Panel} when <tt>{@link Ext.panel.Panel#frame frame=true}</tt>, {@link Ext.window.Window}).  The markup
728         * is of this form:</p>
729         * <pre><code>
730     Ext.core.Element.boxMarkup =
731     &#39;&lt;div class="{0}-tl">&lt;div class="{0}-tr">&lt;div class="{0}-tc">&lt;/div>&lt;/div>&lt;/div>
732      &lt;div class="{0}-ml">&lt;div class="{0}-mr">&lt;div class="{0}-mc">&lt;/div>&lt;/div>&lt;/div>
733      &lt;div class="{0}-bl">&lt;div class="{0}-br">&lt;div class="{0}-bc">&lt;/div>&lt;/div>&lt;/div>&#39;;
734         * </code></pre>
735         * <p>Example usage:</p>
736         * <pre><code>
737     // Basic box wrap
738     Ext.get("foo").boxWrap();
739
740     // You can also add a custom class and use CSS inheritance rules to customize the box look.
741     // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
742     // for how to create a custom box wrap style.
743     Ext.get("foo").boxWrap().addCls("x-box-blue");
744         * </code></pre>
745         * @param {String} class (optional) A base CSS class to apply to the containing wrapper element
746         * (defaults to <tt>'x-box'</tt>). Note that there are a number of CSS rules that are dependent on
747         * this name to make the overall effect work, so if you supply an alternate base class, make sure you
748         * also supply all of the necessary rules.
749         * @return {Ext.core.Element} The outermost wrapping element of the created box structure.
750         */
751         boxWrap : function(cls){
752             cls = cls || Ext.baseCSSPrefix + 'box';
753             var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + Ext.String.format(Ext.core.Element.boxMarkup, cls) + "</div>"));
754             Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
755             return el;
756         },
757
758         /**
759          * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
760          * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
761          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
762          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
763          * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
764          * </ul></div>
765          * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
766          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
767          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
768          * </ul></div>
769          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
770          * @return {Ext.core.Element} this
771          */
772         setSize : function(width, height, animate){
773             var me = this;
774             if (Ext.isObject(width)){ // in case of object from getSize()
775                 height = width.height;
776                 width = width.width;
777             }
778             width = me.adjustWidth(width);
779             height = me.adjustHeight(height);
780             if(!animate || !me.anim){
781                 me.dom.style.width = me.addUnits(width);
782                 me.dom.style.height = me.addUnits(height);
783             }
784             else {
785                 if (!Ext.isObject(animate)) {
786                     animate = {};
787                 }
788                 me.animate(Ext.applyIf({
789                     to: {
790                         width: width,
791                         height: height
792                     }
793                 }, animate));
794             }
795             return me;
796         },
797
798         /**
799          * Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
800          * when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
801          * if a height has not been set using CSS.
802          * @return {Number}
803          */
804         getComputedHeight : function(){
805             var me = this,
806                 h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
807             if(!h){
808                 h = parseFloat(me.getStyle('height')) || 0;
809                 if(!me.isBorderBox()){
810                     h += me.getFrameWidth('tb');
811                 }
812             }
813             return h;
814         },
815
816         /**
817          * Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
818          * when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
819          * if a width has not been set using CSS.
820          * @return {Number}
821          */
822         getComputedWidth : function(){
823             var me = this,
824                 w = Math.max(me.dom.offsetWidth, me.dom.clientWidth);
825                 
826             if(!w){
827                 w = parseFloat(me.getStyle('width')) || 0;
828                 if(!me.isBorderBox()){
829                     w += me.getFrameWidth('lr');
830                 }
831             }
832             return w;
833         },
834
835         /**
836          * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
837          for more information about the sides.
838          * @param {String} sides
839          * @return {Number}
840          */
841         getFrameWidth : function(sides, onlyContentBox){
842             return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
843         },
844
845         /**
846          * Sets up event handlers to add and remove a css class when the mouse is over this element
847          * @param {String} className
848          * @return {Ext.core.Element} this
849          */
850         addClsOnOver : function(className){
851             var dom = this.dom;
852             this.hover(
853                 function(){
854                     Ext.fly(dom, INTERNAL).addCls(className);
855                 },
856                 function(){
857                     Ext.fly(dom, INTERNAL).removeCls(className);
858                 }
859             );
860             return this;
861         },
862
863         /**
864          * Sets up event handlers to add and remove a css class when this element has the focus
865          * @param {String} className
866          * @return {Ext.core.Element} this
867          */
868         addClsOnFocus : function(className){
869             var me = this,
870                 dom = me.dom;
871             me.on("focus", function(){
872                 Ext.fly(dom, INTERNAL).addCls(className);
873             });
874             me.on("blur", function(){
875                 Ext.fly(dom, INTERNAL).removeCls(className);
876             });
877             return me;
878         },
879
880         /**
881          * Sets up event handlers to add and remove a css class when the mouse is down and then up on this element (a click effect)
882          * @param {String} className
883          * @return {Ext.core.Element} this
884          */
885         addClsOnClick : function(className){
886             var dom = this.dom;
887             this.on("mousedown", function(){
888                 Ext.fly(dom, INTERNAL).addCls(className);
889                 var d = Ext.getDoc(),
890                     fn = function(){
891                         Ext.fly(dom, INTERNAL).removeCls(className);
892                         d.removeListener("mouseup", fn);
893                     };
894                 d.on("mouseup", fn);
895             });
896             return this;
897         },
898
899         /**
900          * <p>Returns the dimensions of the element available to lay content out in.<p>
901          * <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
902          * example:<pre><code>
903         var vpSize = Ext.getBody().getViewSize();
904
905         // all Windows created afterwards will have a default value of 90% height and 95% width
906         Ext.Window.override({
907             width: vpSize.width * 0.9,
908             height: vpSize.height * 0.95
909         });
910         // To handle window resizing you would have to hook onto onWindowResize.
911         * </code></pre>
912         *
913         * getViewSize utilizes clientHeight/clientWidth which excludes sizing of scrollbars.
914         * To obtain the size including scrollbars, use getStyleSize
915         *
916         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
917         */
918
919         getViewSize : function(){
920             var me = this,
921                 dom = me.dom,
922                 isDoc = (dom == Ext.getDoc().dom || dom == Ext.getBody().dom),
923                 style, overflow, ret;
924
925             // If the body, use static methods
926             if (isDoc) {
927                 ret = {
928                     width : Ext.core.Element.getViewWidth(),
929                     height : Ext.core.Element.getViewHeight()
930                 };
931
932             // Else use clientHeight/clientWidth
933             }
934             else {
935                 // IE 6 & IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
936                 // We will put the overflow back to it's original value when we are done measuring.
937                 if (Ext.isIE6 || Ext.isIEQuirks) {
938                     style = dom.style;
939                     overflow = style.overflow;
940                     me.setStyle({ overflow: 'hidden'});
941                 }
942                 ret = {
943                     width : dom.clientWidth,
944                     height : dom.clientHeight
945                 };
946                 if (Ext.isIE6 || Ext.isIEQuirks) {
947                     me.setStyle({ overflow: overflow });
948                 }
949             }
950             return ret;
951         },
952
953         /**
954         * <p>Returns the dimensions of the element available to lay content out in.<p>
955         *
956         * getStyleSize utilizes prefers style sizing if present, otherwise it chooses the larger of offsetHeight/clientHeight and offsetWidth/clientWidth.
957         * To obtain the size excluding scrollbars, use getViewSize
958         *
959         * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
960         */
961
962         getStyleSize : function(){
963             var me = this,
964                 doc = document,
965                 d = this.dom,
966                 isDoc = (d == doc || d == doc.body),
967                 s = d.style,
968                 w, h;
969
970             // If the body, use static methods
971             if (isDoc) {
972                 return {
973                     width : Ext.core.Element.getViewWidth(),
974                     height : Ext.core.Element.getViewHeight()
975                 };
976             }
977             // Use Styles if they are set
978             if(s.width && s.width != 'auto'){
979                 w = parseFloat(s.width);
980                 if(me.isBorderBox()){
981                    w -= me.getFrameWidth('lr');
982                 }
983             }
984             // Use Styles if they are set
985             if(s.height && s.height != 'auto'){
986                 h = parseFloat(s.height);
987                 if(me.isBorderBox()){
988                    h -= me.getFrameWidth('tb');
989                 }
990             }
991             // Use getWidth/getHeight if style not set.
992             return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
993         },
994
995         /**
996          * Returns the size of the element.
997          * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
998          * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
999          */
1000         getSize : function(contentSize){
1001             return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
1002         },
1003
1004         /**
1005          * Forces the browser to repaint this element
1006          * @return {Ext.core.Element} this
1007          */
1008         repaint : function(){
1009             var dom = this.dom;
1010             this.addCls(Ext.baseCSSPrefix + 'repaint');
1011             setTimeout(function(){
1012                 Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
1013             }, 1);
1014             return this;
1015         },
1016
1017         /**
1018          * Disables text selection for this element (normalized across browsers)
1019          * @return {Ext.core.Element} this
1020          */
1021         unselectable : function(){
1022             var me = this;
1023             me.dom.unselectable = "on";
1024
1025             me.swallowEvent("selectstart", true);
1026             me.applyStyles("-moz-user-select:none;-khtml-user-select:none;");
1027             me.addCls(Ext.baseCSSPrefix + 'unselectable');
1028             
1029             return me;
1030         },
1031
1032         /**
1033          * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
1034          * then it returns the calculated width of the sides (see getPadding)
1035          * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
1036          * @return {Object/Number}
1037          */
1038         getMargin : function(side){
1039             var me = this,
1040                 hash = {t:"top", l:"left", r:"right", b: "bottom"},
1041                 o = {},
1042                 key;
1043
1044             if (!side) {
1045                 for (key in me.margins){
1046                     o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
1047                 }
1048                 return o;
1049             } else {
1050                 return me.addStyles.call(me, side, me.margins);
1051             }
1052         }
1053     });
1054 })();