diff --git a/etemplate/js/et2_common.js b/etemplate/js/et2_common.js new file mode 100644 index 0000000000..c3cd9d1616 --- /dev/null +++ b/etemplate/js/et2_common.js @@ -0,0 +1,33 @@ +/** + * eGroupWare eTemplate2 - JS Widget base class + * + * @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$ + */ + +function et2_debug(_level, _msg) +{ + if (typeof console != "undefined") + { + if (_level == "log" && typeof console.log == "function") + { + console.log(_msg); + } + + if (_level == "warn" && typeof console.warn == "function") + { + console.warn(_msg); + } + + if (_level == "error" && typeof console.error == "function") + { + console.error(_msg); + } + } +} + diff --git a/etemplate/js/et2_inheritance.js b/etemplate/js/et2_inheritance.js new file mode 100644 index 0000000000..0782a31f09 --- /dev/null +++ b/etemplate/js/et2_inheritance.js @@ -0,0 +1,212 @@ +/** + * 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$ + */ + +/** + * 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. + * + * A single interface is also a simple object defining (empty) functions. Example: + * + * IBreathingObject = { + * breath: function() {} + * } + * + * 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": + * + * 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/ : /.*/; + + /** + * 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 in interfaces) + { + for (var key in interfaces[i]) + { + prototype["_ifacefuncs"].push(key); + } + } + + for (var i in ifaces) + { + 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 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 = function(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) + { + if (this.init) + { + this.init.apply(this, arguments); + } + + // Check whether the object implements all interface functions + for (var i in this._ifacefuncs) + { + var func = this._ifacefuncs[i]; + if (!(typeof this[func] == "function")) + { + throw("Trying to create abstract object, interface " + + "function '" + func + "' not implemented."); + } + } + } + } + + // 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 = arguments.callee; + + return Class; + }; +})(); + diff --git a/etemplate/js/et2_widget.js b/etemplate/js/et2_widget.js new file mode 100644 index 0000000000..12ca726dfb --- /dev/null +++ b/etemplate/js/et2_widget.js @@ -0,0 +1,406 @@ +/** + * eGroupWare eTemplate2 - JS Widget base class + * + * @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$ + */ + +/*egw:uses + et2_xml; + et2_common; + et2_inheritance; +*/ + +/** + * The registry contains all XML tag names and the corresponding widget + * constructor. + */ +var et2_registry = {}; + +/** + * Registers the widget class defined by the given constructor and associates it + * with the types in the _types array. + */ +function et2_register_widget(_constructor, _types) +{ + // Iterate over all given types and register those + for (var i in _types) + { + var type = _types[i].toLowerCase(); + + // Check whether a widget has already been registered for one of the + // types. + if (et2_registry[type]) + { + et2_debug("warn", "Widget class registered for " + type + + " will be overwritten."); + } + + et2_registry[type] = _constructor; + } +} + +/** + * The et2 widget base class. + */ +et2_widget = Class.extend({ + + /** + * The init function is the constructor of the widget. When deriving new + * classes from the widget base class, always call this constructor unless + * you know what you're doing. + * + * @param _parent is the parent object from the XML tree which contains this + * object. The default constructor always adds the new instance to the + * children list of the given parent object. _parent may be NULL. + * @param _type is the node name with which the widget has been created. This + * is usefull if a single widget class implements multiple XET-Node widgets. + */ + init: function(_parent, _type) { + + if (typeof _type == "undefined") + { + _type = "widget"; + } + + // Copy the parent parameter and add this widget to its parent children + // list. + this._parent = _parent; + if (_parent != null) + { + this._parent.addChild(this); + } + + this._children = []; + this.id = ""; + this.type = _type; + }, + + /** + * The destroy function destroys all children of the widget, removes itself + * from the parents children list. + * In all classes derrived from et2_widget ALWAYS override the destroy + * function and remove ALL references to other objects. Also remember to + * unbind ANY event this widget created and to remove all DOM-Nodes it + * created. + */ + destroy: function() { + + // Call the destructor of all children + for (var i = this._children.length; i >= 0; i--) + { + this._children[i].destroy(); + } + + // Remove this element from the parent + if (this._parent !== null) + { + this._parent.removeChild(this); + } + + // Delete all references to other objects + this._children = []; + this._parent = null; + }, + + /** + * Returns the parent widget of this widget + */ + getParent: function() { + return this._parent; + }, + + /** + * Returns the list of children of this widget. + */ + getChildren: function() { + return this._children; + }, + + /** + * Returns the base widget + */ + getRoot: function() { + if (this._parent != null) + { + return this._parent.getRoot(); + } + else + { + return this; + } + }, + + /** + * Inserts an child at the end of the list. + * + * @param _node is the node which should be added. It has to be an instance + * of et2_widget + */ + addChild: function(_node) { + this.insertChild(_node, this._children.length); + }, + + /** + * Inserts a child at the given index. + * + * @param _node is the node which should be added. It has to be an instance + * of et2_widget + * @param _idx is the position at which the element should be added. + */ + insertChild: function(_node, _idx) { + if (_node instanceof et2_widget) + { + _node.parent = this; + this._children.splice(_idx, 0, _node); + } + else + { + throw("_node is not an instance of et2_widget!"); + } + }, + + /** + * Removes the child but does not destroy it. + */ + removeChild: function(_node) { + // Retrieve the child from the child list + var idx = this._children.indexOf(_node); + + if (idx >= 0) + { + // This element is no longer parent of the child + _node._parent = null; + + this._children.splice(idx, 1); + } + }, + + /** + * Searches an element by id in the tree, descending into the child levels. + * + * @param _id is the id you're searching for + */ + getWidgetById: function(_id) { + if (this.id == _id) + { + return this; + } + + for (var i = 0; i < this._children.length; i++) + { + var elem = this._children[i].getWidgetById(_id); + + if (elem != null) + { + return elem; + } + } + + return null; + }, + + /** + * Loads the widget tree from an XML node + */ + loadFromXML: function(_node) { + // Try to load the attributes of the current node + if (_node.attributes) + { + this.loadAttributes(_node.attributes); + } + + // Load the child nodes. + for (var i = 0; i < _node.childNodes.length; i++) + { + var node = _node.childNodes[i]; + var widgetType = node.nodeName.toLowerCase(); + + // Check whether a widget with the given type is registered. + var constructor = typeof et2_registry[widgetType] == "undefined" ? + et2_placeholder : et2_registry[widgetType]; + + // Creates the new widget, passes this widget as an instance and + // passes the widgetType. Then it goes on loading the XML for it. + (new constructor(this, widgetType)).loadFromXML(node); + } + }, + + /** + * Loads the widget attributes from the passed DOM attributes array. + */ + loadAttributes: function(_attrs) { + for (var i = 0; i < _attrs.length; i++) + { + var attr = _attrs[i]; + + // Check whether a setter exists for the given attribute + if (typeof this["set_" + attr.name] == "function" && attr.name.charAt(0) != "_") + { + this["set_" + attr.name](attr.value); + } + } + }, + + /** + * Calls the setter of each property with its current value, calls the + * update function of all child nodes. + */ + update: function() { + // Go through every property of this object and check whether a + // corresponding setter function exists. If yes, it is called. + for (var key in this) + { + if (typeof this["set_" + key] == "function" && key.charAt(0) != "_") + { + this["set_" + key](this[key]); + } + } + + // Call the update function of all children. + for (var i in this._children) + { + this._children[i].update(); + } + }, + + get_id: function() { + return this.id; + }, + + set_id: function(_value) { + this.id = _value; + }, + + get_type: function() { + return this.type; + } +}); + +/** + * Interface for all widget classes, which are based on a DOM node. + */ +et2_IDOMNode = { + getDOMNode: function() {} +} + +/** + * Abstract widget class which can be inserted into the DOM. All widget classes + * deriving from this class have to care about implementing the "getDOMNode" + * function which has to return the DOM-Node. + */ +et2_DOMWidget = et2_widget.extend(et2_IDOMNode, { + + /** + * When the DOMWidget is initialized, it grabs the DOM-Node of the parent + * object (if available) and passes it to its own "createDOMNode" function + */ + init: function(_parent, _type) { + + // Call the inherited constructor + this._super.apply(this, arguments); + + this.parentNode = null; + + // Check whether the parent implements the et2_IDOMNode interface. If + // yes, grab the DOM node and create our own. + if (this._parent && this._parent.implements(et2_IDOMNode)) { + this.setParentDOMNode(this._parent.getDOMNode()); + } + }, + + destroy: function() { + + this.detatchFromDOM(); + + this._super(); + }, + + detatchFromDOM: function() { + if (this.parentNode) + { + var node = this.getDOMNode(); + + if (node) + { + this.parentNode.removeChild(node); + } + } + }, + + /** + * Set the parent DOM node of this element. If another parent node is already + * set, this widget removes itself from the DOM tree + */ + setParentDOMNode: function(_node) { + if (_node != this.parentNode) + { + // Detatch this element from the DOM tree + this.detatchFromDOM(); + + this.parentNode = _node; + + // Attach the DOM node of this widget (if existing) to the new parent + var node = this.getDOMNode(); + if (node) + { + this.parentNode.appendChild(node); + } + } + }, + + getParentDOMNode: function() { + return this.parentNode; + }, + + set_id: function(_value) { + this._super(_value); + + var node = this.getDOMNode(); + if (node) + { + node.setAttribute("id", _value); + } + } + +}); + +/** + * Container object for not-yet supported widgets + */ +et2_placeholder = et2_DOMWidget.extend({ + + init: function() { + this.placeDiv = document.createElement("span"); + + this._super.apply(this, arguments); + }, + + getDOMNode: function() { + return this.placeDiv; + } + +}); + +/** + * Common container object + */ +et2_container = et2_DOMWidget.extend({ + + init: function() { + this.div = document.createElement("div"); + + this._super.apply(this, arguments); + }, + + getDOMNode: function() { + return this.div; + } + +}); + diff --git a/etemplate/js/et2_xml.js b/etemplate/js/et2_xml.js new file mode 100644 index 0000000000..5382a20a63 --- /dev/null +++ b/etemplate/js/et2_xml.js @@ -0,0 +1,70 @@ +/** + * eGroupWare eTemplate2 - JS XML Code + * + * @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$ + */ + +/** + * Loads the given URL asynchronously from the server. When the file is loaded, + * the given callback function is called, where "this" is set to the given + * context. + */ +function et2_loadXMLFromURL(_url, _callback, _context) +{ + if (typeof _context == "undefined") + { + _context = null; + } + + // Use the XMLDOM object on IE + if (window.ActiveXObject) + { + var xmldoc = new ActiveXObject("Microsoft.XMLDOM"); + + // Set the callback function + xmldoc.onreadystatechange = function() { + if (xmldoc && xmldoc.readyState == 4) + { + _callback.call(_context, xmldoc); + } + } + + xmldoc.load(_url); + } + else if (window.XMLHttpRequest) + { + // Otherwise make an XMLHttpRequest. Tested with Firefox 3.6, Chrome, Opera + var xmlhttp = new XMLHttpRequest(); + + // Set the callback function + xmlhttp.onreadystatechange = function() { + if (xmlhttp.readyState == 4) + { + var xmldoc = xmlhttp.responseXML.documentElement; + _callback.call(_context, xmldoc); + } + } + + // Force the browser to interpret the result as XML. overrideMimeType is + // non-standard, so we check for its existance. + if (xmlhttp.overrideMimeType) + { + xmlhttp.overrideMimeType("application/xml"); + } + + // Retrieve the script asynchronously + xmlhttp.open("GET", _url, true); + xmlhttp.send(null); + } + else + { + throw("XML Request object could not be created!"); + } +} + diff --git a/etemplate/js/test/test_xml.html b/etemplate/js/test/test_xml.html new file mode 100644 index 0000000000..1e948ad275 --- /dev/null +++ b/etemplate/js/test/test_xml.html @@ -0,0 +1,24 @@ + +
+