/** * Utility class for manipulating CSS rules * @singleton */Ext.define('Ext.util.CSS', function() { var CSS, rules = null, doc = document, camelRe = /(-[a-z])/gi, camelFn = function(m, a){ return a.charAt(1).toUpperCase(); }; return { singleton: true, rules: rules, initialized: false, /** * @private */ constructor: function() { // Cache a reference to the singleton CSS = this; }, /** * Creates a stylesheet from a text blob of rules. * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document. * @param {String} cssText The text containing the css rules * @param {String} id An id to add to the stylesheet for later removal * @return {CSSStyleSheet} */ createStyleSheet: function (cssText, id) { var ss, head = doc.getElementsByTagName('head')[0], styleEl = doc.createElement('style'); styleEl.setAttribute('type', 'text/css'); if (id) { styleEl.setAttribute('id', id); } // Feature detect old IE ss = styleEl.styleSheet; if (ss) { head.appendChild(styleEl); ss.cssText = cssText; } else { styleEl.appendChild(doc.createTextNode(cssText)); head.appendChild(styleEl); ss = styleEl.sheet; } CSS.cacheStyleSheet(ss); return ss; }, /** * Removes a style or link tag by id * @param {String} id The id of the tag */ removeStyleSheet : function(id) { var existing = doc.getElementById(id); if (existing) { existing.parentNode.removeChild(existing); } }, /** * Dynamically swaps an existing stylesheet reference for a new one * @param {String} id The id of an existing link tag to remove * @param {String} url The href of the new stylesheet to include */ swapStyleSheet : function(id, url) { var ss; CSS.removeStyleSheet(id); ss = doc.createElement("link"); ss.setAttribute("rel", "stylesheet"); ss.setAttribute("type", "text/css"); ss.setAttribute("id", id); ss.setAttribute("href", url); doc.getElementsByTagName("head")[0].appendChild(ss); }, // @private cacheStyleSheet : function(ss) { if (!rules) { rules = CSS.rules = {}; } try {// try catch for cross domain access issue var ssRules = ss.cssRules || ss.rules, i = ssRules.length - 1, imports = ss.imports, len = imports ? imports.length : 0, rule, j; // Old IE has a different way of handling imports for (j = 0; j < len; ++j) { CSS.cacheStyleSheet(imports[j]); } for (; i >= 0; --i) { rule = ssRules[i]; // If it's an @import rule, import its stylesheet if (rule.styleSheet) { CSS.cacheStyleSheet(rule.styleSheet); } CSS.cacheRule(rule, ss); } } catch(e) {} }, cacheRule: function(cssRule, styleSheet) { // If it's an @import rule, import its stylesheet if (cssRule.styleSheet) { return CSS.cacheStyleSheet(cssRule.styleSheet); } var selectorText = cssRule.selectorText, selectorCount, j; if (selectorText) { // Split in case there are multiple, comma-delimited selectors selectorText = selectorText.split(','); selectorCount = selectorText.length; for (j = 0; j < selectorCount; j++) { // IE<8 does not keep a reference to parentStyleSheet in the rule, so we // must cache an object like this until IE<8 is deprecated. rules[Ext.String.trim(selectorText[j]).toLowerCase()] = { parentStyleSheet: styleSheet, cssRule: cssRule }; } } }, /** * Gets all css rules for the document * @param {Boolean} refreshCache true to refresh the internal cache * @return {Object} An object (hash) of rules indexed by selector */ getRules : function(refreshCache) { var result = {}, selector; if (rules === null || refreshCache) { CSS.refreshCache(); } for (selector in rules) { result[selector] = rules[selector].cssRule; } return result; }, /** * Refresh the rule cache if you have dynamically added stylesheets * @return {Object} An object (hash) of rules indexed by selector */ refreshCache: function() { var ds = doc.styleSheets, i = 0, len = ds.length; rules = CSS.rules = {}; for (; i < len; i++) { try { if (!ds[i].disabled) { CSS.cacheStyleSheet(ds[i]); } } catch(e) {} } }, /** * Gets an an individual CSS rule by selector(s) * @param {String/String[]} selector The CSS selector or an array of selectors to try. The first selector that is found is returned. * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically * @return {CSSStyleRule} The CSS rule or null if one is not found */ getRule: function(selector, refreshCache, rawCache) { var i, result; if (!rules || refreshCache) { CSS.refreshCache(); } if (!Ext.isArray(selector)) { result = rules[selector.toLowerCase()]; if (result && !rawCache) { result = result.cssRule; } return result || null; } for (i = 0; i < selector.length; i++) { if (rules[selector[i]]) { return rawCache ? rules[selector[i].toLowerCase()] : rules[selector[i].toLowerCase()].cssRule; } } return null; }, /** * Creates a rule. * @param {CSSStyleSheet} styleSheet The StyleSheet to create the rule in as returned from {@link #createStyleSheet}. * @param {String} selector The selector to target the rule. * @param {String} property The cssText specification eg `"color:red;font-weight:bold;text-decoration:underline"` * @return {CSSStyleRule} The created rule */ createRule: function(styleSheet, selector, cssText) { var result, ruleSet = styleSheet.cssRules || styleSheet.rules, index = ruleSet.length; if (styleSheet.insertRule) { styleSheet.insertRule(selector + ' {' + cssText + '}', index); } else { styleSheet.addRule(selector, cssText||' '); } CSS.cacheRule(result = ruleSet[index], styleSheet); return result; }, /** * Updates a rule property * @param {String/String[]} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found. * @param {String} property The css property or a cssText specification eg `"color:red;font-weight:bold;text-decoration:underline"` * @param {String} value The new value for the property * @return {Boolean} true If a rule was found and updated */ updateRule : function(selector, property, value) { var rule, i, styles; if (!Ext.isArray(selector)) { rule = CSS.getRule(selector); if (rule) { // 2 arg form means cssText sent, so parse it and update each style if (arguments.length === 2) { styles = Ext.Element.parseStyles(property); for (property in styles) { rule.style[property.replace(camelRe, camelFn)] = styles[property]; } } else { rule.style[property.replace(camelRe, camelFn)] = value; } return true; } } else { for (i = 0; i < selector.length; i++) { if (CSS.updateRule(selector[i], property, value)) { return true; } } } return false; }, deleteRule: function(selector) { var rule = CSS.getRule(selector, false, true), styleSheet, index; if (rule) { styleSheet = rule.parentStyleSheet; index = Ext.Array.indexOf(styleSheet.cssRules || styleSheet.rules, rule.cssRule); if (styleSheet.deleteRule) { styleSheet.deleteRule(index); } else { styleSheet.removeRule(index); } delete rules[selector]; } } };});