@qooxdoo/framework
Version:
The JS Framework for Coders
345 lines (288 loc) • 9.2 kB
JavaScript
/* ************************************************************************
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();
}
});