- Checked browser compatibility, tested with IE8, Chrome, Opera, FF3.6

- Added indexOf function for IE compatiblity - this and some other code is redundant to that in egw_action_common.js - Probably this code should be merged into jsapi and jsapi.js should be cleaned up and splitted into multiple files
- Implemented template widget
- Implemented dummy implementation of description widget
- Improved et2_placeholder - it now shows all properties set for that placeholder
- Improved and extended test page
- Improved interface system in et2_inheritance.js - each object derrived from Class now has a instanceOf function which checks, whether the object is either an instance of the given class or implements the given interface (same behaviour as instanceOf in Java)
- Widgets can now define which other widget classes are allowed inside of them
This commit is contained in:
Andreas Stöckel 2011-08-05 14:53:54 +00:00
parent 9b7819977d
commit 8b2dae28f7
9 changed files with 8666 additions and 41 deletions

View File

@ -31,3 +31,19 @@ function et2_debug(_level, _msg)
}
}
/**
* IE Fix for array.indexOf
*/
if (typeof Array.prototype.indexOf == "undefined")
{
Array.prototype.indexOf = function(_elem) {
for (var i = 0; i < this.length; i++)
{
if (this[i] === _elem)
return i;
}
return -1;
};
}

View File

@ -0,0 +1,47 @@
/**
* eGroupWare eTemplate2 - JS Template 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
jquery.jquery;
et2_widget;
*/
/**
* Class which implements the "description" XET-Tag
*/
et2_description = et2_DOMWidget.extend({
init: function(_parent) {
this.span = $j(document.createElement("span"));
this._super.apply(this, arguments);
this.value = "";
},
set_value: function(_value) {
if (_value != this.value)
{
this.value = _value;
this.span.text(_value);
}
},
getDOMNode: function() {
return this.span[0];
}
});
et2_register_widget(et2_description, ["description"]);

View File

@ -21,11 +21,11 @@
* 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:
* An interface has to be created in the following way:
*
* IBreathingObject = {
* IBreathingObject = new Interface({
* breath: function() {}
* }
* });
*
* Human = Class.extend(IBreathingObject, {
* walk: function() {
@ -72,6 +72,15 @@
// 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.
@ -84,15 +93,23 @@
prototype["_ifacefuncs"] = [];
for (var i in interfaces)
for (var i = 0; i < interfaces.length; i++)
{
for (var key in interfaces[i])
var iface = interfaces[i];
if (iface instanceof Interface)
{
prototype["_ifacefuncs"].push(key);
for (var key in iface)
{
prototype["_ifacefuncs"].push(key);
}
}
else
{
throw("Interfaces must be instanceof Interface!");
}
}
for (var i in ifaces)
for (var i = 0; i < ifaces.length; i++)
{
prototype["_ifacefuncs"].push(ifaces[i]);
}
@ -109,7 +126,21 @@
}
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;
}
}
};
// The base Class implementation (does nothing)
this.Class = function(){};
@ -179,13 +210,8 @@
// 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)
for (var i = 0; i < this._ifacefuncs.length; i++)
{
var func = this._ifacefuncs[i];
if (!(typeof this[func] == "function"))
@ -194,6 +220,11 @@
"function '" + func + "' not implemented.");
}
}
if (this.init)
{
this.init.apply(this, arguments);
}
}
}

View File

