Upgrade to ExtJS 3.1.1 - Released 02/08/2010
[extjs.git] / src / core / core / Element.style.js
1 /*!
2  * Ext JS Library 3.1.1
3  * Copyright(c) 2006-2010 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.Element
9  */
10 Ext.Element.addMethods(function(){
11     // local style camelizing for speed
12     var propCache = {},
13         camelRe = /(-[a-z])/gi,
14         classReCache = {},
15         view = document.defaultView,
16         propFloat = Ext.isIE ? 'styleFloat' : 'cssFloat',
17         opacityRe = /alpha\(opacity=(.*)\)/i,
18         trimRe = /^\s+|\s+$/g,
19         EL = Ext.Element,
20         PADDING = "padding",
21         MARGIN = "margin",
22         BORDER = "border",
23         LEFT = "-left",
24         RIGHT = "-right",
25         TOP = "-top",
26         BOTTOM = "-bottom",
27         WIDTH = "-width",
28         MATH = Math,
29         HIDDEN = 'hidden',
30         ISCLIPPED = 'isClipped',
31         OVERFLOW = 'overflow',
32         OVERFLOWX = 'overflow-x',
33         OVERFLOWY = 'overflow-y',
34         ORIGINALCLIP = 'originalClip',
35         // special markup used throughout Ext when box wrapping elements
36         borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
37         paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
38         margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
39         data = Ext.Element.data;
40
41
42     // private
43     function camelFn(m, a) {
44         return a.charAt(1).toUpperCase();
45     }
46
47     function chkCache(prop) {
48         return propCache[prop] || (propCache[prop] = prop == 'float' ? propFloat : prop.replace(camelRe, camelFn));
49     }
50
51     return {
52         // private  ==> used by Fx
53         adjustWidth : function(width) {
54             var me = this;
55             var isNum = Ext.isNumber(width);
56             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
57                width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
58             }
59             return (isNum && width < 0) ? 0 : width;
60         },
61
62         // private   ==> used by Fx
63         adjustHeight : function(height) {
64             var me = this;
65             var isNum = Ext.isNumber(height);
66             if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
67                height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
68             }
69             return (isNum && height < 0) ? 0 : height;
70         },
71
72
73         /**
74          * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
75          * @param {String/Array} className The CSS class to add, or an array of classes
76          * @return {Ext.Element} this
77          */
78         addClass : function(className){
79             var me = this, i, len, v;
80             className = Ext.isArray(className) ? className : [className];
81             for (i=0, len = className.length; i < len; i++) {
82                 v = className[i];
83                 if (v) {
84                     me.dom.className += (!me.hasClass(v) && v ? " " + v : "");
85                 };
86             };
87             return me;
88         },
89
90         /**
91          * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
92          * @param {String/Array} className The CSS class to add, or an array of classes
93          * @return {Ext.Element} this
94          */
95         radioClass : function(className){
96             var cn = this.dom.parentNode.childNodes, v;
97             className = Ext.isArray(className) ? className : [className];
98             for (var i=0, len = cn.length; i < len; i++) {
99                 v = cn[i];
100                 if(v && v.nodeType == 1) {
101                     Ext.fly(v, '_internal').removeClass(className);
102                 }
103             };
104             return this.addClass(className);
105         },
106
107         /**
108          * Removes one or more CSS classes from the element.
109          * @param {String/Array} className The CSS class to remove, or an array of classes
110          * @return {Ext.Element} this
111          */
112         removeClass : function(className){
113             var me = this, v;
114             className = Ext.isArray(className) ? className : [className];
115             if (me.dom && me.dom.className) {
116                 for (var i=0, len=className.length; i < len; i++) {
117                     v = className[i];
118                     if(v) {
119                         me.dom.className = me.dom.className.replace(
120                             classReCache[v] = classReCache[v] || new RegExp('(?:^|\\s+)' + v + '(?:\\s+|$)', "g"), " "
121                         );
122                     }
123                 };
124             }
125             return me;
126         },
127
128         /**
129          * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
130          * @param {String} className The CSS class to toggle
131          * @return {Ext.Element} this
132          */
133         toggleClass : function(className){
134             return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
135         },
136
137         /**
138          * Checks if the specified CSS class exists on this element's DOM node.
139          * @param {String} className The CSS class to check for
140          * @return {Boolean} True if the class exists, else false
141          */
142         hasClass : function(className){
143             return className && (' '+this.dom.className+' ').indexOf(' '+className+' ') != -1;
144         },
145
146         /**
147          * Replaces a CSS class on the element with another.  If the old name does not exist, the new name will simply be added.
148          * @param {String} oldClassName The CSS class to replace
149          * @param {String} newClassName The replacement CSS class
150          * @return {Ext.Element} this
151          */
152         replaceClass : function(oldClassName, newClassName){
153             return this.removeClass(oldClassName).addClass(newClassName);
154         },
155
156         isStyle : function(style, val) {
157             return this.getStyle(style) == val;
158         },
159
160         /**
161          * Normalizes currentStyle and computedStyle.
162          * @param {String} property The style property whose value is returned.
163          * @return {String} The current value of the style property for this element.
164          */
165         getStyle : function(){
166             return view && view.getComputedStyle ?
167                 function(prop){
168                     var el = this.dom,
169                         v,
170                         cs,
171                         out,
172                         display,
173                         wk = Ext.isWebKit,
174                         display;
175                         
176                     if(el == document){
177                         return null;
178                     }
179                     prop = chkCache(prop);
180                     // Fix bug caused by this: https://bugs.webkit.org/show_bug.cgi?id=13343
181                     if(wk && /marginRight/.test(prop)){
182                         display = this.getStyle('display');
183                         el.style.display = 'inline-block';
184                     }
185                     out = (v = el.style[prop]) ? v :
186                            (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
187
188                     // Webkit returns rgb values for transparent.
189                     if(wk){
190                         if(out == 'rgba(0, 0, 0, 0)'){
191                             out = 'transparent';
192                         }else if(display){
193                             el.style.display = display;
194                         }
195                     }
196                     return out;
197                 } :
198                 function(prop){
199                     var el = this.dom,
200                         m,
201                         cs;
202
203                     if(el == document) return null;
204                     if (prop == 'opacity') {
205                         if (el.style.filter.match) {
206                             if(m = el.style.filter.match(opacityRe)){
207                                 var fv = parseFloat(m[1]);
208                                 if(!isNaN(fv)){
209                                     return fv ? fv / 100 : 0;
210                                 }
211                             }
212                         }
213                         return 1;
214                     }
215                     prop = chkCache(prop);
216                     return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
217                 };
218         }(),
219
220         /**
221          * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
222          * are convert to standard 6 digit hex color.
223          * @param {String} attr The css attribute
224          * @param {String} defaultValue The default value to use when a valid color isn't found
225          * @param {String} prefix (optional) defaults to #. Use an empty string when working with
226          * color anims.
227          */
228         getColor : function(attr, defaultValue, prefix){
229             var v = this.getStyle(attr),
230                 color = Ext.isDefined(prefix) ? prefix : '#',
231                 h;
232
233             if(!v || /transparent|inherit/.test(v)){
234                 return defaultValue;
235             }
236             if(/^r/.test(v)){
237                 Ext.each(v.slice(4, v.length -1).split(','), function(s){
238                     h = parseInt(s, 10);
239                     color += (h < 16 ? '0' : '') + h.toString(16);
240                 });
241             }else{
242                 v = v.replace('#', '');
243                 color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
244             }
245             return(color.length > 5 ? color.toLowerCase() : defaultValue);
246         },
247
248         /**
249          * Wrapper for setting style properties, also takes single object parameter of multiple styles.
250          * @param {String/Object} property The style property to be set, or an object of multiple styles.
251          * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
252          * @return {Ext.Element} this
253          */
254         setStyle : function(prop, value){
255             var tmp,
256                 style,
257                 camel;
258             if (!Ext.isObject(prop)) {
259                 tmp = {};
260                 tmp[prop] = value;
261                 prop = tmp;
262             }
263             for (style in prop) {
264                 value = prop[style];
265                 style == 'opacity' ?
266                     this.setOpacity(value) :
267                     this.dom.style[chkCache(style)] = value;
268             }
269             return this;
270         },
271
272         /**
273          * Set the opacity of the element
274          * @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
275          * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
276          * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
277          * @return {Ext.Element} this
278          */
279          setOpacity : function(opacity, animate){
280             var me = this,
281                 s = me.dom.style;
282
283             if(!animate || !me.anim){
284                 if(Ext.isIE){
285                     var opac = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')' : '',
286                     val = s.filter.replace(opacityRe, '').replace(trimRe, '');
287
288                     s.zoom = 1;
289                     s.filter = val + (val.length > 0 ? ' ' : '') + opac;
290                 }else{
291                     s.opacity = opacity;
292                 }
293             }else{
294                 me.anim({opacity: {to: opacity}}, me.preanim(arguments, 1), null, .35, 'easeIn');
295             }
296             return me;
297         },
298
299         /**
300          * Clears any opacity settings from this element. Required in some cases for IE.
301          * @return {Ext.Element} this
302          */
303         clearOpacity : function(){
304             var style = this.dom.style;
305             if(Ext.isIE){
306                 if(!Ext.isEmpty(style.filter)){
307                     style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
308                 }
309             }else{
310                 style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
311             }
312             return this;
313         },
314
315         /**
316          * Returns the offset height of the element
317          * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
318          * @return {Number} The element's height
319          */
320         getHeight : function(contentHeight){
321             var me = this,
322                 dom = me.dom,
323                 hidden = Ext.isIE && me.isStyle('display', 'none'),
324                 h = MATH.max(dom.offsetHeight, hidden ? 0 : dom.clientHeight) || 0;
325
326             h = !contentHeight ? h : h - me.getBorderWidth("tb") - me.getPadding("tb");
327             return h < 0 ? 0 : h;
328         },
329
330         /**
331          * Returns the offset width of the element
332          * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
333          * @return {Number} The element's width
334          */
335         getWidth : function(contentWidth){
336             var me = this,
337                 dom = me.dom,
338                 hidden = Ext.isIE && me.isStyle('display', 'none'),
339                 w = MATH.max(dom.offsetWidth, hidden ? 0 : dom.clientWidth) || 0;
340             w = !contentWidth ? w : w - me.getBorderWidth("lr") - me.getPadding("lr");
341             return w < 0 ? 0 : w;
342         },
343
344         /**
345          * Set the width of this Element.
346          * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
347          * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
348          * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
349          * </ul></div>
350          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
351          * @return {Ext.Element} this
352          */
353         setWidth : function(width, animate){
354             var me = this;
355             width = me.adjustWidth(width);
356             !animate || !me.anim ?
357                 me.dom.style.width = me.addUnits(width) :
358                 me.anim({width : {to : width}}, me.preanim(arguments, 1));
359             return me;
360         },
361
362         /**
363          * Set the height of this Element.
364          * <pre><code>
365 // change the height to 200px and animate with default configuration
366 Ext.fly('elementId').setHeight(200, true);
367
368 // change the height to 150px and animate with a custom configuration
369 Ext.fly('elId').setHeight(150, {
370     duration : .5, // animation will have a duration of .5 seconds
371     // will change the content to "finished"
372     callback: function(){ this.{@link #update}("finished"); }
373 });
374          * </code></pre>
375          * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
376          * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
377          * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
378          * </ul></div>
379          * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
380          * @return {Ext.Element} this
381          */
382          setHeight : function(height, animate){
383             var me = this;
384             height = me.adjustHeight(height);
385             !animate || !me.anim ?
386                 me.dom.style.height = me.addUnits(height) :
387                 me.anim({height : {to : height}}, me.preanim(arguments, 1));
388             return me;
389         },
390
391         /**
392          * Gets the width of the border(s) for the specified side(s)
393          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
394          * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
395          * @return {Number} The width of the sides passed added together
396          */
397         getBorderWidth : function(side){
398             return this.addStyles(side, borders);
399         },
400
401         /**
402          * Gets the width of the padding(s) for the specified side(s)
403          * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
404          * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
405          * @return {Number} The padding of the sides passed added together
406          */
407         getPadding : function(side){
408             return this.addStyles(side, paddings);
409         },
410
411         /**
412          *  Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
413          * @return {Ext.Element} this
414          */
415         clip : function(){
416             var me = this,
417                 dom = me.dom;
418
419             if(!data(dom, ISCLIPPED)){
420                 data(dom, ISCLIPPED, true);
421                 data(dom, ORIGINALCLIP, {
422                     o: me.getStyle(OVERFLOW),
423                     x: me.getStyle(OVERFLOWX),
424                     y: me.getStyle(OVERFLOWY)
425                 });
426                 me.setStyle(OVERFLOW, HIDDEN);
427                 me.setStyle(OVERFLOWX, HIDDEN);
428                 me.setStyle(OVERFLOWY, HIDDEN);
429             }
430             return me;
431         },
432
433         /**
434          *  Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
435          * @return {Ext.Element} this
436          */
437         unclip : function(){
438             var me = this,
439                 dom = me.dom;
440
441             if(data(dom, ISCLIPPED)){
442                 data(dom, ISCLIPPED, false);
443                 var o = data(dom, ORIGINALCLIP);
444                 if(o.o){
445                     me.setStyle(OVERFLOW, o.o);
446                 }
447                 if(o.x){
448                     me.setStyle(OVERFLOWX, o.x);
449                 }
450                 if(o.y){
451                     me.setStyle(OVERFLOWY, o.y);
452                 }
453             }
454             return me;
455         },
456
457         // private
458         addStyles : function(sides, styles){
459             var val = 0,
460                 m = sides.match(/\w/g),
461                 s;
462             for (var i=0, len=m.length; i<len; i++) {
463                 s = m[i] && parseInt(this.getStyle(styles[m[i]]), 10);
464                 if (s) {
465                     val += MATH.abs(s);
466                 }
467             }
468             return val;
469         },
470
471         margins : margins
472     }
473 }()
474 );