| /******************************************************************************* |
| * Copyright: 2004, 2012 1&1 Internet AG, Germany, http://www.1und1.de, |
| * and EclipseSource |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v1.0 which accompanies this |
| * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * 1&1 Internet AG and others - original API and implementation |
| * EclipseSource - adaptation for the Eclipse Rich Ajax Platform |
| ******************************************************************************/ |
| |
| /** |
| * This class is one of the most important parts of qooxdoo's |
| * object-oriented features. |
| * |
| * Its {@link #define} method is used to create qooxdoo classes. |
| * |
| * Each instance of a class defined by {@link #define} has |
| * the following keys attached to the constructor and the prototype: |
| * |
| * <table> |
| * <tr><th><code>classname</code></th><td>The fully-qualified name of the class (e.g. <code>"qx.ui.core.Widget"</code>).</td></tr> |
| * <tr><th><code>basename</code></th><td>The namespace part of the class name (e.g. <code>"qx.ui.core"</code>).</td></tr> |
| * <tr><th><code>constructor</code></th><td>A reference to the constructor of the class.</td></tr> |
| * <tr><th><code>superclass</code></th><td>A reference to the constructor of the super class.</td></tr> |
| * </table> |
| * |
| * Each method may access static members of the same class by using |
| * <code>this.self(arguments)</code> ({@link qx.core.Object#self}): |
| * <pre class='javascript'> |
| * statics : { FOO : "bar" }, |
| * members: { |
| * baz: function(x) { |
| * this.self(arguments).FOO; |
| * ... |
| * } |
| * } |
| * </pre> |
| * |
| * Each overriding method may call the overridden method by using |
| * <code>this.base(arguments [, ...])</code> ({@link qx.core.Object#base}). This is also true for calling |
| * the constructor of the superclass. |
| * <pre class='javascript'> |
| * members: { |
| * foo: function(x) { |
| * this.base(arguments, x); |
| * ... |
| * } |
| * } |
| * </pre> |
| */ |
| qx.Class.define("qx.Class", |
| { |
| statics : |
| { |
| |
| _normalizeConfig : function( config ) { |
| if (!config) { |
| var config = {}; |
| } |
| if (config.include && !(config.include instanceof Array)) { |
| config.include = [config.include]; |
| } |
| if (config.implement && !(config.implement instanceof Array)) { |
| config.implement = [config.implement]; |
| } |
| if (!config.hasOwnProperty("extend") && !config.type) { |
| config.type = "static"; |
| } |
| return config; |
| }, |
| |
| /** |
| * Define a new class using the qooxdoo class system. This sets up the |
| * namespace for the class and generates the class from the definition map. |
| * |
| * Example: |
| * <pre class='javascript'> |
| * qx.Class.define("name", |
| * { |
| * extend : Object, // superclass |
| * implement : [Interfaces], |
| * include : [Mixins], |
| * |
| * statics: |
| * { |
| * CONSTANT : 3.141, |
| * |
| * publicMethod: function() {}, |
| * _protectedMethod: function() {}, |
| * __privateMethod: function() {} |
| * }, |
| * |
| * properties: |
| * { |
| * "tabIndexOld": { type: "number", defaultValue : -1, _legacy : true } |
| * "tabIndex": { check: "Number", init : -1 } |
| * }, |
| * |
| * members: |
| * { |
| * publicField: "foo", |
| * publicMethod: function() {}, |
| * |
| * _protectedField: "bar", |
| * _protectedMethod: function() {}, |
| * |
| * __privateField: "baz", |
| * __privateMethod: function() {} |
| * } |
| * }); |
| * </pre> |
| * |
| * @type static |
| * @param name {String} Name of the class |
| * @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>type</th><td>String</td><td> |
| * Type of the class. Valid types are "abstract", "static" and "singleton". |
| * If unset it defaults to a regular non-static class. |
| * </td></tr> |
| * <tr><th>extend</th><td>Class</td><td>The super class the current class inherits from.</td></tr> |
| * <tr><th>implement</th><td>Interface | Interface[]</td><td>Single interface or array of interfaces the class implements.</td></tr> |
| * <tr><th>include</th><td>Mixin | Mixin[]</td><td>Single mixin or array of mixins, which will be merged into the class.</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 members of the class.</td></tr> |
| * <tr><th>properties</th><td>Map</td><td>Map of property definitions. For a description of the format of a property definition see |
| * {@link qx.core.Property} or the legacy version {@link qx.core.LegacyProperty}.</td></tr> |
| * <tr><th>members</th><td>Map</td><td>Map of instance members of the class.</td></tr> |
| * <tr><th>settings</th><td>Map</td><td>Map of settings for this class. For a description of the format of a setting see |
| * {@link qx.core.Setting}.</td></tr> |
| * <tr><th>variants</th><td>Map</td><td>Map of settings for this class. For a description of the format of a setting see |
| * {@link qx.core.Variant}</td></tr> |
| * <tr><th>events</th><td>Map</td><td> |
| * Map of events the class fires. The keys are the names of the events and the values are the |
| * corresponding event type class names. |
| * </td></tr> |
| * <tr><th>defer</th><td>Function</td><td>Function that is called at the end of processing the class declaration. It allows access to the declared statics, members and properties.</td></tr> |
| * <tr><th>destruct</th><td>Function</td><td>The destructor of the class.</td></tr> |
| * </table> |
| * @return {void} |
| * @throws TODOC |
| */ |
| define : function(name, config) { |
| if( this._stopLoading ) { |
| throw new Error( "Stop loading " + name ); |
| } |
| try { |
| config = this._normalizeConfig( config ); |
| this.__validateConfig( name, config ); |
| var clazz; |
| if( !config.extend ) { |
| clazz = config.statics || {}; |
| } else { |
| if( !config.construct ) { |
| config.construct = this.__createDefaultConstructor(); |
| } |
| clazz = this.__wrapConstructor(config.construct, name, config.type); |
| if( config.statics ) { |
| var key; |
| for(var i=0, a=qx.lang.Object.getKeys(config.statics), l=a.length; i<l; i++) { |
| key = a[i]; |
| clazz[key] = config.statics[key]; |
| } |
| } |
| } |
| var basename = this.createNamespace(name, clazz, false); |
| clazz.name = clazz.classname = name; |
| clazz.basename = basename; |
| this.__registry[ name ] = clazz; |
| |
| // Attach toString |
| if (!clazz.hasOwnProperty("toString")) { |
| clazz.toString = this.genericToString; |
| } |
| |
| if( config.extend ) { |
| var superproto = config.extend.prototype; |
| var helper = this.__createEmptyFunction(); |
| helper.prototype = superproto; |
| var proto = new helper; |
| clazz.prototype = proto; |
| proto.name = proto.classname = name; |
| proto.basename = basename; |
| config.construct.base = clazz.superclass = config.extend; |
| config.construct.self = clazz.constructor = proto.constructor = clazz; |
| if( config.destruct ) { |
| clazz.$$destructor = config.destruct; |
| } |
| var that = this; |
| clazz.$$initializer = function() { |
| //console.log( "init " + name ); |
| if( config.properties ) { |
| that.__addProperties(clazz, config.properties, true); |
| } |
| if( config.members ) { |
| that.__addMembers(clazz, config.members, true, true, false); |
| } |
| if( config.events ) { |
| that.__addEvents(clazz, config.events, true); |
| } |
| if( config.include ) { |
| for (var i=0, l=config.include.length; i<l; i++) { |
| that.__addMixin(clazz, config.include[i], false); |
| } |
| } |
| } |
| } |
| if( config.variants ) { |
| for (var key in config.variants) { |
| qx.core.Variant.define(key, config.variants[key].allowedValues, config.variants[key].defaultValue); |
| } |
| } |
| if( config.defer ) { |
| this.__initializeClass( clazz ); |
| config.defer.self = clazz; |
| config.defer(clazz, clazz.prototype, { |
| add : function( name, config ) { |
| var properties = {}; |
| properties[name] = config; |
| qx.Class.__addProperties(clazz, properties, true); |
| } |
| } ); |
| } |
| } catch( ex ) { |
| alert( "Error loading class " + name + ": " + ex ); |
| this._stopLoading = true; |
| throw ex; |
| } |
| }, |
| |
| |
| /** |
| * Creates a namespace and assigns the given object to it. |
| * |
| * @type static |
| * @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 {Object} last part of the namespace (typically the class name) |
| * @throws TODOC |
| */ |
| createNamespace : function(name, object) |
| { |
| var splits = name.split("."); |
| var parent = window; |
| var part = splits[0]; |
| |
| for (var i=0, l=splits.length-1; i<l; i++, part=splits[i]) |
| { |
| if (!parent[part]) { |
| parent = parent[part] = {}; |
| } else { |
| parent = parent[part]; |
| } |
| } |
| if (parent[part] === undefined) { |
| parent[part] = object; |
| } |
| // return last part name (i.e. classname) |
| return part; |
| }, |
| |
| |
| /** |
| * Whether the given class exists |
| * |
| * @type static |
| * @param name {String} class name to check |
| * @return {Boolean} true if class exists |
| */ |
| isDefined : function(name) { |
| return this.getByName(name) !== undefined; |
| }, |
| |
| |
| /** |
| * Determine the total number of classes |
| * |
| * @type static |
| * @return {Number} the total number of classes |
| */ |
| getTotalNumber : function() { |
| return qx.lang.Object.getLength(this.__registry); |
| }, |
| |
| |
| /** |
| * Find a class by its name |
| * |
| * @type static |
| * @param name {String} class name to resolve |
| * @return {Class} the class |
| */ |
| getByName : function(name) { |
| return this.__registry[name]; |
| }, |
| |
| |
| /** |
| * Include all features of the given mixin into the class. The mixin must |
| * not include any methods or properties that are already available in the |
| * class. This would only be possible using the {@link #patch} method. |
| * |
| * @type static |
| * @param clazz {Class} An existing class which should be modified by including the mixin. |
| * @param mixin {Mixin} The mixin to be included. |
| */ |
| include : function(clazz, mixin) |
| { |
| qx.Class.__addMixin(clazz, mixin, false); |
| }, |
| |
| |
| /** |
| * Include all features of the given mixin into the class. The mixin may |
| * include features which are already defined in the target class. Existing |
| * features of equal name will be overwritten. |
| * Please keep in mind that this functionality is not intented for regular |
| * use, but as a formalized way (and a last resort) in order to patch |
| * existing classes. |
| * |
| * <b>WARNING</b>: You may break working classes and features. |
| * |
| * @type static |
| * @param clazz {Class} An existing class which should be modified by including the mixin. |
| * @param mixin {Mixin} The mixin to be included. |
| */ |
| patch : function(clazz, mixin) |
| { |
| qx.Class.__addMixin(clazz, mixin, true); |
| }, |
| |
| |
| /** |
| * Whether a class is a direct or indirect sub class of another class, |
| * or both classes coincide. |
| * |
| * @type static |
| * @param clazz {Class} the class to check. |
| * @param superClass {Class} the potential super class |
| * @return {Boolean} whether clazz is a sub class of superClass. |
| */ |
| isSubClassOf : function(clazz, superClass) |
| { |
| if (!clazz) { |
| return false; |
| } |
| |
| if (clazz == superClass) { |
| return true; |
| } |
| |
| if (clazz.prototype instanceof superClass) { |
| return true; |
| } |
| |
| return false; |
| }, |
| |
| |
| /** |
| * Returns the definition of the given property. Returns null |
| * if the property does not exist. |
| * |
| * TODO: Correctly support refined properties? |
| * |
| * @type member |
| * @param clazz {Class} class to check |
| * @param name {String} name of the event to check for |
| * @return {Map|null} whether the object support the given event. |
| */ |
| getPropertyDefinition : function(clazz, name) |
| { |
| while (clazz) |
| { |
| if (clazz.$$properties && clazz.$$properties[name]) { |
| return clazz.$$properties[name]; |
| } |
| |
| clazz = clazz.superclass; |
| } |
| |
| return null; |
| }, |
| |
| |
| /** |
| * Returns the class or one of its superclasses which contains the |
| * declaration for the given property in its class definition. Returns null |
| * if the property is not specified anywhere. |
| * |
| * @param clazz {Class} class to look for the property |
| * @param name {String} name of the property |
| * @return {Class | null} The class which includes the property |
| */ |
| getByProperty : function(clazz, name) |
| { |
| while (clazz) |
| { |
| if (clazz.$$properties && clazz.$$properties[name]) { |
| return clazz; |
| } |
| |
| clazz = clazz.superclass; |
| } |
| |
| return null; |
| }, |
| |
| |
| /** |
| * Whether a class has the given property |
| * |
| * @type member |
| * @param clazz {Class} class to check |
| * @param name {String} name of the property to check for |
| * @return {Boolean} whether the class includes the given property. |
| */ |
| hasProperty : function(clazz, name) { |
| return !!this.getPropertyDefinition(clazz, name); |
| }, |
| |
| |
| /** |
| * Returns the event type of the given event. Returns null if |
| * the event does not exist. |
| * |
| * @type member |
| * @param clazz {Class} class to check |
| * @param name {String} name of the event |
| * @return {Map|null} Event type of the given event. |
| */ |
| getEventType : function(clazz, name) |
| { |
| var clazz = clazz.constructor; |
| |
| while (clazz.superclass) |
| { |
| if (clazz.$$events && clazz.$$events[name] !== undefined) { |
| return clazz.$$events[name]; |
| } |
| |
| clazz = clazz.superclass; |
| } |
| |
| return null; |
| }, |
| |
| |
| /** |
| * Whether a class supports the given event type |
| * |
| * @type member |
| * @param clazz {Class} class to check |
| * @param name {String} name of the event to check for |
| * @return {Boolean} whether the class supports the given event. |
| */ |
| supportsEvent : function(clazz, name) { |
| return !!this.getEventType(clazz, name); |
| }, |
| |
| |
| /** |
| * Whether a class directly includes a mixin. |
| * |
| * @type static |
| * @param clazz {Class} class to check |
| * @param mixin {Mixin} the mixin to check for |
| * @return {Boolean} whether the class includes the mixin directly. |
| */ |
| hasOwnMixin: function(clazz, mixin) { |
| return clazz.$$includes && clazz.$$includes.indexOf(mixin) !== -1; |
| }, |
| |
| |
| /** |
| * Returns the class or one of its superclasses which contains the |
| * declaration for the given mixin. Returns null if the mixin is not |
| * specified anywhere. |
| * |
| * @param clazz {Class} class to look for the mixin |
| * @param mixin {Mixin} mixin to look for |
| * @return {Class | null} The class which directly includes the given mixin |
| */ |
| getByMixin : function(clazz, mixin) |
| { |
| var list, i, l; |
| |
| while (clazz) |
| { |
| if (clazz.$$includes) |
| { |
| list = clazz.$$flatIncludes; |
| |
| for (i=0, l=list.length; i<l; i++) |
| { |
| if (list[i] === mixin) { |
| return clazz; |
| } |
| } |
| } |
| |
| clazz = clazz.superclass; |
| } |
| |
| return null; |
| }, |
| |
| |
| /** |
| * Returns a list of all mixins available in a given class. |
| * |
| * @param clazz {Class} class which should be inspected |
| * @return {Mixin[]} array of mixins this class uses |
| */ |
| getMixins : function(clazz) |
| { |
| var list = []; |
| |
| while (clazz) |
| { |
| if (clazz.$$includes) { |
| list.push.apply(list, clazz.$$flatIncludes); |
| } |
| |
| clazz = clazz.superclass; |
| } |
| |
| return list; |
| }, |
| |
| |
| /** |
| * Whether a given class or any of its superclasses includes a given mixin. |
| * |
| * @type static |
| * @param clazz {Class} class to check |
| * @param mixin {Mixin} the mixin to check for |
| * @return {Boolean} whether the class includes the mixin. |
| */ |
| hasMixin: function(clazz, mixin) { |
| return !!this.getByMixin(clazz, mixin); |
| }, |
| |
| |
| /** |
| * Whether a given class directly includes a interface. |
| * |
| * This function will only return "true" if the interface was defined |
| * in the class declaration (@link qx.Class#define}) using the "implement" |
| * key. |
| * |
| * @type static |
| * @param clazz {Class} class or instance to check |
| * @param iface {Interface} the interface to check for |
| * @return {Boolean} whether the class includes the mixin directly. |
| */ |
| hasOwnInterface : function(clazz, iface) { |
| return clazz.$$implements && clazz.$$implements.indexOf(iface) !== -1; |
| }, |
| |
| |
| /** |
| * Returns the class or one of its superclasses which contains the |
| * declaration of the given interface. Returns null if the interface is not |
| * specified anywhere. |
| * |
| * @param clazz {Class} class to look for the interface |
| * @param iface {Interface} interface to look for |
| * @return {Class | null} the class which directly implements the given interface |
| */ |
| getByInterface : function(clazz, iface) |
| { |
| var list, i, l; |
| |
| while (clazz) |
| { |
| if (clazz.$$implements) |
| { |
| list = clazz.$$flatImplements; |
| |
| for (i=0, l=list.length; i<l; i++) |
| { |
| if (list[i] === iface) { |
| return clazz; |
| } |
| } |
| } |
| |
| clazz = clazz.superclass; |
| } |
| |
| return null; |
| }, |
| |
| |
| /** |
| * Returns a list of all mixins available in a class. |
| * |
| * @param clazz {Class} class which should be inspected |
| * @return {Mixin[]} array of mixins this class uses |
| */ |
| getInterfaces : function(clazz) |
| { |
| var list = []; |
| |
| while (clazz) |
| { |
| if (clazz.$$implements) { |
| list.push.apply(list, clazz.$$flatImplements); |
| } |
| |
| clazz = clazz.superclass; |
| } |
| |
| return list; |
| }, |
| |
| |
| /** |
| * Whether a given class or any of its superclasses includes a given interface. |
| * |
| * This function will return "true" if the interface was defined |
| * in the class declaration (@link qx.Class#define}) of the class |
| * or any of its super classes using the "implement" |
| * key. |
| * |
| * @type static |
| * @param clazz {Class|Object} class or instance to check |
| * @param iface {Interface} the interface to check for |
| * @return {Boolean} whether the class includes the interface. |
| */ |
| hasInterface : function(clazz, iface) { |
| return !!this.getByInterface(clazz, iface); |
| }, |
| |
| |
| /** |
| * Whether a given class conforms to an interface. |
| * |
| * Checks whether all methods defined in the interface are |
| * implemented in the class. The class does not need to implement |
| * the interface explicitly. |
| * |
| * @type static |
| * @param clazz {Class} class to check |
| * @param iface {Interface} the interface to check for |
| * @return {Boolean} whether the class conforms to the interface. |
| */ |
| implementsInterface : function(clazz, iface) |
| { |
| return false; |
| }, |
| |
| |
| /** |
| * Helper method to handle singletons |
| * |
| * @type static |
| * @internal |
| * @return {var} TODOC |
| */ |
| getInstance : function() |
| { |
| if (!this.$$instance) |
| { |
| this.$$allowconstruct = true; |
| this.$$instance = new this; |
| delete this.$$allowconstruct; |
| } |
| |
| return this.$$instance; |
| }, |
| |
| |
| |
| |
| |
| /* |
| --------------------------------------------------------------------------- |
| PRIVATE/INTERNAL BASICS |
| --------------------------------------------------------------------------- |
| */ |
| |
| /** |
| * This method will be attached to all classes to return |
| * a nice identifier for them. |
| * |
| * @internal |
| * @return {String} The class identifier |
| */ |
| genericToString : function() { |
| return "[Class " + this.classname + "]"; |
| }, |
| |
| |
| /** Stores all defined classes */ |
| __registry : qx.core.Bootstrap.__registry, |
| |
| |
| /** {Map} allowed keys in non-static class definition */ |
| __allowedKeys : qx.core.Variant.select("qx.debug", |
| { |
| "on": |
| { |
| "type" : "string", // String |
| "extend" : "function", // Function |
| "implement" : "object", // Interface[] |
| "include" : "object", // Mixin[] |
| "construct" : "function", // Function |
| "statics" : "object", // Map |
| "properties" : "object", // Map |
| "members" : "object", // Map |
| "settings" : "object", // Map |
| "variants" : "object", // Map |
| "events" : "object", // Map |
| "defer" : "function", // Function |
| "destruct" : "function" // Function |
| }, |
| |
| "default" : null |
| }), |
| |
| |
| /** {Map} allowed keys in static class definition */ |
| __staticAllowedKeys : qx.core.Variant.select("qx.debug", |
| { |
| "on": |
| { |
| "type" : "string", // String |
| "statics" : "object", // Map |
| "settings" : "object", // Map |
| "variants" : "object", // Map |
| "defer" : "function" // Function |
| }, |
| |
| "default" : null |
| }), |
| |
| |
| /** |
| * Validates an incoming configuration and checks for proper keys and values |
| * |
| * @type static |
| * @param name {String} The name of the class |
| * @param config {Map} Configuration map |
| * @return {void} |
| * @throws TODOC |
| */ |
| __validateConfig : qx.core.Variant.select("qx.debug", |
| { |
| "on": function(name, config) |
| { |
| // Validate type |
| if (config.type && !(config.type === "static" || config.type === "abstract" || config.type === "singleton")) { |
| throw new Error('Invalid type "' + config.type + '" definition for class "' + name + '"!'); |
| } |
| |
| // Validate keys |
| var allowed = config.type === "static" ? this.__staticAllowedKeys : this.__allowedKeys; |
| for (var key in config) |
| { |
| if (!allowed[key]) { |
| throw new Error('The configuration key "' + key + '" in class "' + name + '" is not allowed!'); |
| } |
| |
| if (config[key] == null) { |
| throw new Error('Invalid key "' + key + '" in class "' + name + '"! The value is undefined/null!'); |
| } |
| |
| if (typeof config[key] !== allowed[key]) { |
| throw new Error('Invalid type of key "' + key + '" in class "' + name + '"! The type of the key must be "' + allowed[key] + '"!'); |
| } |
| } |
| |
| // Validate maps |
| var maps = [ "statics", "properties", "members", "settings", "variants", "events" ]; |
| for (var i=0, l=maps.length; i<l; i++) |
| { |
| var key = maps[i]; |
| |
| if (config[key] !== undefined && (config[key] instanceof Array || config[key] instanceof RegExp || config[key] instanceof Date || config[key].classname !== undefined)) { |
| throw new Error('Invalid key "' + key + '" in class "' + name + '"! The value needs to be a map!'); |
| } |
| } |
| |
| // Validate include definition |
| if (config.include) |
| { |
| if (config.include instanceof Array) |
| { |
| for (var i=0, a=config.include, l=a.length; i<l; i++) |
| { |
| if (a[i] == null || a[i].$$type !== "Mixin") { |
| throw new Error('The include definition in class "' + name + '" contains an invalid mixin at position ' + i + ': ' + a[i]); |
| } |
| } |
| } |
| else |
| { |
| throw new Error('Invalid include definition in class "' + name + '"! Only mixins and arrays of mixins are allowed!'); |
| } |
| } |
| |
| // Validate implement definition |
| if (config.implement) |
| { |
| if (config.implement instanceof Array) |
| { |
| for (var i=0, a=config.implement, l=a.length; i<l; i++) |
| { |
| if (a[i] == null || a[i].$$type !== "Interface") { |
| throw new Error('The implement definition in class "' + name + '" contains an invalid interface at position ' + i + ': ' + a[i]); |
| } |
| } |
| } |
| else |
| { |
| throw new Error('Invalid implement definition in class "' + name + '"! Only interfaces and arrays of interfaces are allowed!'); |
| } |
| } |
| |
| // Check mixin compatibility |
| if (config.include) |
| { |
| try { |
| qx.Mixin.checkCompatibility(config.include); |
| } catch(ex) { |
| throw new Error('Error in include definition of class "' + name + '"! ' + ex.message); |
| } |
| } |
| |
| // Validate variants |
| if (config.variants) |
| { |
| for (var key in config.variants) |
| { |
| if (key.substr(0, key.indexOf(".")) != name.substr(0, name.indexOf("."))) { |
| throw new Error('Forbidden variant "' + key + '" found in "' + name + '". It is forbidden to define a variant for an external namespace!'); |
| } |
| } |
| } |
| }, |
| |
| "default" : function() {} |
| }), |
| |
| |
| |
| /* |
| --------------------------------------------------------------------------- |
| PRIVATE ADD HELPERS |
| --------------------------------------------------------------------------- |
| */ |
| |
| /** |
| * Attach events to the class |
| * |
| * @param clazz {Class} class to add the events to |
| * @param events {Map} map of event names the class fires. |
| * @param patch {Boolean ? false} Enable redefinition of event type? |
| */ |
| __addEvents : function(clazz, events, patch) |
| { |
| |
| if (clazz.$$events) |
| { |
| for (var key in events) { |
| clazz.$$events[key] = events[key]; |
| } |
| } |
| else |
| { |
| clazz.$$events = events; |
| } |
| }, |
| |
| |
| /** |
| * Attach properties to classes |
| * |
| * @type static |
| * @param clazz {Class} class to add the properties to |
| * @param properties {Map} map of properties |
| * @param patch {Boolean ? false} Overwrite property with the limitations of a property |
| which means you are able to refine but not to replace (esp. for new properties) |
| */ |
| __addProperties : function(clazz, properties, patch) |
| { |
| var config; |
| |
| if (patch === undefined) { |
| patch = false; |
| } |
| |
| var attach = !!clazz.$$propertiesAttached; |
| |
| for (var name in properties) |
| { |
| config = properties[name]; |
| |
| // Store name into configuration |
| config.name = name; |
| |
| // Add config to local registry |
| if (!config.refine) |
| { |
| if (clazz.$$properties === undefined) { |
| clazz.$$properties = {}; |
| } |
| |
| clazz.$$properties[name] = config; |
| } |
| |
| // Store init value to prototype. This makes it possible to |
| // overwrite this value in derived classes. |
| if (config.init !== undefined) { |
| clazz.prototype["__init$" + name] = config.init; |
| } |
| |
| // register event name |
| if (config.event !== undefined) { |
| var event = {} |
| event[config.event] = "qx.event.type.ChangeEvent"; |
| this.__addEvents(clazz, event, patch); |
| } |
| |
| // Remember inheritable properties |
| if (config.inheritable) { |
| qx.core.Property.$$inheritable[name] = true; |
| } |
| |
| // If instances of this class were already created, we |
| // need to attach the new style properties functions, directly. |
| if (attach) { |
| qx.core.Property.attachMethods(clazz, name, config); |
| } |
| |
| // Create old style properties |
| if (config._fast) { |
| qx.core.LegacyProperty.addFastProperty(config, clazz.prototype); |
| } else if (config._cached) { |
| qx.core.LegacyProperty.addCachedProperty(config, clazz.prototype); |
| } else if (config._legacy) { |
| qx.core.LegacyProperty.addProperty(config, clazz.prototype); |
| } |
| } |
| }, |
| |
| /** |
| * |
| * @param clazz {Class} class to add property to |
| * @param name {String} name of the property |
| * @param config {Map} configuration map |
| * @param patch {Boolean ? false} enable refine/patch? |
| */ |
| __validateProperty : qx.core.Variant.select("qx.debug", |
| { |
| "on": function(clazz, name, config, patch) |
| { |
| var has = this.hasProperty(clazz, name); |
| var compat = config._legacy || config._fast || config._cached; |
| |
| if (has) |
| { |
| var existingProperty = this.getPropertyDefinition(clazz, name); |
| var existingCompat = existingProperty._legacy || existingProperty._fast || existingProperty._cached; |
| |
| if (compat != existingCompat) { |
| throw new Error("Could not redefine existing property '" + name + "' of class '" + clazz.classname + "'."); |
| } |
| |
| if (config.refine && existingProperty.init === undefined) { |
| throw new Error("Could not refine a init value if there was previously no init value defined. Property '" + name + "' of class '" + clazz.classname + "'."); |
| } |
| } |
| |
| if (!has && config.refine) { |
| throw new Error("Could not refine non-existent property: " + name + "!"); |
| } |
| |
| if (has && !patch) { |
| throw new Error("Class " + clazz.classname + " already has a property: " + name + "!"); |
| } |
| |
| if (has && patch && !compat) |
| { |
| if (!config.refine) { |
| throw new Error('Could not refine property "' + name + '" without a "refine" flag in the property definition! This class: ' + clazz.classname + ', original class: ' + this.getByProperty(clazz, name).classname + '.'); |
| } |
| |
| for (var key in config) |
| { |
| if (key !== "init" && key !== "refine") { |
| throw new Error("Class " + clazz.classname + " could not refine property: " + name + "! Key: " + key + " could not be refined!"); |
| } |
| } |
| } |
| |
| if (compat) { |
| return; |
| } |
| |
| // Check 0.7 keys |
| var allowed = config.group ? qx.core.Property.$$allowedGroupKeys : qx.core.Property.$$allowedKeys; |
| for (var key in config) |
| { |
| if (allowed[key] === undefined) { |
| throw new Error('The configuration key "' + key + '" of property "' + name + '" in class "' + clazz.classname + '" is not allowed!'); |
| } |
| |
| if (config[key] === undefined) { |
| throw new Error('Invalid key "' + key + '" of property "' + name + '" in class "' + clazz.classname + '"! The value is undefined: ' + config[key]); |
| } |
| |
| if (allowed[key] !== null && typeof config[key] !== allowed[key]) { |
| throw new Error('Invalid type of key "' + key + '" of property "' + name + '" in class "' + clazz.classname + '"! The type of the key must be "' + allowed[key] + '"!'); |
| } |
| } |
| |
| if (config.transform != null) |
| { |
| if (!(typeof config.transform == "string")) { |
| throw new Error('Invalid transform definition of property "' + name + '" in class "' + clazz.classname + '"! Needs to be a String.'); |
| } |
| } |
| |
| if (config.check != null) |
| { |
| if (!(typeof config.check == "string" ||config.check instanceof Array || config.check instanceof Function)) { |
| throw new Error('Invalid check definition of property "' + name + '" in class "' + clazz.classname + '"! Needs to be a String, Array or Function.'); |
| } |
| } |
| |
| if (config.event != null && !this.isSubClassOf(clazz, qx.core.Target)) |
| { |
| throw new Error("Invalid property '"+name+"' in class '"+clazz.classname+"': Properties defining an event can only be defined in sub classes of 'qx.core.Target'!"); |
| } |
| }, |
| |
| "default" : null |
| }), |
| |
| |
| /** |
| * Attach members to a class |
| * |
| * @param clazz {Class} clazz to add members to |
| * @param members {Map} The map of members to attach |
| * @param patch {Boolean ? false} Enable patching of |
| * @param base (Boolean ? true) Attach base flag to mark function as members |
| * of this class |
| * @param wrap {Boolean ? false} Whether the member method should be wrapped. |
| * this is needed to allow base calls in patched mixin members. |
| * @return {void} |
| */ |
| __addMembers : function(clazz, members, patch, base, wrap) |
| { |
| var proto = clazz.prototype; |
| var key, member; |
| |
| for (var i=0, a=qx.lang.Object.getKeys(members), l=a.length; i<l; i++) |
| { |
| key = a[i]; |
| member = members[key]; |
| |
| |
| // Added helper stuff to functions |
| // Hint: Could not use typeof function because RegExp objects are functions, too |
| if (base !== false && member instanceof Function) |
| { |
| if (wrap == true) |
| { |
| // wrap "patched" mixin member |
| member = this.__mixinMemberWrapper(member, proto[key]); |
| } |
| else |
| { |
| // Configure extend (named base here) |
| // Hint: proto[key] is not yet overwritten here |
| if (proto[key]) { |
| member.base = proto[key]; |
| } |
| member.self = clazz; |
| } |
| |
| } |
| |
| // Attach member |
| proto[key] = member; |
| } |
| }, |
| |
| |
| /** |
| * Wraps a member function of a mixin, which is included using "patch". This |
| * allows "base" calls in the mixin member function. |
| * |
| * @param member {Function} The mixin method to wrap |
| * @param base {Function} The overwritten method |
| * @return {Function} the wrapped mixin member |
| */ |
| __mixinMemberWrapper : function(member, base) |
| { |
| if (base) |
| { |
| return function() |
| { |
| var oldBase = member.base; |
| member.base = base; |
| var retval = member.apply(this, arguments); |
| member.base = oldBase; |
| return retval; |
| } |
| } |
| else |
| { |
| return member; |
| } |
| }, |
| |
| |
| /** |
| * Include all features of the mixin into the given class (recursive). |
| * |
| * @param clazz {Class} A class previously defined where the mixin should be attached. |
| * @param mixin {Mixin} Include all features of this mixin |
| * @param patch {Boolean} Overwrite existing fields, functions and properties |
| */ |
| __addMixin : function(clazz, mixin, patch) |
| { |
| |
| // Attach content |
| var list = qx.Mixin.flatten([mixin]); |
| var entry; |
| |
| for (var i=0, l=list.length; i<l; i++) |
| { |
| entry = list[i]; |
| |
| // Attach events |
| if (entry.$$events) { |
| this.__addEvents(clazz, entry.$$events, patch); |
| } |
| |
| // Attach properties (Properties are already readonly themselve, no patch handling needed) |
| if (entry.$$properties) { |
| this.__addProperties(clazz, entry.$$properties, patch); |
| } |
| |
| // Attach members (Respect patch setting, but dont apply base variables) |
| if (entry.$$members) { |
| this.__addMembers(clazz, entry.$$members, patch, patch, patch); |
| } |
| } |
| |
| // Store mixin reference |
| if (clazz.$$includes) |
| { |
| clazz.$$includes.push(mixin); |
| clazz.$$flatIncludes.push.apply(clazz.$$flatIncludes, list); |
| } |
| else |
| { |
| clazz.$$includes = [mixin]; |
| clazz.$$flatIncludes = list; |
| } |
| }, |
| |
| |
| |
| |
| |
| /* |
| --------------------------------------------------------------------------- |
| PRIVATE FUNCTION HELPERS |
| --------------------------------------------------------------------------- |
| */ |
| |
| /** |
| * Returns the default constructor. |
| * This constructor just calles the constructor of the base class. |
| * |
| * @type static |
| * @return {Function} The default constructor. |
| */ |
| __createDefaultConstructor : function() |
| { |
| function defaultConstructor() { |
| arguments.callee.base.apply(this, arguments); |
| }; |
| |
| return defaultConstructor; |
| }, |
| |
| |
| /** |
| * Returns an empty function. This is needed to get an empty function with an empty closure. |
| * |
| * @type static |
| * @return {Function} empty function |
| */ |
| __createEmptyFunction : function() { |
| return function() {}; |
| }, |
| |
| __initializeClass : function( clazz ) { |
| if( clazz.$$initializer ) { |
| var inits = []; |
| var target = clazz; |
| while( target.$$initializer ) { |
| inits.push( target ); |
| target = target.superclass; |
| } |
| while( inits.length > 0 ) { |
| target = inits.pop(); |
| target.$$initializer(); |
| delete target.$$initializer; |
| } |
| } |
| }, |
| |
| |
| /** |
| * Generate a wrapper of the original class constructor in order to enable |
| * some of the advanced OO features (e.g. abstract class, singleton, mixins) |
| * |
| * @param construct {Function} the original constructor |
| * @param name {String} name of the class |
| * @param type {String} the user specified class type |
| */ |
| __wrapConstructor : function(construct, name, type) |
| { |
| var init = this.__initializeClass; |
| var wrapper = function() { |
| |
| // We can access the class/statics using arguments.callee |
| var clazz=arguments.callee.constructor; |
| init( clazz ); |
| |
| // Attach local properties |
| if(!clazz.$$propertiesAttached)qx.core.Property.attach(clazz); |
| |
| // Execute default constructor |
| var retval=clazz.$$original.apply(this,arguments); |
| |
| // Initialize local mixins |
| if(clazz.$$includes){var mixins=clazz.$$flatIncludes; |
| for(var i=0,l=mixins.length;i<l;i++){ |
| if(mixins[i].$$constructor){mixins[i].$$constructor.apply(this,arguments);}}} |
| |
| // Mark instance as initialized |
| if(this.classname===', name, '.classname)this.$$initialized=true; |
| |
| // Return optional return value |
| return retval; |
| |
| } |
| |
| // Add singleton getInstance() |
| if (type === "singleton") { |
| wrapper.getInstance = this.getInstance; |
| } |
| |
| // Store original constructor |
| wrapper.$$original = construct; |
| |
| // Store wrapper into constructor (needed for base calls etc.) |
| construct.wrapper = wrapper; |
| |
| // Return generated wrapper |
| return wrapper; |
| } |
| } |
| }); |