@ -0,0 +1,123 @@
/**
* eGroupWare eTemplate2 - JS Template 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_widget;
*/
/**
* Class which implements the "template" XET-Tag. When the id parameter is set,
* the template class checks whether another template with this id already
* exists. If yes, this template is removed from the DOM tree, copied and
* inserted in place of this template.
*
* TODO: Check whether this widget behaves as it should.
*/
et2_template = et2_DOMWidget.extend({
/**
* Initializes this template widget as a simple container.
*/
init: function(_parent) {
this.proxiedTemplate = null;
this.isProxied = false;
this.div = document.createElement("div");
this._super.apply(this, arguments);
},
/**
* If the parent node is changed, either the DOM-Node of the proxied template
* or the DOM-Node of this template is connected to the parent DOM-Node.
*/
onSetParent: function() {
// 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)) {
var parentNode = this._parent.getDOMNode();
if (parentNode)
{
if (this.proxiedTemplate)
{
this.proxiedTemplate.setParentDOMNode(parentNode);
}
else if (!this.isProxied)
{
this.setParentDOMNode(parentNode);
}
}
}
},
makeProxied: function() {
if (!this.isProxied)
{
this.detatchFromDOM();
this.div = null;
this.parentNode = null;
}
this.isProxied = true;
},
set_id: function(_value) {
if (_value != this.id)
{
// Check whether a template with the given name already exists and
// is not a proxy.
var tmpl = this.getRoot().getWidgetById(_value);
if (tmpl instanceof et2_template && tmpl.proxiedTemplate == null &&
tmpl != this)
{
// Check whether we still have a proxied template, if yes,
// destroy it
if (this.proxiedTemplate != null)
{
this.proxiedTemplate.destroy();
this.proxiedTemplate = null;
}
// This element does not have a node in the tree
this.detatchFromDOM();
// Detatch the proxied template from the DOM to and set its
// isProxied property to true
tmpl.makeProxied();
// Create a clone of the template and add it as child of this
// template (done by passing "this" to the clone function)
this.proxiedTemplate = tmpl.clone(this);
// Disallow adding any new node to this template
this.supportedWidgetClasses = [];
// Call the parent change event function
this.onSetParent();
}
else
{
this._super(_value);
}
}
},
getDOMNode: function(_fromProxy) {
return this.div;
}
});
et2_register_widget(et2_template, ["template"]);

View File

