/** * EGroupWare - JS code for implementing inheritance * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package etemplate * @subpackage api * @link http://www.egroupware.org * @author Andreas Stöckel * @copyright Stylite 2011 * @version $Id$ */ /** * Usage of the JS inheritance system * ---------------------------------- * * To create a class write * * MyClass = Class.extend([interfaces, ] functions); * * where "interfaces" is a single interface or an array of interfaces and * functions an object containing the functions the class implements. * * An interface has to be created in the following way: * * var IBreathingObject = new Interface({ * breath: function() {} * }); * * var Human = Class.extend(IBreathingObject, { * walk: function() { * console.log("Walking"); * }, * speak: function(_words) { * console.log(_words); * } * }); * * As "Human" does not implement the function "breath", "Human" is treated as * abstract. Trying to create an instance of "Human" will throw an exception. * However * * Human.prototype.implements(IBreathingObject); * * will return true. Lets create a specific class of "Human": * * var ChuckNorris = Human.extend({ * breath: function() { * console.log("Chuck Norris does not breath, he holds air hostage."); * }, * speak: function(_words) { * console.warn("Chuck Norris says:"); * this._super(_words); * } * }); */ // The following code is mostly taken from // http://ejohn.org/blog/simple-javascript-inheritance/ // some parts were slightly changed for better understanding. Added possiblity // to use interfaces. /** * Simple JavaScript Inheritance * By John Resig http://ejohn.org/ * MIT Licensed * * Inspired by base2 and Prototype */ (function() { "use strict"; var initializing = false; /** * Turn this to "true" to track creation and destruction of elements */ var getMem_freeMem_trace = false; var tracedObjects = {}; // Check whether "function decompilation" works - fnTest is normally used to // check whether a var fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; /** * Base "Class" for interfaces - needed to check whether an object is an * interface * * @param {object} fncts * @class {Interface} */ this.Interface = function(fncts) { for (var key in fncts) { this[key] = fncts[key]; } }; /** * The addInterfaceFunctions function adds all interface functions the class has * to implement to the class prototype. * * @param {Class} prototype * @param {array} interfaces */ function addInterfaceFunctions(prototype, interfaces) { // Remember all interface functions in the prototype var ifaces = ((typeof prototype["_ifacefuncs"] == "undefined") ? [] : prototype["_ifacefuncs"]); prototype["_ifacefuncs"] = []; for (var i = 0; i < interfaces.length; i++) { var iface = interfaces[i]; if (iface instanceof Interface) { for (var key in iface) { prototype["_ifacefuncs"].push(key); } } else { throw("Interfaces must be instance of Interface!"); } } for (var i = 0; i < ifaces.length; i++) { prototype["_ifacefuncs"].push(ifaces[i]); } }; function addAttributeFunctions(prototype, _super) { function _copyMerge(_new, _old) { var result = {}; // Copy the new object if (typeof _new != "undefined") { for (var key in _new) { result[key] = _new[key]; } } // Merge the old object for (var key in _old) { if (typeof result[key] == "undefined") { result[key] = _old[key]; } } return result; } var attributes = {}; // Copy the old attributes for (var key in prototype.attributes) { attributes[key] = _copyMerge({}, prototype.attributes[key]); } // Add the old attributes to the new ones. If the attributes already // exist, they are merged. for (var key in _super.attributes) { var _old = _super.attributes[key]; attributes[key] = _copyMerge(attributes[key], _old); } if(prototype._validate_attributes) { prototype._validate_attributes(attributes); } prototype.attributes = attributes; }; /** * Create a new Class that inherits from this class. The first parameter * is an array which defines a set of interfaces the object has to * implement. An interface is simply an object with named functions. * * @param {array} interfaces * @param {object} prop * @return {Class} */ function classExtend(interfaces, prop) { if (typeof prop == "undefined") { prop = interfaces; interfaces = []; } // If a single interface is given, encapsulate it in an array if (!(interfaces instanceof Array)) { interfaces = [interfaces]; } if (typeof prop.attributes == "undefined") { prop.attributes = {}; } var _super = this.prototype; // Instantiate a base class (but only create the instance, // don't run the init constructor) initializing = true; var prototype = new this(); initializing = false; // Copy the properties over onto the new prototype for (var name in prop) { // Check if we're overwriting an existing function and check whether // the function actually uses "_super" - the RegExp test function // silently converts the funciton prop[name] to a string. if (typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name])) { prototype[name] = (function(name, fn){ return function() { var tmp = this._super; // Add a new ._super() method that is the same method // but on the super-class this._super = _super[name]; // The method only need to be bound temporarily, so we // remove it when we're done executing var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]); } else { prototype[name] = prop[name]; } } // Add the interface functions and the "implements" function to the // prototype addInterfaceFunctions(prototype, interfaces); // Merge the attributes and create the functions corresponding to the // attributes addAttributeFunctions(prototype, _super); /** * The dummy class constructor * * @constructor {Class} */ function Class() { // All construction is actually done in the init method if (!initializing) { // Check whether the object implements all interface functions for (var i = 0; i < this._ifacefuncs.length; i++) { var func = this._ifacefuncs[i]; if (!(typeof this[func] == "function")) { throw("Trying to create abstract object, interface " + "function '" + func + "' not implemented."); } } // Do some tracing of the getMem_freeMem_trace is activated if (getMem_freeMem_trace) { this.__OBJ_UID = "obj_" + egw.uid(); var className = this.className(); tracedObjects[this.__OBJ_UID] = { "created": new Date().getTime(), "class": className }; egw.debug("log", "*" + this.__OBJ_UID + " (" + className + ")"); } if (this.init) { this.init.apply(this, arguments); } } } // Populate our constructed prototype object Class.prototype = prototype; // Enforce the constructor to be what we expect Class.prototype.constructor = Class; /** * Create a new Class that inherits from this class. The first parameter * is an array which defines a set of interfaces the object has to * implement. An interface is simply an object with named functions. * * @param {array} interfaces * @param {object} prop * @return {Class} */ Class.extend = classExtend; return Class; }; // The base Class implementation (does nothing) this.Class = function(){}; /** * Create a new Class that inherits from this class. The first parameter * is an array which defines a set of interfaces the object has to * implement. An interface is simply an object with named functions. * * @param {array} interfaces * @param {object} prop * @return {Class} */ Class.extend = classExtend; // The base class has no attributes Class.prototype.attributes = {}; Class.prototype._ifacefuncs = []; // Add the basic functions /** * Destructor function - it calls "destroy" if it has been defined and then * deletes all keys of this element, so that any access to this element will * eventually throw an exception, making it easier to hunt down memory leaks. */ Class.prototype.free = function() { if (this.destroy) { this.destroy(); } // Trace the freeing of the object if (getMem_freeMem_trace) { delete(tracedObjects[this.__OBJ_UID]); egw.debug("log", "-" + this.__OBJ_UID); } // Delete every object entry for (var key in this) { delete(this[key]); } // Don't raise an exception when attempting to free an element multiple // times. this.free = function() {}; }; // Some debug functions for memory leak hunting if (getMem_freeMem_trace) { /** * Prints a list of all objects UIDs which have not been freed yet. */ Class.prototype.showTrace = function() { console.log(tracedObjects); }, /** * VERY slow - for debugging only! */ Class.prototype.className = function() { for (var key in window) { if (key.substr(0, 3) == "et2" && this.constructor == window[key]) { return key; } } return "?"; }; } /** * The implements function can be used to check whether the object * implements the given interface. * * @param {Class} _iface interface to check */ Class.prototype.implements = function(_iface) { for (var key in _iface) { if (this._ifacefuncs.indexOf(key) < 0) { return false; } } return true; }; /** * The instanceOf function can be used to check for both - classes and * interfaces. Please don't change the case of this function as this * affects IE and Opera support. * * @param {Class} _obj object to check */ Class.prototype.instanceOf = function(_obj) { if (_obj instanceof Interface) { return this.implements(_obj); } else { return this instanceof _obj; } }; }).call(window);