Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / util / CSS.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.util.CSS
17  * Utility class for manipulating CSS rules
18  * @singleton
19  */
20 Ext.define('Ext.util.CSS', function() {
21     var rules = null;
22     var doc = document;
23
24     var camelRe = /(-[a-z])/gi;
25     var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
26
27     return {
28
29         singleton: true,
30
31         constructor: function() {
32             this.rules = {};
33             this.initialized = false;
34         },
35
36         /**
37          * Creates a stylesheet from a text blob of rules.
38          * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
39          * @param {String} cssText The text containing the css rules
40          * @param {String} id An id to add to the stylesheet for later removal
41          * @return {CSSStyleSheet}
42          */
43         createStyleSheet : function(cssText, id) {
44             var ss,
45                 head = doc.getElementsByTagName("head")[0],
46                 styleEl = doc.createElement("style");
47
48             styleEl.setAttribute("type", "text/css");
49             if (id) {
50                styleEl.setAttribute("id", id);
51             }
52
53             if (Ext.isIE) {
54                head.appendChild(styleEl);
55                ss = styleEl.styleSheet;
56                ss.cssText = cssText;
57             } else {
58                 try{
59                     styleEl.appendChild(doc.createTextNode(cssText));
60                 } catch(e) {
61                    styleEl.cssText = cssText;
62                 }
63                 head.appendChild(styleEl);
64                 ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || doc.styleSheets[doc.styleSheets.length-1]);
65             }
66             this.cacheStyleSheet(ss);
67             return ss;
68         },
69
70         /**
71          * Removes a style or link tag by id
72          * @param {String} id The id of the tag
73          */
74         removeStyleSheet : function(id) {
75             var existing = document.getElementById(id);
76             if (existing) {
77                 existing.parentNode.removeChild(existing);
78             }
79         },
80
81         /**
82          * Dynamically swaps an existing stylesheet reference for a new one
83          * @param {String} id The id of an existing link tag to remove
84          * @param {String} url The href of the new stylesheet to include
85          */
86         swapStyleSheet : function(id, url) {
87             var doc = document;
88             this.removeStyleSheet(id);
89             var ss = doc.createElement("link");
90             ss.setAttribute("rel", "stylesheet");
91             ss.setAttribute("type", "text/css");
92             ss.setAttribute("id", id);
93             ss.setAttribute("href", url);
94             doc.getElementsByTagName("head")[0].appendChild(ss);
95         },
96
97         /**
98          * Refresh the rule cache if you have dynamically added stylesheets
99          * @return {Object} An object (hash) of rules indexed by selector
100          */
101         refreshCache : function() {
102             return this.getRules(true);
103         },
104
105         // private
106         cacheStyleSheet : function(ss) {
107             if(!rules){
108                 rules = {};
109             }
110             try {// try catch for cross domain access issue
111                 var ssRules = ss.cssRules || ss.rules,
112                     selectorText,
113                     i = ssRules.length - 1,
114                     j,
115                     selectors;
116
117                 for (; i >= 0; --i) {
118                     selectorText = ssRules[i].selectorText;
119                     if (selectorText) {
120
121                         // Split in case there are multiple, comma-delimited selectors
122                         selectorText = selectorText.split(',');
123                         selectors = selectorText.length;
124                         for (j = 0; j < selectors; j++) {
125                             rules[Ext.String.trim(selectorText[j]).toLowerCase()] = ssRules[i];
126                         }
127                     }
128                 }
129             } catch(e) {}
130         },
131
132         /**
133         * Gets all css rules for the document
134         * @param {Boolean} refreshCache true to refresh the internal cache
135         * @return {Object} An object (hash) of rules indexed by selector
136         */
137         getRules : function(refreshCache) {
138             if (rules === null || refreshCache) {
139                 rules = {};
140                 var ds = doc.styleSheets,
141                     i = 0,
142                     len = ds.length;
143
144                 for (; i < len; i++) {
145                     try {
146                         if (!ds[i].disabled) {
147                             this.cacheStyleSheet(ds[i]);
148                         }
149                     } catch(e) {}
150                 }
151             }
152             return rules;
153         },
154
155         /**
156          * Gets an an individual CSS rule by selector(s)
157          * @param {String/String[]} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
158          * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
159          * @return {CSSStyleRule} The CSS rule or null if one is not found
160          */
161         getRule: function(selector, refreshCache) {
162             var rs = this.getRules(refreshCache);
163             if (!Ext.isArray(selector)) {
164                 return rs[selector.toLowerCase()];
165             }
166             for (var i = 0; i < selector.length; i++) {
167                 if (rs[selector[i]]) {
168                     return rs[selector[i].toLowerCase()];
169                 }
170             }
171             return null;
172         },
173
174         /**
175          * Updates a rule property
176          * @param {String/String[]} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
177          * @param {String} property The css property
178          * @param {String} value The new value for the property
179          * @return {Boolean} true If a rule was found and updated
180          */
181         updateRule : function(selector, property, value){
182             if (!Ext.isArray(selector)) {
183                 var rule = this.getRule(selector);
184                 if (rule) {
185                     rule.style[property.replace(camelRe, camelFn)] = value;
186                     return true;
187                 }
188             } else {
189                 for (var i = 0; i < selector.length; i++) {
190                     if (this.updateRule(selector[i], property, value)) {
191                         return true;
192                     }
193                 }
194             }
195             return false;
196         }
197     };
198 }());