@ -11,6 +11,7 @@
*/
/*egw:uses
jquery.jquery;
et2_xml;
et2_common;
et2_inheritance;
@ -29,7 +30,7 @@ var et2_registry = {};
function et2_register_widget(_constructor, _types)
{
// Iterate over all given types and register those
for (var i in _types)
for (var i = 0; i < _types.length; i++)
{
var type = _types[i].toLowerCase();
@ -71,6 +72,8 @@ et2_widget = Class.extend({
// Copy the parent parameter and add this widget to its parent children
// list.
this._parent = _parent;
this.onSetParent();
if (_parent != null)
{
this._parent.addChild(this);
@ -79,6 +82,10 @@ et2_widget = Class.extend({
this._children = [];
this.id = "";
this.type = _type;
// The supported widget classes array defines a whitelist for all widget
// classes or interfaces child widgets have to support.
this.supportedWidgetClasses = [et2_widget];
},
/**
@ -92,7 +99,7 @@ et2_widget = Class.extend({
destroy: function() {
// Call the destructor of all children
for (var i = this._children.length; i >= 0; i--)
for (var i = this._children.length - 1; i >= 0; i--)
{
this._children[i].destroy();
}
@ -106,6 +113,41 @@ et2_widget = Class.extend({
// Delete all references to other objects
this._children = [];
this._parent = null;
this.onSetParent();
},
/**
* Creates a copy of this widget. The parameters given are passed to the
* constructor of the copied object. If the parameters are omitted, _parent
* is defaulted to null
*/
clone: function(_parent, _type) {
// Default _parent to null
if (typeof _parent == "undefined")
{
_parent = null;
}
// Create the copy
var copy = new (this.constructor)(_parent, _type);
// Create a clone of all child elements
for (var i = 0; i < this._children.length; i++)
{
this._children[i].clone(copy, this._children[i].type);
}
// Copy all properties for which a setter function exists
for (var key in this)
{
if (key != "id" && typeof copy["set_" + key] == "function")
{
copy["set_" + key](this[key]);
}
}
return copy;
},
/**
@ -115,6 +157,14 @@ et2_widget = Class.extend({
return this._parent;
},
/**
* The set parent event is called, whenever the parent of the widget is set.
* Child classes can overwrite this function. Whe onSetParent is called,
* the change of the parent has already taken place.
*/
onSetParent: function() {
},
/**
* Returns the list of children of this widget.
*/
@ -154,14 +204,15 @@ et2_widget = Class.extend({
* @param _idx is the position at which the element should be added.
*/
insertChild: function(_node, _idx) {
if (_node instanceof et2_widget)
// Check whether the node is one of the supported widget classes.
if (this.isOfSupportedWidgetClass(_node))
{
_node.parent = this;
this._children.splice(_idx, 0, _node);
}
else
{
throw("_node is not an instance of et2_widget!");
throw("_node is not supported by this widget class!");
}
},
@ -176,6 +227,7 @@ et2_widget = Class.extend({
{
// This element is no longer parent of the child
_node._parent = null;
_node.onSetParent();
this._children.splice(idx, 1);
}
@ -205,6 +257,18 @@ et2_widget = Class.extend({
return null;
},
isOfSupportedWidgetClass: function(_obj)
{
for (var i = 0; i < this.supportedWidgetClasses.length; i++)
{
if (_obj.instanceOf(this.supportedWidgetClasses[i]))
{
return true;
}
}
return false;
},
/**
* Loads the widget tree from an XML node
*/
@ -221,6 +285,20 @@ et2_widget = Class.extend({
var node = _node.childNodes[i];
var widgetType = node.nodeName.toLowerCase();
if (widgetType == "#comment")
{
continue;
}
if (widgetType == "#text")
{
if (node.data.replace(/^\s+|\s+$/g, ''))
{
this.loadContent(node.data);
}
continue;
}
// Check whether a widget with the given type is registered.
var constructor = typeof et2_registry[widgetType] == "undefined" ?
et2_placeholder : et2_registry[widgetType];
@ -247,6 +325,12 @@ et2_widget = Class.extend({
}
},
/**
* Called whenever textNodes are loaded from the XML tree
*/
loadContent: function(_content) {
},
/**
* Calls the setter of each property with its current value, calls the
* update function of all child nodes.
@ -263,7 +347,7 @@ et2_widget = Class.extend({
}
// Call the update function of all children.
for (var i in this._children)
for (var i = 0; i < this._children.length; i++)
{
this._children[i].update();
}
@ -285,9 +369,9 @@ et2_widget = Class.extend({
/**
* Interface for all widget classes, which are based on a DOM node.
*/
et2_IDOMNode = {
et2_IDOMNode = new Interface({
getDOMNode: function() {}
}
});
/**
* Abstract widget class which can be inserted into the DOM. All widget classes
@ -301,17 +385,11 @@ et2_DOMWidget = et2_widget.extend(et2_IDOMNode, {
* object (if available) and passes it to its own "createDOMNode" function
*/
init: function(_parent, _type) {
this.parentNode = null;
this.visible = true;
// 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() {
@ -321,14 +399,22 @@ et2_DOMWidget = et2_widget.extend(et2_IDOMNode, {
this._super();
},
onSetParent: function() {
// 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());
}
},
detatchFromDOM: function() {
if (this.parentNode)
{
var node = this.getDOMNode();
if (node)
{
this.parentNode.removeChild(node);
this.parentNode = null;
}
}
},
@ -366,6 +452,17 @@ et2_DOMWidget = et2_widget.extend(et2_IDOMNode, {
{
node.setAttribute("id", _value);
}
},
set_visible: function(_value) {
/*if (_value != this.visible)
{
var node = this.getDOMNode();
if (node)
{
node.set
}
}*/
}
});
@ -376,13 +473,40 @@ et2_DOMWidget = et2_widget.extend(et2_IDOMNode, {
et2_placeholder = et2_DOMWidget.extend({
init: function() {
this.placeDiv = document.createElement("span");
// Create the placeholder div
this.placeDiv = $j(document.createElement("span"))
.addClass("et2_placeholder");
// The attrNodes object will hold the DOM nodes which represent the
// values of this object
this.attrNodes = {};
this._super.apply(this, arguments);
var headerNode = $j(document.createElement("span"))
.text(this.type)
.addClass("et2_caption");
$j(this.placeDiv).append(headerNode);
},
loadAttributes: function(_attrs) {
for (var i = 0; i < _attrs.length; i++)
{
var attr = _attrs[i];
if (typeof this.attrNodes[attr.name] == "undefined")
{
this.attrNodes[attr.name] = $j(document.createElement("span"))
.addClass("et2_attr");
this.placeDiv.append(this.attrNodes[attr.name]);
}
this.attrNodes[attr.name].text(attr.name + "=" + attr.value);
}
},
getDOMNode: function() {
return this.placeDiv;
return this.placeDiv[0];
}
});
@ -404,3 +528,12 @@ et2_container = et2_DOMWidget.extend({
});
/**
* Interface for all widgets which support returning a value
*/
et2_IValue = new Interface({
getValue: function() {}
});

View File

@ -31,7 +31,21 @@ function et2_loadXMLFromURL(_url, _callback, _context)
xmldoc.onreadystatechange = function() {
if (xmldoc && xmldoc.readyState == 4)
{
_callback.call(_context, xmldoc);
// Find the root node - the root node is the node which is not
// the "xml", not a text node and not a comment node - those nodes
// are marked with an "#"
for (var i = 0; i < xmldoc.childNodes.length; i++)
{
var nodeName = xmldoc.childNodes[i].nodeName;
if (nodeName != "xml" && nodeName.charAt(0) != "#")
{
// Call the callback function and pass the current node
_callback.call(_context, xmldoc.childNodes[i]);
return;
}
}
throw("Could not find XML root node.");
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<overlay>
<template id="test" template="" lang="" group="0" version="1">
<description value="This description is inside the 'test' template" options=""/>
</template>
<description value="Test template:" options=""/>
<template id="test" />
<template id="test" />
<template id="test" />
<template id="test" />
<template id="test" />
</overlay>

8176
etemplate/js/test/jquery.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,20 +2,92 @@
<head>
<title>ET2 - Test</title>
<script src="jquery.js"></script>
<script src="../et2_xml.js"></script>
<script src="../et2_inheritance.js"></script>
<script src="../et2_common.js"></script>
<script src="../et2_widget.js"></script>
</head>
<body onload="init();">
<script>
var doc = null;
<script src="../et2_template.js"></script>
<script src="../et2_description.js"></script>
<style type="text/css">
body {
font-family: Lucida Grande, sans-serif;
font-size: 10pt;
}
function init() {
et2_loadXMLFromURL("../../../timesheet/templates/default/edit.xet",
#linklist a {
color: blue;
display: block;
}
#linklist a:visited {
color: blue;
}
#linklist a:hover {
color: #5050FF;
}
#container {
margin: 10px;
border: 1px solid gray;
padding: 10px;
}
.header {
color: #111;
margin: 30px 0 5px 0;
border-bottom: 1px solid #111;
}
.et2_placeholder {
display: inline-block;
border: 1px solid cornflowerblue;
background-color: #FCFCFC;
padding: 3px;
margin: 1px;
}
.et2_placeholder .et2_caption {
display: block;
font-size: 8pt;
margin: 0 0 5px 0;
font-weight: bold;
color: #2E2E2E;
text-shadow: rgba(255, 255, 255, 0.5) 0 1px 0;
}
.et2_placeholder .et2_attr {
display: block;
font-size: 8pt;
color: #3030A0;
margin: 2px 0 2px 0;
}
</style>
</head>
<body>
<h1>EGroupware ETemplate2 Test</h1>
<div class="header">Choose one of the following tests:</div>
<div id="linklist">
<a href="#" onclick="open_xet('../../../timesheet/templates/default/edit.xet');">Timesheet edit dialog</a>
<a href="#" onclick="open_xet('et2_test_template.xet');">Template proxy test</a>
</div>
<div class="header">ETemplate2 container:</div>
<div id="container"></div>
<script>
var container = null;
function open_xet(file) {
et2_loadXMLFromURL(file,
function(_xmldoc) {
var container = new et2_container(null);
container.setParentDOMNode(document.body);
if (container != null)
{
container.destroy();
container = null;
}
container = new et2_container(null);
container.setParentDOMNode(document.getElementById("container"));
container.loadFromXML(_xmldoc);
});
}