egroupware/etemplate/js/et2_inheritance.js

248 lines
6.2 KiB
JavaScript
Raw Normal View History

/**
* eGroupWare eTemplate2 - 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$
*/
"use strict";
/**
* 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(){
var initializing = false
// 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
this.Interface = function(fncts) {
for (var key in fncts)
{
this[key] = fncts[key];
}
};
/**
* The addInterfaceStuff function adds all interface functions the class has
* to implement to the class prototype.
*/
function addInterfaceStuff(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 instanceof Interface!");
}
}
for (var i = 0; i < ifaces.length; i++)
{
prototype["_ifacefuncs"].push(ifaces[i]);
}
// The implements function can be used to check whether the object
// implements the given interface.
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.
prototype["instanceOf"] = function(_obj) {
if (_obj instanceof Interface)
{
return this.implements(_obj);
}
else
{
return this instanceof _obj;
}
}
};
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];
}
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
addInterfaceStuff(prototype, interfaces);
// The dummy class constructor
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.");
}
}
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;
// And make this class extendable
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.
Class.extend = classExtend;
}).call(window);