1 /**
  2  * @namespace CSS stuff
  3  */
  4 Jelo.CSS = function() {
  5     /**
  6      * @private Resets the opacity style of a DOM element. Required for IE.
  7      */
  8     var clearOpacity = function(el) {
  9         if (Jelo.Env.isIE) {
 10             if (typeof el.style.filter === "string" && (/alpha/i).test(el.style.filter)) {
 11                 el.style.filter = "";
 12             }
 13         } else {
 14             el.style.opacity = "";
 15             el.style["-moz-opacity"] = "";
 16             el.style["-khtml-opacity"] = "";
 17         }
 18     };
 19     
 20     /** @private alias */
 21     var toCamel = Jelo.Format.toCamel;
 22     
 23     /** @scope Jelo.CSS */
 24     return {
 25         /**
 26          * Mainly used by internal functions, clearOpacity fixes IE behavior.
 27          * 
 28          * @function
 29          * @param {HTMLElement} element
 30          */
 31         clearOpacity : clearOpacity,
 32         
 33         /**
 34          * @function
 35          * @param {HTMLElement} element The item to which the class should be
 36          *        assigned.
 37          * @param {String} class The class name to assign. Duplicates will be
 38          *        filtered out automatically.
 39          */
 40         addClass     : function(e, c) {
 41         	if (Jelo.Valid.isArray(e)) {
 42         		var fn = arguments.callee;
 43         		Jelo.each(e, function() {
 44         			fn(this, c);
 45         		});
 46         		return;
 47         	}
 48             var curr = e.className;
 49             if (!Jelo.CSS.hasClass(e, c)) {
 50                 e.className = curr + (curr.length ? " " : "") + c;
 51             }
 52         },
 53         
 54         /**
 55          * @function
 56          * @param {HTMLElement} element The item to investigate.
 57          * @param {String} class The class name to search for.
 58          * @returns {Boolean} True if the item's className property contains the
 59          *          supplied class name.
 60          */
 61         hasClass     : function(e, c) {
 62             return c && (' ' + e.className + ' ').indexOf(' ' + c + ' ') != -1;
 63         },
 64         
 65         /**
 66          * @function
 67          * @param {HTMLElement} element The item to affect.
 68          * @param {String} class The class name to remove.
 69          */
 70         removeClass  : function(e, c) {
 71             if (Jelo.Valid.isArray(e)) {
 72                 var fn = arguments.callee;
 73                 Jelo.each(e, function() {
 74                     fn(this, c);
 75                 });
 76                 return;
 77             }
 78             e.className = e.className.replace(new RegExp('\\b' + c + '\\b'), '').replace(/^\s\s*/, '').replace(/\s\s*$/, '');
 79         },
 80         
 81         /**
 82          * Gets the current or computed value for an element's CSS property.
 83          * 
 84          * @function
 85          * @param {HTMLElement|HTMLElement[]|String} element One or more items
 86          *        to investigate. If a string is supplied, it is considered a
 87          *        CSS selector and will match elements accordingly.
 88          * @param {String} property The CSS property to retrieve
 89          * @return {String} CSS property value
 90          */
 91         getStyle     : function() {
 92             var view = document.defaultView;
 93             return (view && view.getComputedStyle) ? function(el, p, toInt) {
 94                 var /* counter */i, /* value */v, /* return value */ret, /* camelCase property */cp, styles = [];
 95                 if (!el || !p) {
 96                     return null;
 97                 }
 98                 if (typeof el == "string") {
 99                     el = Jelo.Dom.select(el);
100                 }
101                 if (typeof p == "string") {
102                     p = p.toLowerCase();
103                 }
104                 if (Jelo.Valid.isArray(el)) {
105                     for (i = 0; i < el.length; i++) {
106                         styles.push(Jelo.CSS.getStyle(el[i], p, toInt));
107                     }
108                     return styles;
109                 }
110                 if (Jelo.Valid.isArray(p)) {
111                     for (i = 0; i < p.length; i++) {
112                         styles.push(Jelo.CSS.getStyle(el, p[i], toInt));
113                     }
114                     return styles;
115                 }
116                 if (p == "float") {
117                     p = "cssFloat";
118                 }
119                 cp = toCamel(p);
120                 switch (cp) {
121                     case "backgroundPositionX" :
122                         try {
123                             ret = Jelo.CSS.getStyle(el, "background-position").split(" ")[0];
124                         } catch (e1) {
125                             return null;
126                         }
127                         break;
128                     case "backgroundPositionY" :
129                         try {
130                             ret = Jelo.CSS.getStyle(el, "background-position").split(" ")[1];
131                         } catch (e2) {
132                             return null;
133                         }
134                         break;
135                     default :
136                         if ((v = el.style[p])) {
137                             ret = (/color/).test(p)
138                                 ? Jelo.Format.rgbToHex(v)
139                                 : v.toString().toLowerCase();
140                         } else if ((v = view.getComputedStyle(el, "")[cp])) {
141                             ret = (/color/).test(p) ? Jelo.Format.rgbToHex(v) : v.toString();
142                         }
143                 }
144                 return toInt ? parseInt(ret, 10) : ret;
145             }
146                 : function(el, p, toInt) {
147                     var /* counter */i, /* value */v, /* return value */ret, /* camelCase property */cp, styles = [];
148                     if (!el || !p) {
149                         return null;
150                     }
151                     if (typeof el == "string") {
152                         el = Jelo.Dom.select(el);
153                     }
154                     if (typeof p == "string") {
155                         p = p.toLowerCase();
156                     }
157                     if (Jelo.Valid.isArray(el)) {
158                         for (i = 0; i < el.length; i++) {
159                             styles.push(Jelo.CSS.getStyle(el[i], p, toInt));
160                         }
161                         return styles;
162                     }
163                     if (Jelo.Valid.isArray(p)) {
164                         for (i = 0; i < p.length; i++) {
165                             styles.push(Jelo.CSS.getStyle(el, p[i], toInt));
166                         }
167                         return styles;
168                     }
169                     if (p == "opacity") {
170                         if (typeof el.style.filter == 'string') {
171                             var m = el.style.filter.match(/alpha\(opacity=(.*)\)/i);
172                             if (m) {
173                                 var fv = parseFloat(m[1]);
174                                 if (!isNaN(fv)) {
175                                     return fv ? fv / 100 : 0;
176                                 }
177                             }
178                         }
179                         return 1;
180                     }
181                     if (p == "float") {
182                         p = "styleFloat";
183                     }
184                     p = toCamel(p);
185                     if ((v = el.style[p])) {
186                         ret = v.toString();
187                     } else if (el.currentStyle && (v = el.currentStyle[p])) {
188                         if (v == "auto") {
189                             if ((v = el["offset" + p.replace(/^(.)/, function(m, l) {
190                                 return l.toUpperCase(); // initial cap
191                             })])) {
192                                 ret = v + "px";
193                             }
194                         }
195                         ret = v.toString();
196                     }
197                     return toInt ? parseInt(ret, 10) : ret;
198                 };
199         }(),
200         
201         /**
202          * Gets the current or computed value for an element's CSS property.
203          * 
204          * @function
205          * @param {HTMLElement|HTMLElement[]|String} element One or more items
206          *        to affect. If a string is supplied, it is considered a CSS
207          *        selector and will match elements accordingly.
208          * @param {String|String[]} property The CSS property to assign.
209          * @param {String|String[]} value The value to assign.
210          */
211         setStyle     : function(el, p, v) {
212             var /* counter */i, /* units */u;
213             if (typeof el === "string") {
214                 el = Jelo.Dom.select(el);
215             }
216             if (Jelo.Valid.isArray(el)) {
217                 for (i = 0; i < el.length; i++) {
218                     Jelo.CSS.setStyle(el[i], p, v);
219                 }
220                 return;
221             }
222             if (Jelo.Valid.isArray(p) || Jelo.Valid.isArray(v)) {
223                 if (Jelo.Valid.isArray(p) && Jelo.Valid.isArray(v) && (p.length == v.length)) {
224                     for (i = 0; i < p.length; i++) {
225                         Jelo.CSS.setStyle(el, p[i], v[i]);
226                     }
227                 } else {
228                     throw new Error('Jelo.CSS.setStyle: Properties and values must both be Arrays with the same length, or both be Strings.');
229                 }
230                 return;
231             }
232             p = toCamel(p);
233             if ((/width|height|top|right|bottom|left|size/).test(p)) {
234                 u = v.replace(/[^(%|px|em)]/g, "");
235                 if (!u.length) {
236                     u = "px";
237                 }
238                 v = parseInt(v, 10);
239                 if (isNaN(v)) {
240                     v = 0;
241                 }
242                 v += u;
243             }
244             var s = el.style;
245             if (p === "opacity") {
246                 if (Jelo.Env.isIE) {
247                     s.zoom = 1;
248                     s.filter = (s.filter || '').replace(/alpha\([^\)]*\)/gi, "") +
249                         (v == 1 ? "" : " alpha(opacity=" + v * 100 + ")");
250                 } else {
251                     s.opacity = parseFloat(v);
252                 }
253             } else {
254                 s[p] = v;
255             }
256         },
257         
258         /**
259          * Generates a random hexidecimal color, including the hash symbol (e.g.
260          * #5181ff)
261          * 
262          * @returns {String} A "CSS-formatted" color.
263          */
264         randomColor  : function() {
265             return '#' + (function(h) {
266                 return new Array(7 - h.length).join('0') + h;
267             })((Math.random() * (0xFFFFFF + 1) << 0).toString(16));
268         },
269         
270         /**
271          * Gets a CSS stylesheet rule.
272          * http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
273          * 
274          * @function
275          * @param {String} selector CSS selector, exactly as entered in the stylesheet.
276          * @param {Boolean=false} deleteFlag True to delete the matching rule.
277          */
278         getRule      : function(s, d) {
279             s = s.toLowerCase && s.toLowerCase();
280             if (document.styleSheets) {
281                 for (var i = 0; i < document.styleSheets.length; i++) {
282                     var styleSheet = document.styleSheets[i];
283                     var ii = 0; // TODO: convert to for loop
284                     var cssRule = false;
285                     do {
286                         if (styleSheet.cssRules) {
287                             cssRule = styleSheet.cssRules[ii];
288                         } else {
289                             cssRule = styleSheet.rules && styleSheet.rules[ii];
290                         }
291                         if (cssRule && typeof cssRule.selectorText === 'string') {
292                             if (cssRule.selectorText.toLowerCase() == s) {
293                                 if (d) {
294                                     if (styleSheet.cssRules) {
295                                         styleSheet.deleteRule(ii);
296                                     } else {
297                                         styleSheet.removeRule(ii);
298                                     }
299                                     return true;
300                                 } else {
301                                     return cssRule;
302                                 }
303                             }
304                         }
305                         ii++;
306                     } while (cssRule)
307                 }
308             }
309             return false;
310         },
311         /**
312          * Deletes a CSS stylesheet rule.
313          * http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
314          * 
315          * @function
316          * @param {String} selector CSS selector, exactly as entered in the stylesheet.
317          */
318         deleteRule   : function(s) {
319             return Jelo.CSS.getRule(s, true);
320         },
321         /**
322          * Creates a new CSS stylesheet rule.
323          * http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
324          * 
325          * @function
326          * @param {String} selector CSS selector, exactly as entered in the stylesheet.
327          * @returns {CSSRule} A new rule that can be modified as follows:
328          * var r = Jelo.CSS.createRule('#test'); r.style.color = 'green';
329          */
330         createRule   : function(s) {
331             if (document.styleSheets) {
332                 if (!getRule(s)) {
333                     if (document.styleSheets[0].addRule) {
334                         document.styleSheets[0].addRule(s, null, 0);
335                     } else {
336                         document.styleSheets[0].insertRule(s + ' { }', 0);
337                     }
338                 }
339             }
340             return Jelo.CSS.getRule(s);
341         }
342     };
343 }();
344 
345 /**
346  * Convenience method. If two arguments are supplied, this is shorthand for
347  * {@link Jelo.CSS.getStyle}. If three arguments are supplied, this is
348  * shorthand for {@link Jelo.CSS.setStyle}.
349  * 
350  * @function
351  * @name css
352  * @memberOf Jelo
353  */
354 Jelo.css = function() {
355     if (arguments.length == 2) {
356         return Jelo.CSS.getStyle(arguments[0], arguments[1]);
357     }
358     if (arguments.length == 3) {
359         return Jelo.CSS.setStyle(arguments[0], arguments[1], arguments[2]);
360     }
361     throw new Error("Jelo.css(element, property) for getStyle, and Jelo.css(element, property, value) for setStyle.");
362 };
363