diff --git a/etemplate/js/et2_baseWidget.js b/etemplate/js/et2_baseWidget.js
new file mode 100644
index 0000000000..21f0769864
--- /dev/null
+++ b/etemplate/js/et2_baseWidget.js
@@ -0,0 +1,118 @@
+/**
+ * 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: et2_widget.js 36021 2011-08-07 13:43:46Z igel457 $
+ */
+
+"use strict";
+
+/*egw:uses
+ jquery.jquery;
+ lib/tooltip.js;
+ et2_widget;
+*/
+
+/**
+ * Class which manages the DOM node itself. The simpleWidget class is derrived
+ * from et2_DOMWidget and implements the getDOMNode function. A setDOMNode
+ * function is provided, which attatches the given node to the DOM if possible.
+ */
+var et2_baseWidget = et2_DOMWidget.extend({
+
+ attributes: {
+ "statustext": {
+ "name": "Tooltip",
+ "type": "string",
+ "description": "Tooltip which is shown for this element"
+ }
+ },
+
+ init: function() {
+ this._super.apply(this, arguments);
+
+ this.node = null;
+ this.statustext = "";
+
+ this._tooltipElem = null;
+ },
+
+ detatchFromDOM: function() {
+ // Detach this node from the tooltip node
+ if (this._tooltipElem)
+ {
+ egw_global_tooltip.unbindFromElement(this._tooltipElem);
+ this._tooltipElem = null;
+ }
+
+ this._super.apply(this, arguments);
+ },
+
+ attachToDOM: function() {
+ this._super.apply(this,arguments);
+
+ // Update the statustext
+ this.set_statustext(this.statustext);
+ },
+
+ setDOMNode: function(_node) {
+ if (_node != this.node)
+ {
+ // Deatch the old node from the DOM
+ this.detatchFromDOM();
+
+ // Set the new DOM-Node
+ this.node = _node;
+
+ // Attatch the DOM-Node to the tree
+ return this.attachToDOM();
+ }
+
+ return false;
+ },
+
+ getDOMNode: function() {
+ return this.node;
+ },
+
+ getTooltipElement: function() {
+ return this.getDOMNode();
+ },
+
+ set_statustext: function(_value) {
+ // Don't execute the code below, if no tooltip will be attached/detached
+ if (_value == "" && !this._tooltipElem)
+ {
+ return;
+ }
+
+ this.statustext = _value;
+
+ //Get the domnode the tooltip should be attached to
+ var elem = $j(this.getTooltipElement());
+
+ if (elem)
+ {
+ //If a tooltip is already attached to the element, remove it first
+ if (this._tooltipElem)
+ {
+ egw_global_tooltip.unbindFromElement(this._tooltipElem);
+ this._tooltipElem = null;
+ }
+
+ if (_value && _value != '')
+ {
+ egw_global_tooltip.bindToElement(elem, _value);
+ this._tooltipElem = elem;
+ }
+ }
+ }
+
+});
+
+
diff --git a/etemplate/js/et2_box.js b/etemplate/js/et2_box.js
new file mode 100644
index 0000000000..289e490a2e
--- /dev/null
+++ b/etemplate/js/et2_box.js
@@ -0,0 +1,37 @@
+/**
+ * eGroupWare eTemplate2 - JS Box object
+ *
+ * @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";
+
+/*egw:uses
+ jquery.jquery;
+ et2_baseWidget;
+*/
+
+/**
+ * Class which implements the hbox and vbox tag
+ */
+var et2_box = et2_baseWidget.extend({
+
+ init: function(_parent, _type) {
+ this._super.apply(this, arguments);
+
+ this.div = $j(document.createElement("div"))
+ .addClass("et2_" + _type);
+
+ this.setDOMNode(this.div[0]);
+ }
+
+});
+
+et2_register_widget(et2_box, ["hbox", "vbox"]);
+
diff --git a/etemplate/js/et2_button.js b/etemplate/js/et2_button.js
index 27d534fac3..92310ae216 100644
--- a/etemplate/js/et2_button.js
+++ b/etemplate/js/et2_button.js
@@ -14,20 +14,31 @@
/*egw:uses
jquery.jquery;
- et2_widget;
+ et2_baseWidget;
*/
/**
* Class which implements the "button" XET-Tag
*/
-var et2_button = et2_DOMWidget.extend({
+var et2_button = et2_baseWidget.extend({
+
+ attributes: {
+ "label": {
+ "name": "caption",
+ "type": "string",
+ "description": "Label of the button"
+ }
+ },
init: function(_parent) {
+ this._super.apply(this, arguments);
+
+ this.label = "";
+
this.btn = $j(document.createElement("button"))
.addClass("et2_button");
- this._super.apply(this, arguments);
- this.label = "";
+ this.setDOMNode(this.btn[0]);
},
set_label: function(_value) {
@@ -37,10 +48,6 @@ var et2_button = et2_DOMWidget.extend({
this.btn.text(_value);
}
- },
-
- getDOMNode: function() {
- return this.btn[0];
}
});
diff --git a/etemplate/js/et2_common.js b/etemplate/js/et2_common.js
index 0152b47aae..51a19119c2 100644
--- a/etemplate/js/et2_common.js
+++ b/etemplate/js/et2_common.js
@@ -10,27 +10,6 @@
* @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);
- }
- }
-}
-
/**
* IE Fix for array.indexOf
*/
@@ -46,4 +25,302 @@ if (typeof Array.prototype.indexOf == "undefined")
};
}
+/**
+ * ET2_DEBUGLEVEL specifies which messages are printed to the console. Decrease
+ * the value of ET2_DEBUGLEVEL to get less messages.
+ */
+var ET2_DEBUGLEVEL = 0;
+
+function et2_debug(_level, _msg)
+{
+ if (typeof console != "undefined")
+ {
+ if (_level == "log" && ET2_DEBUGLEVEL >= 4 &&
+ typeof console.log == "function")
+ {
+ console.log(_msg);
+ }
+
+ if (_level == "info" && ET2_DEBUGLEVEL >= 3 &&
+ typeof console.info == "function")
+ {
+ console.info(_msg);
+ }
+
+ if (_level == "warn" && ET2_DEBUGLEVEL >= 2 &&
+ typeof console.warn == "function")
+ {
+ console.warn(_msg);
+ }
+
+ if (_level == "error" && ET2_DEBUGLEVEL >= 1 &&
+ typeof console.error == "function")
+ {
+ console.error(_msg);
+ }
+ }
+}
+
+/**
+ * Array with all types supported by the et2_checkType function.
+ */
+var et2_validTypes = ["boolean", "string", "float", "integer", "any"];
+
+/**
+ * Object whith default values for the above types. Do not specify array or
+ * objects inside the et2_typeDefaults object, as this instance will be shared
+ * between all users of it.
+ */
+var et2_typeDefaults = {
+ "boolean": false,
+ "string": "",
+ "float": 0.0,
+ "integer": 0,
+ "any": null
+};
+
+/**
+ * Checks whether the given value is of the given type. Strings are converted
+ * into the corresponding type. The (converted) value is returned. All supported
+ * types are listed in the et2_validTypes array.
+ */
+function et2_checkType(_val, _type)
+{
+ function _err() {
+ throw("'" + _val + "' is not of specified _type '" + _type + "'");
+ }
+
+ // If the type is "any" simply return the value again
+ if (_type == "any")
+ {
+ return _val;
+ }
+
+ // If the type is boolean, check whether the given value is exactly true or
+ // false. Otherwise check whether the value is the string "true" or "false".
+ if (_type == "boolean")
+ {
+ if (_val === true || _val === false)
+ {
+ return _val;
+ }
+
+ var lcv = _val.toLowerCase();
+ if (lcv === "true" || lcv === "false" || lcv === "")
+ {
+ return _val === "true";
+ }
+
+ _err();
+ }
+
+ // Check whether the given value is of the type "string"
+ if (_type == "string")
+ {
+ if (typeof _val == "string")
+ {
+ return _val;
+ }
+
+ _err();
+ }
+
+ // Check whether the value is already a number, otherwise try to convert it
+ // to one.
+ if (_type == "float")
+ {
+ if (typeof _val == "number")
+ {
+ return _val;
+ }
+
+ if (!isNaN(_val))
+ {
+ return parseFloat(_val);
+ }
+
+ _err();
+ }
+
+ // Check whether the value is an integer by comparing the result of
+ // parseInt(_val) to the value itself.
+ if (_type == "integer")
+ {
+ if (parseInt(_val) == _val)
+ {
+ return parseInt(_val);
+ }
+
+ _err();
+ }
+
+ // We should never come here
+ throw("Invalid type identifier supplied.");
+}
+
+/**
+ * Validates the given attribute with the given id. The validation checks for
+ * the existance of a human name, a description, a type and a default value.
+ * If the human name defaults to the given id, the description defaults to an
+ * empty string, the type defaults to any and the default to the corresponding
+ * type default.
+ */
+function et2_validateAttrib(_id, _attrib)
+{
+ // Default ignore to false.
+ if (typeof _attrib["ignore"] == "undefined")
+ {
+ _attrib["ignore"] = false
+ }
+
+ // Break if "ignore" is set to true.
+ if (_attrib.ignore)
+ {
+ return;
+ }
+
+ if (typeof _attrib["name"] == "undefined")
+ {
+ _attrib["name"] = _id;
+ et2_debug("log", "Human name ('name'-Field) for attribute '" +
+ _id + "' has not been supplied, set to '" + _id + "'");
+ }
+
+ if (typeof _attrib["description"] == "undefined")
+ {
+ _attrib["description"] = "";
+ et2_debug("log", "Description for attribute '" +
+ _id + "' has not been supplied");
+ }
+
+ if (typeof _attrib["type"] == "undefined")
+ {
+ _attrib["type"] = "any";
+ }
+ else
+ {
+ if (et2_validTypes.indexOf(_attrib["type"]) < 0)
+ {
+ et2_debug("error", "Invalid type for attribute '" + _id +
+ "' supplied.");
+ }
+ }
+
+ // Set the defaults
+ if (typeof _attrib["default"] == "undefined")
+ {
+ _attrib["default"] = et2_typeDefaults[_attrib["type"]];
+ }
+}
+
+/**
+ * Equivalent to the PHP array_values function
+ */
+function et2_arrayValues(_arr)
+{
+ var result = [];
+ for (var key in _arr)
+ {
+ if (parseInt(key) == key)
+ {
+ result.push(_arr[key]);
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Equivalent to the PHP substr function, partly take from phpjs, licensed under
+ * the GPL.
+ */
+function et2_substr (str, start, len) {
+ var end = str.length;
+
+ if (start < 0)
+ {
+ start += end;
+ }
+ end = typeof len === 'undefined' ? end : (len < 0 ? len + end : len + start);
+
+ return start >= str.length || start < 0 || start > end ? "" : str.slice(start, end);
+}
+
+/**
+ * Split a $delimiter-separated options string, which can contain parts with
+ * delimiters enclosed in $enclosure. Ported from class.boetemplate.inc.php
+ *
+ * Examples:
+ * - et2_csvSplit('"1,2,3",2,3') === array('1,2,3','2','3')
+ * - et2_csvSplit('1,2,3',2) === array('1','2,3')
+ * - et2_csvSplit('"1,2,3",2,3',2) === array('1,2,3','2,3')
+ * - et2_csvSplit('"a""b,c",d') === array('a"b,c','d') // to escape enclosures double them!
+ *
+ * @param string _str
+ * @param int _num=null in how many parts to split maximal, parts over this
+ * number end up (unseparated) in the last part
+ * @param string _delimiter=','
+ * @param string _enclosure='"'
+ * @return array
+ */
+function et2_csvSplit(_str, _num, _delimiter, _enclosure)
+{
+ // Default the parameters
+ if (typeof _num == "undefined")
+ {
+ _num == null;
+ }
+
+ if (typeof _delimiter == "undefined")
+ {
+ _delimiter = ",";
+ }
+
+ if (typeof _enclosure == "undefined")
+ {
+ _enclosure = '"';
+ }
+
+ // If the _enclosure string does not occur in the string, simply use the
+ // split function
+ if (_str.indexOf(_enclosure) == -1)
+ {
+ return _num === null ? _str.split(_delimiter) :
+ _str.split(_delimiter, _num);
+ }
+
+ // Split the string at the delimiter and join it again, when a enclosure is
+ // found at the beginning/end of a part
+ var parts = _str.split(_delimiter);
+ for (var n = 0; typeof parts[n] != "undefined"; n++)
+ {
+ var part = parts[n];
+
+ if (part.charAt(0) === _enclosure)
+ {
+ var m = n;
+ while (typeof parts[m + 1] != "undefined" && parts[n].substr(-1) !== _enclosure)
+ {
+ parts[n] += _delimiter + parts[++m];
+ delete(parts[m]);
+ }
+ parts[n] = et2_substr(parts[n].replace(
+ new RegExp(_enclosure + _enclosure, 'g'), _enclosure), 1 , -1);
+ n = m;
+ }
+ }
+
+ // Rebuild the array index
+ parts = et2_arrayValues(parts);
+
+ // Limit the parts to the given number
+ if (_num !== null && _num > 0 && _num < parts.length && parts.length > 0)
+ {
+ parts[_num - 1] = parts.slice(_num - 1, parts.length).join(_delimiter);
+ parts = parts.slice(0, _num);
+ }
+
+ return parts;
+}
+
diff --git a/etemplate/js/et2_description.js b/etemplate/js/et2_description.js
index 1ff3dd7bb1..883175afab 100644
--- a/etemplate/js/et2_description.js
+++ b/etemplate/js/et2_description.js
@@ -14,24 +14,82 @@
/*egw:uses
jquery.jquery;
- et2_widget;
+ et2_baseWidget;
*/
/**
* Class which implements the "description" XET-Tag
*/
-var et2_description = et2_DOMWidget.extend({
+var et2_description = et2_baseWidget.extend({
+
+ attributes: {
+ "value": {
+ "name": "Caption",
+ "type": "string",
+ "description": "Displayed text"
+ },
+
+ /**
+ * Options converted from the "options"-attribute.
+ */
+ "font_style": {
+ "name": "Font Style",
+ "type": "string",
+ "description": "Style may be a compositum of \"b\" and \"i\" which " +
+ " renders the text bold and/or italic."
+ },
+ "href": {
+ "name": "Link Target",
+ "type": "string",
+ "description": "Link URL, empty if you don't wan't to display a link."
+ },
+ "activate_links": {
+ "name": "Replace URLs",
+ "type": "boolean",
+ "default": false,
+ "description": "If set, URLs in the text are automatically replaced " +
+ "by links"
+ },
+ "label_for": {
+ "name": "Label for widget",
+ "type": "string",
+ "description": "Marks the text as label for the given widget."
+ },
+ "extra_link_target": {
+ "name": "Link target",
+ "type": "string",
+ "default": "_self",
+ "description": "Link target descriptor"
+ },
+ "extra_link_popup": {
+ "name": "Popup",
+ "type": "string",
+ "description": "???"
+ },
+ "extra_link_title": {
+ "name": "Link Title",
+ "type": "string",
+ "description": "Link title which is displayed on mouse over."
+ }
+ },
+
+ legacyOptions: ["font_style", "href", "activate_links", "label_for",
+ "extra_link_target", "extra_link_popup", "extra_link_title"],
init: function(_parent) {
+ this._super.apply(this, arguments);
+
+ this.value = "";
+ this.font_style = "";
+
this.span = $j(document.createElement("span"))
.addClass("et2_label");
- this._super.apply(this, arguments);
- this.value = "";
+ this.setDOMNode(this.span[0]);
},
- set_value: function(_value) {
- if (_value != this.value)
+ set_value: function(_value, _force) {
+ if (_value != this.value || _force)
{
this.value = _value;
@@ -39,8 +97,14 @@ var et2_description = et2_DOMWidget.extend({
}
},
- getDOMNode: function() {
- return this.span[0];
+ set_font_style: function(_value) {
+ if (_value != this.font_style)
+ {
+ this.font_style = _value;
+
+ this.span.toggleClass("et2_bold", _value.indexOf("b") >= 0);
+ this.span.toggleClass("et2_italic", _value.indexOf("i") >= 0);
+ }
}
});
diff --git a/etemplate/js/et2_grid.js b/etemplate/js/et2_grid.js
index 1521d1df93..b129126fe4 100644
--- a/etemplate/js/et2_grid.js
+++ b/etemplate/js/et2_grid.js
@@ -98,12 +98,6 @@ var et2_grid = et2_DOMWidget.extend({
};
},
- _readAttrWithDefault: function(_node, _name, _default) {
- var val = _node.getAttribute(_name);
-
- return (val === null) ? _default : val;
- },
-
_getCell: function(_cells, _x, _y) {
if ((0 <= _y) && (_y < _cells.length))
{
@@ -132,10 +126,10 @@ var et2_grid = et2_DOMWidget.extend({
var colDataEntry = this._getColDataEntry();
if (nodeName == "column")
{
- colDataEntry["width"] = this._readAttrWithDefault(node, "width", "auto");
- colDataEntry["class"] = this._readAttrWithDefault(node, "class", "");
- colDataEntry["align"] = this._readAttrWithDefault(node, "align", "");
- colDataEntry["span"] = this._readAttrWithDefault(node, "span", "1");
+ colDataEntry["width"] = et2_readAttrWithDefault(node, "width", "auto");
+ colDataEntry["class"] = et2_readAttrWithDefault(node, "class", "");
+ colDataEntry["align"] = et2_readAttrWithDefault(node, "align", "");
+ colDataEntry["span"] = et2_readAttrWithDefault(node, "span", "1");
}
else
{
@@ -149,10 +143,10 @@ var et2_grid = et2_DOMWidget.extend({
var rowDataEntry = this._getRowDataEntry();
if (nodeName == "row")
{
- rowDataEntry["height"] = this._readAttrWithDefault(node, "height", "auto");
- rowDataEntry["class"] = this._readAttrWithDefault(node, "class", "");
- rowDataEntry["valign"] = this._readAttrWithDefault(node, "valign", "");
- rowDataEntry["span"] = this._readAttrWithDefault(node, "span", "1");
+ rowDataEntry["height"] = et2_readAttrWithDefault(node, "height", "auto");
+ rowDataEntry["class"] = et2_readAttrWithDefault(node, "class", "");
+ rowDataEntry["valign"] = et2_readAttrWithDefault(node, "valign", "");
+ rowDataEntry["span"] = et2_readAttrWithDefault(node, "span", "1");
}
else
{
diff --git a/etemplate/js/et2_inheritance.js b/etemplate/js/et2_inheritance.js
index 377c7c0fbb..9233805d4a 100644
--- a/etemplate/js/et2_inheritance.js
+++ b/etemplate/js/et2_inheritance.js
@@ -12,6 +12,10 @@
"use strict";
+/*egw:uses
+ et2_common;
+*/
+
/**
* Usage of the JS inheritance system
* ----------------------------------
@@ -84,10 +88,10 @@
};
/**
- * The addInterfaceStuff function adds all interface functions the class has
+ * The addInterfaceFunctions function adds all interface functions the class has
* to implement to the class prototype.
*/
- function addInterfaceStuff(prototype, interfaces)
+ function addInterfaceFunctions(prototype, interfaces)
{
// Remember all interface functions in the prototype
var ifaces = ((typeof prototype["_ifacefuncs"] == "undefined") ? [] :
@@ -107,7 +111,7 @@
}
else
{
- throw("Interfaces must be instanceof Interface!");
+ throw("Interfaces must be instance of Interface!");
}
}
@@ -144,6 +148,118 @@
}
};
+ function addAttributeFunctions(prototype, _super)
+ {
+ var attributes = prototype.attributes;
+
+ // Add the old attributes to the new ones. If the attributes already
+ // exist, they are merged.
+ for (var key in _super.attributes)
+ {
+ var attrib = _super.attributes[key];
+
+ if (typeof attributes[key] == "undefined")
+ {
+ // In the case that the old attribute has no equivalent in the
+ // new class, simply create a reference to the old one.
+ attributes[key] = attrib;
+ }
+ else
+ {
+ // Otherwise merge the two attribute descriptors.
+ for (var key2 in attrib)
+ {
+ if (typeof attributes[key][key2] == "undefined")
+ {
+ attributes[key][key2] = attrib[key2];
+ }
+ }
+ }
+ }
+
+ // Validate the attributes
+ for (var key in attributes)
+ {
+ et2_validateAttrib(key, attributes[key]);
+ }
+
+ /**
+ * The initAttributes function sets the attributes to their default
+ * values. The attributes are not overwritten, which means, that the
+ * default is only set, if either a setter exists or this[propName] does
+ * not exist yet.
+ */
+ prototype.initAttributes = function() {
+ for (var key in this.attributes)
+ {
+ if (!this.attributes[key].ignore)
+ {
+ this.setAttribute(key, this.attributes[key]["default"],
+ false);
+ }
+ }
+
+ this._attrsInitialized = true;
+ }
+
+ /**
+ * The setAttribute function sets the attribute with the given name to
+ * the given value. _override defines, whether this[_name] will be set,
+ * if this key already exists. _override defaults to true. A warning
+ * is issued if the attribute does not exist.
+ */
+ prototype.setAttribute = function(_name, _value, _override) {
+ if (typeof this.attributes[_name] != "undefined")
+ {
+ if (!this.attributes[_name].ignore)
+ {
+ if (typeof _override == "undefined")
+ {
+ _override = true;
+ }
+
+ var val = et2_checkType(_value, this.attributes[_name].type);
+
+ if (typeof this["set_" + _name] == "function")
+ {
+ this["set_" + _name](val);
+ }
+ else if (_override || typeof this[_name] == "undefined")
+ {
+ this[_name] = val;
+ }
+ }
+ }
+ else
+ {
+ et2_debug("warn", "Attribute '" + _name + "' does not exist!");
+ }
+ }
+
+ /**
+ * Returns the value of the given attribute. If the property does not
+ * exist, an error message is issued.
+ */
+ prototype.getAttribute = function(_name) {
+ if (typeof this.attributes[_name] != "undefined" &&
+ !this.attributes[_name].ignore)
+ {
+ if (typeof this["get_" + _name] == "function")
+ {
+ return this["get_" + _name]();
+ }
+ else
+ {
+ return this[_name];
+ }
+ }
+ else
+ {
+ et2_error("error", "Attribute '" + _name + "' does not exist!");
+ }
+ }
+ };
+
function classExtend(interfaces, prop) {
if (typeof prop == "undefined")
@@ -158,6 +274,11 @@
interfaces = [interfaces];
}
+ if (typeof prop.attributes == "undefined")
+ {
+ prop.attributes = {};
+ }
+
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
@@ -199,7 +320,11 @@
// Add the interface functions and the "implements" function to the
// prototype
- addInterfaceStuff(prototype, interfaces);
+ addInterfaceFunctions(prototype, interfaces);
+
+ // Merge the attributes and create the functions corresponding to the
+ // attributes
+ addAttributeFunctions(prototype, _super);
// The dummy class constructor
function Class() {
@@ -221,6 +346,12 @@
{
this.init.apply(this, arguments);
}
+
+ // Initialize the attributes
+ if (typeof this._attrsInitialized == "undefined")
+ {
+ this.initAttributes();
+ }
}
}
@@ -243,5 +374,8 @@
// 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;
+
+ // The base class has no attributes
+ Class.attributes = {};
}).call(window);
diff --git a/etemplate/js/et2_inputWidget.js b/etemplate/js/et2_inputWidget.js
new file mode 100644
index 0000000000..7c37e69039
--- /dev/null
+++ b/etemplate/js/et2_inputWidget.js
@@ -0,0 +1,94 @@
+/**
+ * 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: et2_widget.js 36021 2011-08-07 13:43:46Z igel457 $
+ */
+
+"use strict";
+
+/*egw:uses
+ jquery.jquery;
+ et2_baseWidget;
+*/
+
+/**
+ * Interface for all widgets which support returning a value
+ */
+var et2_IInput = new Interface({
+ /**
+ * getValue has to return the value of the input widget
+ */
+ getValue: function() {},
+
+ /**
+ * Is dirty returns true if the value of the widget has changed since it
+ * was loaded.
+ */
+ isDirty: function() {},
+
+ /**
+ * Causes the dirty flag to be reseted.
+ */
+ resetDirty: function() {}
+});
+
+/**
+ * et2_inputWidget derrives from et2_simpleWidget and implements the IInput
+ * interface. When derriving from this class, call setDOMNode with an input
+ * DOMNode.
+ */
+var et2_inputWidget = et2_simpleWidget.extend(et2_IInput, {
+
+ attributes: {
+ "value": {
+ "name": "Value",
+ "description": "The value of the widget",
+ "type": "string",
+ "default": ""
+ }
+ },
+
+ init: function() {
+ this._super.apply(this, arguments);
+
+ this._oldValue = "";
+ },
+
+ set_value: function(_value) {
+ this._oldValue = _value;
+
+ if (this.node)
+ {
+ $j(this.node).val(_value);
+ }
+ },
+
+ get_value: function() {
+ return this.getValue();
+ },
+
+ getValue: function() {
+ if (this.node)
+ {
+ return $j(this.node).val();
+ }
+
+ return this._oldValue;
+ },
+
+ isDirty: function() {
+ return this._oldValue != this.getValue();
+ },
+
+ resetDirty: function() {
+ this._oldValue = this.getValue();
+ }
+
+});
+
diff --git a/etemplate/js/et2_tabs.js b/etemplate/js/et2_tabs.js
new file mode 100644
index 0000000000..29e3b572bd
--- /dev/null
+++ b/etemplate/js/et2_tabs.js
@@ -0,0 +1,178 @@
+/**
+ * eGroupWare eTemplate2 - JS Tabs object
+ *
+ * @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";
+
+/*egw:uses
+ jquery.jquery;
+ et2_widget;
+*/
+
+/**
+ * Class which implements the tabbox-tag
+ */
+var et2_tabbox = et2_DOMWidget.extend({
+
+ init: function(_parent, _type) {
+ // Create the outer tabbox container
+ this.container = $j(document.createElement("div"))
+ .addClass("et2_tabbox");
+
+ // Create the upper container for the tab flags
+ var cntr = $j(document.createElement("div"))
+ .addClass("et2_flags")
+ .appendTo(this.container);
+
+ this.flagContainer = $j(document.createElement("span"))
+ .addClass("et2_tabflagcntr")
+ .appendTo(cntr);
+
+ $j(document.createElement("span"))
+ .addClass("et2_tabspacer")
+ .appendTo(cntr);
+
+ // Create the lower tab container
+ this.tabContainer = $j(document.createElement("div"))
+ .addClass("et2_tabs")
+ .appendTo(this.container);
+
+ this._super.apply(this, arguments);
+
+ this.tabData = [];
+ },
+
+ destroy: function(_parent, _type) {
+
+ this._super.apply(this, arguments);
+
+ this.container = null;
+ this.flagContainer = null;
+ this.tabData = [];
+ },
+
+ _readTabs: function(tabData, tabs) {
+ et2_filteredNodeIterator(tabs, function(node, nodeName) {
+ if (nodeName == "tab")
+ {
+ tabData.push({
+ "label": et2_readAttrWithDefault(node, "label", "Tab"),
+ "widget": null,
+ "contentDiv": null,
+ "flagDiv": null
+ });
+ }
+ else
+ {
+ throw("Error while parsing: Invalid tag '" + nodeName +
+ "' in tabs tag");
+ }
+ }, this);
+ },
+
+ _readTabPanels: function(tabData, tabpanels) {
+ var i = 0;
+ et2_filteredNodeIterator(tabpanels, function(node, nodeName) {
+ if (i < tabData.length)
+ {
+ // Create the widget corresponding to the given node
+ tabData[i].widget = this.createElementFromNode(node,
+ nodeName);
+ }
+ else
+ {
+ throw("Error while reading tabpanels tag, too many widgets!");
+ }
+ i++;
+ }, this);
+ },
+
+ loadFromXML: function(_node) {
+ // Get the tabs and tabpanels tags
+ var tabsElems = et2_directChildrenByTagName(_node, "tabs");
+ var tabpanelsElems = et2_directChildrenByTagName(_node, "tabpanels");
+
+ if (tabsElems.length == 1 && tabpanelsElems.length == 1)
+ {
+ var tabs = tabsElems[0];
+ var tabpanels = tabpanelsElems[0];
+
+ var tabData = [];
+
+ // Parse the "tabs" tag
+ this._readTabs(tabData, tabs);
+
+ // Read and create the widgets defined in the "tabpanels"
+ this._readTabPanels(tabData, tabpanels);
+
+ // Create the tab DOM-Nodes
+ this.createTabs(tabData)
+ }
+ else
+ {
+ throw("Error while parsing tabbox, none or multiple tabs or tabpanels tags!");
+ }
+ },
+
+ createTabs: function(tabData) {
+ this.tabData = tabData;
+
+ this.tabContainer.empty();
+ this.flagContainer.empty();
+
+ for (var i = 0; i < this.tabData.length; i++)
+ {
+ // Add a spacer to the flag container
+ $j(document.createElement("span"))
+ .addClass("et2_flagspacer")
+ .text("-")
+ .appendTo(this.flagContainer);
+
+ var entry = this.tabData[i];
+
+ entry.flagDiv = $j(document.createElement("span"))
+ .addClass("et2_tabflag")
+ .text(entry.label)
+ .appendTo(this.flagContainer);
+
+ entry.contentDiv = $j(document.createElement("div"))
+ .addClass("et2_tabcntr")
+ .hide()
+ .appendTo(this.tabContainer);
+
+ // Let the widget appear on its corresponding page
+ entry.widget.onSetParent();
+ }
+ },
+
+ getDOMNode: function(_sender) {
+ if (_sender == this)
+ {
+ return this.container[0];
+ }
+ else
+ {
+ for (var i = 0; i < this.tabData.length; i++)
+ {
+ if (this.tabData[i].widget == _sender)
+ {
+ return this.tabData[i].contentDiv[0];
+ }
+ }
+
+ return null;
+ }
+ }
+
+});
+
+et2_register_widget(et2_tabbox, ["tabbox"]);
+
diff --git a/etemplate/js/et2_template.js b/etemplate/js/et2_template.js
index 0a9e5df4fa..a4ad2e270f 100644
--- a/etemplate/js/et2_template.js
+++ b/etemplate/js/et2_template.js
@@ -26,6 +26,23 @@
*/
var et2_template = et2_DOMWidget.extend({
+ attributes: {
+ "template": {
+ },
+ "group": {
+ },
+ "version": {
+ "name": "Version",
+ "type": "string",
+ "description": "Version of the template"
+ },
+ "lang": {
+ "name": "Language",
+ "type": "string",
+ "description": "Language the template is written in"
+ }
+ },
+
/**
* Initializes this template widget as a simple container.
*/
@@ -34,6 +51,7 @@ var et2_template = et2_DOMWidget.extend({
this.isProxied = false;
this.div = document.createElement("div");
+ this.id = "";
this._super.apply(this, arguments);
},
diff --git a/etemplate/js/et2_textbox.js b/etemplate/js/et2_textbox.js
index 9c5e2765bc..14a083e2d6 100644
--- a/etemplate/js/et2_textbox.js
+++ b/etemplate/js/et2_textbox.js
@@ -14,33 +14,56 @@
/*egw:uses
jquery.jquery;
- et2_widget;
+ et2_inputWidget;
*/
/**
* Class which implements the "textbox" XET-Tag
*/
-var et2_textbox = et2_DOMWidget.extend({
+var et2_textbox = et2_baseWidget.extend({
- init: function(_parent) {
- this.input = $j(document.createElement("input"))
- .addClass("et2_input");
-
- this._super.apply(this, arguments);
- this.label = "";
- },
-
- set_value: function(_value) {
- if (_value != this.value)
- {
- this.label = _value;
-
- this.input.attr("value", _value);
+ attributes: {
+ "multiline": {
+ "name": "multiline",
+ "type": "boolean",
+ "default": false,
+ "description": "If true, the textbox is a multiline edit field."
}
},
- getDOMNode: function() {
- return this.input[0];
+ init: function(_parent) {
+ this._super.apply(this, arguments);
+
+ this.input = null;
+
+ this.createInputWidget();
+ },
+
+ createInputWidget: function() {
+ if (this.multiline)
+ {
+ this.input = $j(document.createElement("textarea"));
+ }
+ else
+ {
+ this.input = $j(document.createElement("input"));
+ }
+
+ this.input.addClass("et2_textbox");
+
+ this.setDOMNode(this.input[0]);
+ },
+
+ set_multiline: function(_value) {
+ if (_value != this.multiline)
+ {
+ this.multiline = _value;
+
+ this.createInputWidget();
+
+ // Write all settings again
+ this.update();
+ }
}
});
diff --git a/etemplate/js/et2_widget.js b/etemplate/js/et2_widget.js
index 1c2771c1ce..84d2fc42c9 100644
--- a/etemplate/js/et2_widget.js
+++ b/etemplate/js/et2_widget.js
@@ -14,6 +14,7 @@
/*egw:uses
jquery.jquery;
+ lib/tooltip.js;
et2_xml;
et2_common;
et2_inheritance;
@@ -53,6 +54,26 @@ function et2_register_widget(_constructor, _types)
*/
var et2_widget = Class.extend({
+ attributes: {
+ "id": {
+ "name": "ID",
+ "type": "string",
+ "description": "Unique identifier of the widget"
+ },
+
+ /**
+ * Ignore the "span" property by default - it is read by the grid and
+ * other widgets.
+ */
+ "span": {
+ "ignore": true
+ }
+ },
+
+ // Set the legacyOptions array to the names of the properties the "options"
+ // attribute defines.
+ legacyOptions: [],
+
/**
* The init function is the constructor of the widget. When deriving new
* classes from the widget base class, always call this constructor unless
@@ -82,7 +103,6 @@ var et2_widget = Class.extend({
}
this._children = [];
- this.id = "";
this.type = _type;
// The supported widget classes array defines a whitelist for all widget
@@ -148,12 +168,12 @@ var et2_widget = Class.extend({
_obj._children[i].clone(this, _obj._children[i].type);
}
- // Copy all properties for which a setter function exists
- for (var key in _obj)
+ // Copy all properties
+ for (var key in _obj.attributes)
{
- if (key != "id" && typeof this["set_" + key] == "function")
+ if (!_obj.attributes[key].ignore && key != "id")
{
- this["set_" + key](_obj[key]);
+ this.setAttribute(key, _obj.getAttribute(key));
}
}
},
@@ -265,6 +285,31 @@ var et2_widget = Class.extend({
return null;
},
+ /**
+ * Function which allows iterating over the complete widget tree.
+ *
+ * @param _callback is the function which should be called for each widget
+ * @param _context is the context in which the function should be executed
+ * @param _type is an optional parameter which specifies a class/interface
+ * the elements have to be instanceOf.
+ */
+ iterateOver: function(_callback, _context, _type) {
+ if (typeof _type == "undefined")
+ {
+ _type = et2_widget;
+ }
+
+ if (this.instanceOf(_type))
+ {
+ _callback.call(_context, this);
+ }
+
+ for (var i = 0; i < this._children.length; i++)
+ {
+ this._children[i].iterateOver(_callback, _context, _type);
+ }
+ },
+
isOfSupportedWidgetClass: function(_obj)
{
for (var i = 0; i < this.supportedWidgetClasses.length; i++)
@@ -336,12 +381,19 @@ var et2_widget = Class.extend({
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) != "_")
+ if (_attrs[i].name == "options")
{
- this["set_" + attr.name](attr.value);
+ // Parse the legacy options
+ var splitted = et2_csvSplit(_attrs[i].value);
+
+ for (var i = 0; i < splitted.length && i < this.legacyOptions.length; i++)
+ {
+ this.setAttribute(this.legacyOptions[i], splitted[i]);
+ }
+ }
+ else
+ {
+ this.setAttribute(_attrs[i].name, _attrs[i].value);
}
}
},
@@ -357,13 +409,14 @@ var et2_widget = Class.extend({
* 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)
+ for (var key in this.attributes)
{
- if (typeof this["set_" + key] == "function" && key.charAt(0) != "_")
+ if (!this.attributes[key].ignore && key != "id")
{
- this["set_" + key](this[key]);
+ this.setAttribute(key, this.getAttribute(key));
}
}
@@ -372,19 +425,8 @@ var et2_widget = Class.extend({
{
this._children[i].update();
}
- },
-
- get_id: function() {
- return this.id;
- },
-
- set_id: function(_value) {
- this.id = _value;
- },
-
- get_type: function() {
- return this.type;
}
+
});
/**
@@ -392,7 +434,13 @@ var et2_widget = Class.extend({
*/
var et2_IDOMNode = new Interface({
/**
- * Returns the DOM-Node of the current widget.
+ * Returns the DOM-Node of the current widget. The return value has to be
+ * a plain DOM node. If you want to return an jQuery object as you receive
+ * it with
+ *
+ * obj = $j(node);
+ *
+ * simply return obj[0];
*
* @param _sender The _sender parameter defines which widget is asking for
* the DOMNode.depending on that, the widget may return different nodes.
@@ -417,19 +465,32 @@ var et2_DOMWidget = et2_widget.extend(et2_IDOMNode, {
*/
init: function(_parent, _type) {
this.parentNode = null;
- this.visible = true;
+
+ this._attachSet = {
+ "node": null,
+ "parent": null
+ };
// Call the inherited constructor
this._super.apply(this, arguments);
},
+ /**
+ * Detatches the node from the DOM and clears all references to the parent
+ * node or the dom node of this widget.
+ */
destroy: function() {
this.detatchFromDOM();
+ this.parentNode = null;
+ this._attachSet = {};
this._super();
},
+ /**
+ * Automatically tries to attach this node to the parent widget.
+ */
onSetParent: function() {
// Check whether the parent implements the et2_IDOMNode interface. If
// yes, grab the DOM node and create our own.
@@ -438,16 +499,53 @@ var et2_DOMWidget = et2_widget.extend(et2_IDOMNode, {
}
},
+ /**
+ * Detaches the widget from the DOM tree, if it had been attached to the
+ * DOM-Tree using the attachToDOM method.
+ */
detatchFromDOM: function() {
- if (this.parentNode)
+
+ if (this._attachSet.node && this._attachSet.parent)
{
- var node = this.getDOMNode(this);
- if (node)
- {
- this.parentNode.removeChild(node);
- this.parentNode = null;
- }
+ // Remove the current node from the parent node
+ this._attachSet.parent.removeChild(this._attachSet.node);
+
+ // Reset the "attachSet"
+ this._attachSet = {
+ "node": null,
+ "parent": null
+ };
+
+ return true;
}
+
+ return false;
+ },
+
+ /**
+ * Attaches the widget to the DOM tree. Fails if the widget is already
+ * attached to the tree or no parent node or no node for this widget is
+ * defined.
+ */
+ attachToDOM: function() {
+ // Attach the DOM node of this widget (if existing) to the new parent
+ var node = this.getDOMNode(this);
+ if (node && this.parentNode &&
+ (node != this._attachSet.node ||
+ this.parentNode != this._attachSet.parent))
+ {
+ this.parentNode.appendChild(node);
+
+ // Store the currently attached nodes
+ this._attachSet = {
+ "node": node,
+ "parent": this.parentNode
+ };
+
+ return true;
+ }
+
+ return false;
},
/**
@@ -462,51 +560,77 @@ var et2_DOMWidget = et2_widget.extend(et2_IDOMNode, {
this.parentNode = _node;
- // Attach the DOM node of this widget (if existing) to the new parent
- var node = this.getDOMNode(this);
- if (node && this.parentNode)
- {
- this.parentNode.appendChild(node);
- }
+ // And attatch the element to the DOM tree
+ this.attachToDOM();
}
},
+ /**
+ * Returns the parent node.
+ */
getParentDOMNode: function() {
return this.parentNode;
},
+ /**
+ * Sets the id of the DOM-Node.
+ */
set_id: function(_value) {
- this._super(_value);
+
+ this.id = _value;
var node = this.getDOMNode(this);
if (node)
{
- node.setAttribute("id", _value);
+ if (_value != "")
+ {
+ node.setAttribute("id", _value);
+ }
+ else
+ {
+ node.removeAttribute("id");
+ }
}
}
});
/**
- * Container object for not-yet supported widgets
+ * Common container object
*/
-var et2_placeholder = et2_DOMWidget.extend({
+var et2_container = et2_simpleWidget.extend({
init: function() {
- // Create the placeholder div
- this.placeDiv = $j(document.createElement("span"))
- .addClass("et2_placeholder");
+ this._super.apply(this, arguments);
+
+ this.setDOMNode(document.createElement("div"));
+ }
+
+});
+
+
+/**
+ * Container object for not-yet supported widgets
+ */
+var et2_placeholder = et2_baseWidget.extend({
+
+ init: function() {
+ this._super.apply(this, arguments);
// The attrNodes object will hold the DOM nodes which represent the
// values of this object
this.attrNodes = {};
- this._super.apply(this, arguments);
+ // Create the placeholder div
+ this.placeDiv = $j(document.createElement("span"))
+ .addClass("et2_placeholder");
var headerNode = $j(document.createElement("span"))
.text(this.type)
- .addClass("et2_caption");
- $j(this.placeDiv).append(headerNode);
+ .addClass("et2_caption")
+ .appendTo(this.placeDiv);
+
+ this.setDOMNode(this.placeDiv[0]);
},
loadAttributes: function(_attrs) {
@@ -523,37 +647,6 @@ var et2_placeholder = et2_DOMWidget.extend({
this.attrNodes[attr.name].text(attr.name + "=" + attr.value);
}
- },
-
- getDOMNode: function() {
- return this.placeDiv[0];
}
-
});
-/**
- * Common container object
- */
-var et2_container = et2_DOMWidget.extend({
-
- init: function() {
- this.div = document.createElement("div");
-
- this._super.apply(this, arguments);
- },
-
- getDOMNode: function() {
- return this.div;
- }
-
-});
-
-/**
- * Interface for all widgets which support returning a value
- */
-
-var et2_IValue = new Interface({
- getValue: function() {}
-});
-
-
diff --git a/etemplate/js/et2_xml.js b/etemplate/js/et2_xml.js
index cc95e3bd13..656306c622 100644
--- a/etemplate/js/et2_xml.js
+++ b/etemplate/js/et2_xml.js
@@ -114,3 +114,11 @@ function et2_filteredNodeIterator(_node, _callback, _context)
}
}
+function et2_readAttrWithDefault(_node, _name, _default)
+{
+ var val = _node.getAttribute(_name);
+
+ return (val === null) ? _default : val;
+}
+
+
diff --git a/etemplate/js/lib/tooltip.js b/etemplate/js/lib/tooltip.js
new file mode 100644
index 0000000000..8c810f7948
--- /dev/null
+++ b/etemplate/js/lib/tooltip.js
@@ -0,0 +1,180 @@
+/**
+ * eGroupware2 UI - Tooltip JS
+ *
+ * This javascript file contains the JavaScript code for the etemplate2 tooltip
+ *
+ * @link http://www.egroupware.org
+ * @author Andreas Stoeckel (as@stylite.de)
+ * @version $Id: tooltip.js 31497 2010-07-22 09:50:11Z igel457 $
+ */
+
+/* Tooltip handling */
+function egw_tooltip()
+{
+ this.tooltip_div = null;
+ this.current_elem = null;
+ this.time_delta = 100;
+ this.show_delta = 0;
+ this.show_delay = 800;
+ this.x = 0;
+ this.y = 0;
+}
+
+egw_tooltip.prototype.bindToElement = function(_elem, _text)
+{
+ if (_text != '')
+ {
+ var self = this;
+ _elem.bind('mouseenter.tooltip', function(e) {
+ if (_elem != self.current_elem)
+ {
+ //Prepare the tooltip
+ self._prepare(_text);
+
+ self.current_elem = _elem;
+ self.show_delta = 0;
+ self.x = e.clientX;
+ self.y = e.clientY;
+
+ window.setTimeout(function() {self.showHintTimeout();}, self.time_delta);
+ }
+
+ return false;
+ });
+
+ _elem.bind('mouseleave.tooltip', function() {
+ self.current_elem = null;
+ self.show_delta = 0;
+ //self.hide();
+ if (self.tooltip_div)
+ {
+ self.tooltip_div.fadeOut(100);
+ }
+ });
+
+ _elem.bind('mousemove.tooltip', function(e) {
+ //Calculate the distance the mouse took since the last call of mousemove
+ var dx = self.x - e.clientX;
+ var dy = self.y - e.clientY;
+ var movedist = Math.sqrt(dx * dx + dy * dy);
+
+ //Block appereance of the tooltip on fast movements (with small movedistances)
+ if (movedist > 2)
+ self.show_delta = 0;
+
+ self.x = e.clientX;
+ self.y = e.clientY;
+ });
+ }
+}
+
+egw_tooltip.prototype.unbindFromElement = function(_elem)
+{
+ _elem.unbind('mouseenter.tooltip');
+ _elem.unbind('mouseleave.tooltip');
+ _elem.unbind('mousemove.tooltip');
+}
+
+egw_tooltip.prototype.showHintTimeout = function()
+{
+ if (this.current_elem != null)
+ {
+ this.show_delta += this.time_delta;
+ if (this.show_delta < this.show_delay)
+ {
+ //Repeat the call of timeout
+ var self = this;
+ window.setTimeout(function() {self.showHintTimeout();}, this.time_delta);
+ }
+ else
+ {
+ this.show_delta = 0;
+ this.show();
+ }
+ }
+}
+
+egw_tooltip.prototype._prepare = function(_text)
+{
+ var self = this;
+
+ //Remove the old tooltip
+ if (this.tooltip_div)
+ this.hide();
+
+ //Generate the tooltip div, set it's text and append it to the body tag
+ this.tooltip_div = $j(document.createElement('div'));
+ this.tooltip_div.hide();
+ this.tooltip_div.append(_text);
+ this.tooltip_div.addClass("egw_tooltip");
+ $j('body').append(this.tooltip_div);
+
+ //The tooltip should automatically hide when the mouse comes over it
+ this.tooltip_div.mouseenter(function() {
+ self.hide();
+ });
+}
+
+egw_tooltip.prototype.show = function()
+{
+ if (this.tooltip_div)
+ {
+ //Calculate the cursor_rectangle - this is a space the tooltip might
+ //not overlap with
+ var cursor_rect = {
+ left: (this.x - 8),
+ top: (this.y - 8),
+ right: (this.x + 8),
+ bottom: (this.y + 8)
+ };
+
+ //Calculate how much space is left on each side of the rectangle
+ var window_width = $j(document).width();
+ var window_height = $j(document).height();
+ var space_left = {
+ left: (cursor_rect.left),
+ top: (cursor_rect.top),
+ right: (window_width - cursor_rect.right),
+ bottom: (window_height - cursor_rect.bottom)
+ };
+
+ //Get the width and the height of the tooltip
+ var tooltip_width = this.tooltip_div.width();
+ if (tooltip_width > 300) tooltip_width = 300;
+ var tooltip_height = this.tooltip_div.height();
+
+
+ if (space_left.right < tooltip_width) {
+ this.tooltip_div.css('left', cursor_rect.left - tooltip_width);
+ } else if (space_left.left >= tooltip_width) {
+ this.tooltip_div.css('left', cursor_rect.right);
+ } else {
+ this.tooltip_div.css('left', cursor_rect.right);
+ this.tooltip_div.css('max-width', space_left.right);
+ }
+
+ if (space_left.bottom < tooltip_height) {
+ this.tooltip_div.css('top', cursor_rect.top - tooltip_height);
+ } else if (space_left.top >= tooltip_height) {
+ this.tooltip_div.css('top', cursor_rect.bottom);
+ } else {
+ this.tooltip_div.css('top', cursor_rect.bottom);
+ this.tooltip_div.css('max-height', space_left.bottom);
+ }
+
+ this.tooltip_div.fadeIn(100);
+ }
+}
+
+egw_tooltip.prototype.hide = function()
+{
+ if (this.tooltip_div)
+ {
+ this.tooltip_div.remove();
+ this.tooltip_div = null;
+ }
+}
+
+//A reference to the current tooltip object
+window.egw_global_tooltip = new egw_tooltip();
+
diff --git a/etemplate/js/test/et2_test_description.xet b/etemplate/js/test/et2_test_description.xet
new file mode 100644
index 0000000000..e5775712ef
--- /dev/null
+++ b/etemplate/js/test/et2_test_description.xet
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/etemplate/js/test/et2_test_grid.xet b/etemplate/js/test/et2_test_grid.xet
index fa4b2d6b2d..2db9ab63a5 100644
--- a/etemplate/js/test/et2_test_grid.xet
+++ b/etemplate/js/test/et2_test_grid.xet
@@ -25,15 +25,16 @@
-
+
+
-
+
@@ -121,12 +122,12 @@
-
+
+
-
diff --git a/etemplate/js/test/et2_test_tabbox.xet b/etemplate/js/test/et2_test_tabbox.xet
new file mode 100644
index 0000000000..aa310f721b
--- /dev/null
+++ b/etemplate/js/test/et2_test_tabbox.xet
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/etemplate/js/test/et2_test_textbox.xet b/etemplate/js/test/et2_test_textbox.xet
new file mode 100644
index 0000000000..663b17f518
--- /dev/null
+++ b/etemplate/js/test/et2_test_textbox.xet
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/etemplate/js/test/et2_test_timesheet_edit.xet b/etemplate/js/test/et2_test_timesheet_edit.xet
index fbfcd2c9ad..f8c403e638 100644
--- a/etemplate/js/test/et2_test_timesheet_edit.xet
+++ b/etemplate/js/test/et2_test_timesheet_edit.xet
@@ -145,7 +145,7 @@
-
+
diff --git a/etemplate/js/test/gfx/gradient01.png b/etemplate/js/test/gfx/gradient01.png
new file mode 100644
index 0000000000..111cd7fbf3
Binary files /dev/null and b/etemplate/js/test/gfx/gradient01.png differ
diff --git a/etemplate/js/test/gfx/gradient01.svg b/etemplate/js/test/gfx/gradient01.svg
new file mode 100644
index 0000000000..8ffdc2bb0c
--- /dev/null
+++ b/etemplate/js/test/gfx/gradient01.svg
@@ -0,0 +1,85 @@
+
+
+
+
diff --git a/etemplate/js/test/gfx/gradient02.png b/etemplate/js/test/gfx/gradient02.png
new file mode 100644
index 0000000000..8ea5aed2d1
Binary files /dev/null and b/etemplate/js/test/gfx/gradient02.png differ
diff --git a/etemplate/js/test/gfx/gradient02.svg b/etemplate/js/test/gfx/gradient02.svg
new file mode 100644
index 0000000000..61ae2bbbc9
--- /dev/null
+++ b/etemplate/js/test/gfx/gradient02.svg
@@ -0,0 +1,90 @@
+
+
+
+
diff --git a/etemplate/js/test/test_xml.html b/etemplate/js/test/test_xml.html
index 4a849d78ac..804ca50b8f 100644
--- a/etemplate/js/test/test_xml.html
+++ b/etemplate/js/test/test_xml.html
@@ -11,7 +11,10 @@
+
+
+
@@ -84,6 +167,9 @@
Timesheet edit dialog
Template proxy test
Grid test
+ Tabs test
+ Textbox test
+ Description test