UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

345 lines (288 loc) 9.2 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) * Andreas Ecker (ecker) * Fabian Jakobs (fjakobs) ************************************************************************ */ /** * Manager for decoration themes * * NOTE: Instances of this class must be disposed of after use * */ qx.Class.define("qx.theme.manager.Decoration", { type: "singleton", extend: qx.core.Object, implement: [qx.core.IDisposable], statics: { /** The prefix for all created CSS classes*/ CSS_CLASSNAME_PREFIX: "qx-" }, construct() { super(); this.__rules = []; this.__legacyIe = qx.core.Environment.get("engine.name") == "mshtml" && qx.core.Environment.get("browser.documentmode") < 9; }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties: { /** Selected decoration theme */ theme: { check: "Theme", nullable: true, apply: "_applyTheme", event: "changeTheme" } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members: { __dynamic: null, __rules: null, __legacyIe: false, /** * Returns the name which will be / is used as css class name. * @param value {String|qx.ui.decoration.IDecorator} The decorator string or instance. * @return {String} The css class name. */ getCssClassName(value) { var prefix = qx.theme.manager.Decoration.CSS_CLASSNAME_PREFIX; if (qx.lang.Type.isString(value)) { return prefix + value; } else { return prefix + value.toHashCode(); } }, /** * Adds a css class to the global stylesheet for the given decorator. * This includes resolving the decorator if it's a string. * @param value {String|qx.ui.decoration.IDecorator} The decorator string or instance. * @return {String} the css class name. */ addCssClass(value) { var sheet = qx.ui.style.Stylesheet.getInstance(); var instance = value; value = this.getCssClassName(value); var selector = "." + value; if (sheet.hasRule(selector)) { return value; } if (qx.lang.Type.isString(instance)) { instance = this.resolve(instance); } if (!instance) { throw new Error("Unable to resolve decorator '" + value + "'."); } // create and add a CSS rule var css = ""; var styles = instance.getStyles(true); // Sort the styles so that more specific styles come after the group styles, // eg background-color comes after background. The sort order is alphabetical // so that short cut rules come before actual Object.keys(styles) .sort() .forEach(function (key) { // if we find a map value, use it as pseudo class if (qx.Bootstrap.isObject(styles[key])) { var innerCss = ""; var innerStyles = styles[key]; var inner = false; for (var innerKey in innerStyles) { inner = true; innerCss += innerKey + ":" + innerStyles[innerKey] + ";"; } var innerSelector = this.__legacyIe ? selector : selector + (inner ? ":" : ""); this.__rules.push(innerSelector + key); sheet.addRule(innerSelector + key, innerCss); return; } css += key + ":" + styles[key] + ";"; }, this); if (css) { sheet.addRule(selector, css); this.__rules.push(selector); } return value; }, /** * Removes all previously by {@link #addCssClass} created CSS rule from * the global stylesheet. */ removeAllCssClasses() { // remove old rules for (var i = 0; i < this.__rules.length; i++) { var selector = this.__rules[i]; qx.ui.style.Stylesheet.getInstance().removeRule(selector); } this.__rules = []; }, /** * Returns the dynamically interpreted result for the incoming value * * @param value {String} dynamically interpreted idenfier * @return {var} return the (translated) result of the incoming value */ resolve(value) { if (!value) { return null; } if (typeof value === "object") { return value; } var cache = this.__dynamic; if (!cache) { cache = this.__dynamic = {}; } var resolved = cache[value]; if (resolved) { return resolved; } var theme = this.getTheme(); if (!theme) { return null; } if (!theme.decorations[value]) { return null; } // create an empty decorator var decorator = new qx.ui.decoration.Decorator(); // handle recursive decorator includes var recurseDecoratorInclude = function (currentEntry, name) { // follow the include chain to the topmost decorator entry if (currentEntry.include && theme.decorations[currentEntry.include]) { recurseDecoratorInclude( theme.decorations[currentEntry.include], currentEntry.include ); } // apply styles from the included decorator, // overwriting existing values. if (currentEntry.style) { decorator.set(currentEntry.style); } }; // start with the current decorator entry recurseDecoratorInclude(theme.decorations[value], value); cache[value] = decorator; return cache[value]; }, /** * Whether the given value is valid for being used in a property * with the 'check' configured to 'Decorator'. * * @param value {var} Incoming value * @return {Boolean} Whether the value is valid for being used in a Decorator property */ isValidPropertyValue(value) { if (typeof value === "string") { return this.isDynamic(value); } else if (typeof value === "object") { var clazz = value.constructor; return qx.Class.hasInterface(clazz, qx.ui.decoration.IDecorator); } return false; }, /** * Whether a value is interpreted dynamically * * @param value {String} dynamically interpreted identifier * @return {Boolean} returns <code>true</code> if the value is interpreted dynamically */ isDynamic(value) { if (!value) { return false; } var theme = this.getTheme(); if (!theme) { return false; } return !!theme.decorations[value]; }, /** * Whether the given decorator is cached * * @param decorator {String|qx.ui.decoration.IDecorator} The decorator to check * @return {Boolean} <code>true</code> if the decorator is cached * @internal */ isCached(decorator) { return !this.__dynamic ? false : qx.lang.Object.contains(this.__dynamic, decorator); }, // property apply _applyTheme(value, old) { var aliasManager = qx.util.AliasManager.getInstance(); // remove old rules this.removeAllCssClasses(); if (old) { for (var alias in old.aliases) { aliasManager.remove(alias); } } if (value) { for (var alias in value.aliases) { aliasManager.add(alias, value.aliases[alias]); } } this._disposeMap("__dynamic"); this.__dynamic = {}; }, /** * Clears internal caches and removes all previously created CSS classes. */ clear() { // remove aliases var aliasManager = qx.util.AliasManager.getInstance(); var theme = this.getTheme(); if (!aliasManager.isDisposed() && theme && theme.alias) { for (var alias in theme.aliases) { aliasManager.remove(alias, theme.aliases[alias]); } } // remove old rules this.removeAllCssClasses(); this._disposeMap("__dynamic"); this.__dynamic = {}; }, /** * Refreshes all decorator by clearing internal caches and re applying * aliases. */ refresh() { this.clear(); var aliasManager = qx.util.AliasManager.getInstance(); var theme = this.getTheme(); if (theme && theme.alias) { for (var alias in theme.aliases) { aliasManager.add(alias, theme.aliases[alias]); } } } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct() { this.clear(); } });