/** * 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/CSSStyleSheet} stylesheet The id of the style tag, or the CSSStyleSheet * reference to remove */ removeStyleSheet: function(stylesheet) { var styleEl = (typeof stylesheet === 'string') ? doc.getElementById(stylesheet) : stylesheet.ownerNode; if (styleEl) { styleEl.parentNode.removeChild(styleEl); } }, /** * 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 // eslint-disable-next-line vars-on-top 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) { // ignore } }, cacheRule: function(cssRule, styleSheet) { var selectorText, selectorCount, j; // If it's an @import rule, import its stylesheet if (cssRule.styleSheet) { return CSS.cacheStyleSheet(cssRule.styleSheet); } selectorText = cssRule.selectorText; 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) { // ignore } } }, /** * 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 * @param rawCache 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} cssText The cssText specification e.g. * `"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 e.g. * `"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]; } } };});