@qooxdoo/framework
Version:
The JS Framework for Coders
937 lines (796 loc) • 27.6 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)
* Martin Wittemann (martinwittemann)
************************************************************************ */
/**
* Create namespace
*
* @ignore(qx.data)
* @ignore(qx.data.IListData)
* @ignore(qx.util.OOUtil)
*/
if (!window.qx) {
window.qx = {};
}
/**
* Bootstrap qx.Bootstrap to create myself later
* This is needed for the API browser etc. to let them detect me
*/
qx.Bootstrap = {
genericToString : function() {
return "[Class " + this.classname + "]";
},
createNamespace : function(name, object)
{
var splits = name.split(".");
var part = splits[0];
var parent = qx.$$namespaceRoot && qx.$$namespaceRoot[part] ? qx.$$namespaceRoot : window;
for (var i=0, len=splits.length-1; i<len; i++, part=splits[i])
{
if (!parent[part]) {
parent = parent[part] = {};
} else {
parent = parent[part];
}
}
// store object
parent[part] = object;
// return last part name (e.g. classname)
return part;
},
setDisplayName : function(fcn, classname, name)
{
fcn.displayName = classname + "." + name + "()";
},
setDisplayNames : function(functionMap, classname)
{
for (var name in functionMap)
{
var value = functionMap[name];
if (value instanceof Function) {
value.displayName = classname + "." + name + "()";
}
}
},
base : function(args, varargs)
{
if (qx.Bootstrap.DEBUG) {
if (!qx.Bootstrap.isFunction(args.callee.base)) {
throw new Error(
"Cannot call super class. Method is not derived: " +
args.callee.displayName
);
}
}
if (arguments.length === 1) {
return args.callee.base.call(this);
} else {
return args.callee.base.apply(this, Array.prototype.slice.call(arguments, 1));
}
},
define : function(name, config)
{
var isStrictMode = function () {
return (typeof this == 'undefined');
};
if (!config) {
config = { statics : {} };
}
var clazz;
var proto = null;
qx.Bootstrap.setDisplayNames(config.statics, name);
if (config.members || config.extend)
{
qx.Bootstrap.setDisplayNames(config.members, name + ".prototype");
clazz = config.construct || new Function;
if (config.extend) {
this.extendClass(clazz, clazz, config.extend, name, basename);
}
var statics = config.statics || {};
// use keys to include the shadowed in IE
for (var i=0, keys=qx.Bootstrap.keys(statics), l=keys.length; i<l; i++) {
var key = keys[i];
clazz[key] = statics[key];
}
proto = clazz.prototype;
// Enable basecalls within constructor
proto.base = qx.Bootstrap.base;
proto.name = proto.classname = name;
var members = config.members || {};
var key, member;
// use keys to include the shadowed in IE
for (var i=0, keys=qx.Bootstrap.keys(members), l=keys.length; i<l; i++) {
key = keys[i];
member = members[key];
// Enable basecalls for methods
// Hint: proto[key] is not yet overwritten here
if (member instanceof Function && proto[key]) {
member.base = proto[key];
}
proto[key] = member;
}
}
else
{
clazz = config.statics || {};
// Merge class into former class (needed for 'optimize: ["statics"]')
if (qx.Bootstrap.$$registry && qx.Bootstrap.$$registry[name]) {
var formerClass = qx.Bootstrap.$$registry[name];
// Add/overwrite properties and return early if necessary
if (this.keys(clazz).length !== 0) {
// Execute defer to prevent too early overrides
if (config.defer) {
config.defer(clazz, proto);
}
for (var curProp in clazz) {
formerClass[curProp] = clazz[curProp];
}
return formerClass;
}
}
}
// Store type info
clazz.$$type = "Class";
// Attach toString
if (!clazz.hasOwnProperty("toString")) {
clazz.toString = this.genericToString;
}
// Create namespace
var basename = name ? this.createNamespace(name, clazz) : "";
// Store names in constructor/object
clazz.classname = name;
if (!isStrictMode()) {
try {
clazz.name = name;
} catch(ex) {
// Nothing
}
}
clazz.basename = basename;
clazz.$$events = config.events;
// Execute defer section
if (config.defer) {
this.addPendingDefer(clazz, function() {
config.defer(clazz, proto);
});
}
// Store class reference in global class registry
if (name != null) {
qx.Bootstrap.$$registry[name] = clazz;
}
return clazz;
}
};
/**
* Internal class that is responsible for bootstrapping the qooxdoo
* framework at load time.
*/
qx.Bootstrap.define("qx.Bootstrap",
{
statics :
{
/** Timestamp of qooxdoo based application startup */
LOADSTART : qx.$$start || new Date(),
/**
* Mapping for early use of the qx.debug environment setting.
*/
DEBUG : (function() {
// make sure to reflect all changes here to the environment class!
var debug = true;
if (qx.$$environment && qx.$$environment["qx.debug"] === false) {
debug = false;
}
return debug;
})(),
/**
* Minimal accessor API for the environment settings given from the
* generator.
*
* WARNING: This method only should be used if the
* {@link qx.core.Environment} class is not loaded!
*
* @param key {String} The key to get the value from.
* @return {var} The value of the setting or <code>undefined</code>.
*/
getEnvironmentSetting : function(key) {
if (qx.$$environment) {
return qx.$$environment[key];
}
},
/**
* Minimal mutator for the environment settings given from the generator.
* It checks for the existence of the environment settings and sets the
* key if its not given from the generator. If a setting is available from
* the generator, the setting will be ignored.
*
* WARNING: This method only should be used if the
* {@link qx.core.Environment} class is not loaded!
*
* @param key {String} The key of the setting.
* @param value {var} The value for the setting.
*/
setEnvironmentSetting : function(key, value) {
if (!qx.$$environment) {
qx.$$environment = {};
}
if (qx.$$environment[key] === undefined) {
qx.$$environment[key] = value;
}
},
/**
* Creates a namespace and assigns the given object to it.
*
* @internal
* @signature function(name, object)
* @param name {String} The complete namespace to create. Typically, the last part is the class name itself
* @param object {Object} The object to attach to the namespace
* @return {String} last part of the namespace (which object is assigned to)
* @throws {Error} when the given object already exists.
*/
createNamespace : qx.Bootstrap.createNamespace,
/**
* Offers the ability to change the root for creating namespaces from window to
* whatever object is given.
*
* @param root {Object} The root to use.
* @internal
*/
setRoot : function(root) {
qx.$$namespaceRoot = root;
},
/**
* Call the same method of the super class.
*
* @signature function(args, varargs)
* @param args {arguments} the arguments variable of the calling method
* @param varargs {var} variable number of arguments passed to the overwritten function
* @return {var} the return value of the method of the base class.
*/
base : qx.Bootstrap.base,
/**
* Define a new class using the qooxdoo class system.
* Lightweight version of {@link qx.Class#define} with less features.
*
* @signature function(name, config)
* @param name {String?} Name of the class. If null, the class will not be
* attached to a namespace.
* @param config {Map ? null} Class definition structure. The configuration map has the following keys:
* <table>
* <tr><th>Name</th><th>Type</th><th>Description</th></tr>
* <tr><th>extend</th><td>Class</td><td>The super class the current class inherits from.</td></tr>
* <tr><th>construct</th><td>Function</td><td>The constructor of the class.</td></tr>
* <tr><th>statics</th><td>Map</td><td>Map of static values / functions of the class.</td></tr>
* <tr><th>members</th><td>Map</td><td>Map of instance members of the class.</td></tr>
* <tr><th>defer</th><td>Function</td><td>Function that is called at the end of
* processing the class declaration.</td></tr>
* </table>
* @return {Class} The defined class.
*/
define : qx.Bootstrap.define,
/**
* Sets the display name of the given function
*
* @signature function(fcn, classname, name)
* @param fcn {Function} the function to set the display name for
* @param classname {String} the name of the class the function is defined in
* @param name {String} the function name
*/
setDisplayName : qx.Bootstrap.setDisplayName,
/**
* Set the names of all functions defined in the given map
*
* @signature function(functionMap, classname)
* @param functionMap {Object} a map with functions as values
* @param classname {String} the name of the class, the functions are
* defined in
*/
setDisplayNames : qx.Bootstrap.setDisplayNames,
/**
* This method will be attached to all classes to return
* a nice identifier for them.
*
* @internal
* @signature function()
* @return {String} The class identifier
*/
genericToString : qx.Bootstrap.genericToString,
/**
* Inherit a clazz from a super class.
*
* This function differentiates between class and constructor because the
* constructor written by the user might be wrapped and the <code>base</code>
* property has to be attached to the constructor, while the <code>superclass</code>
* property has to be attached to the wrapped constructor.
*
* @param clazz {Function} The class's wrapped constructor
* @param construct {Function} The unwrapped constructor
* @param superClass {Function} The super class
* @param name {Function} fully qualified class name
* @param basename {Function} the base name
*/
extendClass : function(clazz, construct, superClass, name, basename)
{
var superproto = superClass.prototype;
// Use helper function/class to save the unnecessary constructor call while
// setting up inheritance.
var helper = new Function();
helper.prototype = superproto;
var proto = new helper();
// Apply prototype to new helper instance
clazz.prototype = proto;
// Store names in prototype
proto.name = proto.classname = name;
proto.basename = basename;
/*
- Store base constructor to constructor-
- Store reference to extend class
*/
construct.base = superClass;
clazz.superclass = superClass;
/*
- Store statics/constructor onto constructor/prototype
- Store correct constructor
- Store statics onto prototype
*/
construct.self = clazz.constructor = proto.constructor = clazz;
},
/** Private list of classes which have a defer method that needs to be executed */
__pendingDefers: [],
/**
* Adds a callback for a class so that it's defer method can be called, either after all classes
* are loaded or when absolutely necessary because of load-time requirements of other classes.
*
* @param clazz {Class} Class to add a callback to
* @param cb {Function} Callback function
*/
addPendingDefer: function(clazz, cb) {
if (qx.$$loader && qx.$$loader.delayDefer) {
this.__pendingDefers.push(clazz);
clazz.$$pendingDefer = cb;
} else {
cb.call(clazz);
}
},
/**
* Executes the defer methods for classes which are required by the dependency information in
* dbClassInfo (which is a map in the format generated by qxcompiler). Defer methods are of course
* only executed once but they are always put off until absolutely necessary to avoid potential
* side effects and recursive and/or difficult to resolve dependencies.
*
* @param dbClassInfo {Object} qxcompiler map
*/
executePendingDefers: function(dbClassInfo) {
var executeForDbClassInfo = function (dbClassInfo) {
if (dbClassInfo.environment) {
var required = dbClassInfo.environment.required;
if (required) {
for (var key in required) {
var info = required[key];
if (info.load && info.className) {
executeForClassName(info.className);
}
}
}
}
for (var key in dbClassInfo.dependsOn) {
var depInfo = dbClassInfo.dependsOn[key];
if (depInfo.require || depInfo.usage === "dynamic") {
executeForClassName(key);
}
}
}
var executeForClassName = function (className) {
var clazz = getByName(className);
if (!clazz) {
return;
}
if (clazz.$$deferComplete) {
return;
}
var dbClassInfo = clazz.$$dbClassInfo;
if (dbClassInfo) {
executeForDbClassInfo(dbClassInfo);
}
execute(clazz);
}
var execute = function (clazz) {
var cb = clazz.$$pendingDefer;
if (cb) {
delete clazz.$$pendingDefer;
clazz.$$deferComplete = true;
cb.call(clazz);
}
}
var getByName = function (name) {
var clazz = qx.Bootstrap.getByName(name);
if (!clazz) {
var splits = name.split(".");
var part = splits[0];
var root = qx.$$namespaceRoot && qx.$$namespaceRoot[part] ? qx.$$namespaceRoot : window;
var tmp = root;
for (var i = 0, len = splits.length - 1; tmp && i < len; i++, part = splits[i]) {
tmp = tmp[part];
}
if (tmp != root) {
clazz = tmp;
}
}
return clazz;
}
if (!dbClassInfo) {
var pendingDefers = this.__pendingDefers;
this.__pendingDefers = [];
pendingDefers.forEach(execute);
return;
}
executeForDbClassInfo(dbClassInfo);
},
/**
* Find a class by its name
*
* @param name {String} class name to resolve
* @return {Class} the class
*/
getByName : function(name) {
return qx.Bootstrap.$$registry[name];
},
/** @type {Map} Stores all defined classes */
$$registry : {},
/*
---------------------------------------------------------------------------
OBJECT UTILITY FUNCTIONS
---------------------------------------------------------------------------
*/
/**
* Get the number of own properties in the object.
*
* @param map {Object} the map
* @return {Integer} number of objects in the map
* @lint ignoreUnused(key)
*/
objectGetLength : function(map) {
return qx.Bootstrap.keys(map).length;
},
/**
* Inserts all keys of the source object into the
* target objects. Attention: The target map gets modified.
*
* @param target {Object} target object
* @param source {Object} object to be merged
* @param overwrite {Boolean ? true} If enabled existing keys will be overwritten
* @return {Object} Target with merged values from the source object
*/
objectMergeWith : function(target, source, overwrite)
{
if (overwrite === undefined) {
overwrite = true;
}
for (var key in source)
{
if (overwrite || target[key] === undefined) {
target[key] = source[key];
}
}
return target;
},
/**
* IE does not return "shadowed" keys even if they are defined directly
* in the object.
*
* @internal
* @type {String[]}
*/
__shadowedKeys :
[
"isPrototypeOf",
"hasOwnProperty",
"toLocaleString",
"toString",
"valueOf",
"propertyIsEnumerable",
"constructor"
],
/**
* Get the keys of a map as array as returned by a "for ... in" statement.
*
* @signature function(map)
* @internal
* @param map {Object} the map
* @return {Array} array of the keys of the map
*/
keys :
({
"ES5" : Object.keys,
"BROKEN_IE" : function(map)
{
if (map === null || (typeof map !== "object" && typeof map !== "function")) {
throw new TypeError("Object.keys requires an object as argument.");
}
var arr = [];
var hasOwnProperty = Object.prototype.hasOwnProperty;
for (var key in map) {
if (hasOwnProperty.call(map, key)) {
arr.push(key);
}
}
// IE does not return "shadowed" keys even if they are defined directly
// in the object. This is incompatible with the ECMA standard!!
// This is why this checks are needed.
var shadowedKeys = qx.Bootstrap.__shadowedKeys;
for (var i=0, a=shadowedKeys, l=a.length; i<l; i++)
{
if (hasOwnProperty.call(map, a[i])) {
arr.push(a[i]);
}
}
return arr;
},
"default" : function(map)
{
if (map === null || (typeof map !== "object" && typeof map !== "function")) {
throw new TypeError("Object.keys requires an object as argument.");
}
var arr = [];
var hasOwnProperty = Object.prototype.hasOwnProperty;
for (var key in map) {
if (hasOwnProperty.call(map, key)) {
arr.push(key);
}
}
return arr;
}
})[
typeof(Object.keys) === "function" ? "ES5" :
(function() {for (var key in {toString : 1}) { return key; }})() !== "toString" ? "BROKEN_IE" : "default"
],
/**
* Mapping from JavaScript string representation of objects to names
* @internal
* @type {Map}
*/
__classToTypeMap :
{
"[object String]": "String",
"[object Array]": "Array",
"[object Object]": "Object",
"[object RegExp]": "RegExp",
"[object Number]": "Number",
"[object Boolean]": "Boolean",
"[object Date]": "Date",
"[object Function]": "Function",
"[object Error]": "Error",
"[object Blob]": "Blob",
"[object ArrayBuffer]": "ArrayBuffer",
"[object FormData]": "FormData"
},
/*
---------------------------------------------------------------------------
FUNCTION UTILITY FUNCTIONS
---------------------------------------------------------------------------
*/
/**
* Returns a function whose "this" is altered.
*
* *Syntax*
*
* <pre class='javascript'>qx.Bootstrap.bind(myFunction, [self, [varargs...]]);</pre>
*
* *Example*
*
* <pre class='javascript'>
* function myFunction()
* {
* this.setStyle('color', 'red');
* // note that 'this' here refers to myFunction, not an element
* // we'll need to bind this function to the element we want to alter
* };
*
* var myBoundFunction = qx.Bootstrap.bind(myFunction, myElement);
* myBoundFunction(); // this will make the element myElement red.
* </pre>
*
* @param func {Function} Original function to wrap
* @param self {Object ? null} The object that the "this" of the function will refer to.
* @param varargs {arguments ? null} The arguments to pass to the function.
* @return {Function} The bound function.
*/
bind : function(func, self, varargs)
{
var fixedArgs = Array.prototype.slice.call(arguments, 2, arguments.length);
return function() {
var args = Array.prototype.slice.call(arguments, 0, arguments.length);
return func.apply(self, fixedArgs.concat(args));
};
},
/*
---------------------------------------------------------------------------
STRING UTILITY FUNCTIONS
---------------------------------------------------------------------------
*/
/**
* Convert the first character of the string to upper case.
*
* @param str {String} the string
* @return {String} the string with an upper case first character
*/
firstUp : function(str) {
return str.charAt(0).toUpperCase() + str.substr(1);
},
/**
* Convert the first character of the string to lower case.
*
* @param str {String} the string
* @return {String} the string with a lower case first character
*/
firstLow : function(str) {
return str.charAt(0).toLowerCase() + str.substr(1);
},
/*
---------------------------------------------------------------------------
TYPE UTILITY FUNCTIONS
---------------------------------------------------------------------------
*/
/**
* Get the internal class of the value. See
* http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
* for details.
*
* @param value {var} value to get the class for
* @return {String} the internal class of the value
*/
getClass : function(value)
{
// The typeof null and undefined is "object" under IE8
if(value === undefined) {
return "Undefined";
}else if(value === null) {
return "Null";
}
var classString = Object.prototype.toString.call(value);
return (
qx.Bootstrap.__classToTypeMap[classString] ||
classString.slice(8, -1)
);
},
/**
* Whether the value is a string.
*
* @param value {var} Value to check.
* @return {Boolean} Whether the value is a string.
*/
isString : function(value)
{
// Added "value !== null" because IE throws an exception "Object expected"
// by executing "value instanceof String" if value is a DOM element that
// doesn't exist. It seems that there is an internal difference between a
// JavaScript null and a null returned from calling DOM.
// e.q. by document.getElementById("ReturnedNull").
return (
value !== null && (
typeof value === "string" ||
qx.Bootstrap.getClass(value) === "String" ||
value instanceof String ||
(!!value && !!value.$$isString))
);
},
/**
* Whether the value is an array.
*
* @param value {var} Value to check.
* @return {Boolean} Whether the value is an array.
*/
isArray : function(value)
{
// Added "value !== null" because IE throws an exception "Object expected"
// by executing "value instanceof Array" if value is a DOM element that
// doesn't exist. It seems that there is an internal difference between a
// JavaScript null and a null returned from calling DOM.
// e.q. by document.getElementById("ReturnedNull").
return (
value !== null && (
value instanceof Array ||
(value && qx.data && qx.data.IListData && qx.util.OOUtil.hasInterface(value.constructor, qx.data.IListData) ) ||
qx.Bootstrap.getClass(value) === "Array" ||
(!!value && !!value.$$isArray))
);
},
/**
* Whether the value is an object. Note that built-in types like Window are
* not reported to be objects.
*
* @param value {var} Value to check.
* @return {Boolean} Whether the value is an object.
*/
isObject : function(value) {
return (
value !== undefined &&
value !== null &&
qx.Bootstrap.getClass(value) === "Object"
);
},
/**
* Whether the value is a function.
*
* @param value {var} Value to check.
* @return {Boolean} Whether the value is a function.
*/
isFunction : function(value) {
return qx.Bootstrap.getClass(value) === "Function";
},
/**
* Whether the value is a function or an async function.
*
* @param value {var} Value to check.
* @return {Boolean} Whether the value is a function.
*/
isFunctionOrAsyncFunction : function(value) {
var name = qx.Bootstrap.getClass(value)
return ((name === "Function")||(name === "AsyncFunction"));
},
/*
---------------------------------------------------------------------------
LOGGING UTILITY FUNCTIONS
---------------------------------------------------------------------------
*/
$$logs : [],
/**
* Sending a message at level "debug" to the logger.
*
* @param object {Object} Contextual object (either instance or static class)
* @param message {var} Any number of arguments supported. An argument may
* have any JavaScript data type. All data is serialized immediately and
* does not keep references to other objects.
*/
debug : function(object, message) {
qx.Bootstrap.$$logs.push(["debug", arguments]);
},
/**
* Sending a message at level "info" to the logger.
*
* @param object {Object} Contextual object (either instance or static class)
* @param message {var} Any number of arguments supported. An argument may
* have any JavaScript data type. All data is serialized immediately and
* does not keep references to other objects.
*/
info : function(object, message) {
qx.Bootstrap.$$logs.push(["info", arguments]);
},
/**
* Sending a message at level "warn" to the logger.
*
* @param object {Object} Contextual object (either instance or static class)
* @param message {var} Any number of arguments supported. An argument may
* have any JavaScript data type. All data is serialized immediately and
* does not keep references to other objects.
*/
warn : function(object, message) {
qx.Bootstrap.$$logs.push(["warn", arguments]);
},
/**
* Sending a message at level "error" to the logger.
*
* @param object {Object} Contextual object (either instance or static class)
* @param message {var} Any number of arguments supported. An argument may
* have any JavaScript data type. All data is serialized immediately and
* does not keep references to other objects.
*/
error : function(object, message) {
qx.Bootstrap.$$logs.push(["error", arguments]);
},
/**
* Prints the current stack trace at level "info"
*
* @param object {Object} Contextual object (either instance or static class)
*/
trace : function(object) {}
}
});