Etemplate now uses JSON instead of XML for templates on the client side.

XML files are parsed into JSON objects on the server, then sent as JSON.
Etemplate parses the JSON object on the client side instead of the XML file directly.  This is supposed to be faster for IE.
This commit is contained in:
Nathan Gray 2015-08-18 17:47:40 +00:00
parent beb829c315
commit ecb972ca66
15 changed files with 366 additions and 209 deletions

View File

@ -166,9 +166,11 @@ class etemplate_new extends etemplate_widget_template
// Info required to load the etemplate client-side
$dom_id = str_replace('.','-',$this->dom_id);
$filename = etemplate_widget_template::rel2path(etemplate_widget_template::relPath($name));
$load_array = array(
'name' => $this->name,
'url' => etemplate_widget_template::rel2url($this->rel_path),
'url' => egw_framework::link('/etemplate/template.php', array('name' => $this->name, 'download' => filemtime($filename))),
// etemplate_widget_template::rel2url($this->rel_path),
'data' => $data,
'DOMNodeID' => $dom_id,
);

View File

@ -35,6 +35,13 @@ class etemplate_widget_template extends etemplate_widget
*/
protected static $cache = array();
/**
* Tell egw framework it's ok to call this
*/
public $public_functions = array(
'ajaxtoJSON' => true
);
/**
* Get instance of template specified by name, template(-set) and version
*

View File

@ -471,16 +471,15 @@ var et2_widget = ClassWithAttributes.extend(
},
/**
* The parseXMLAttrs function takes an XML DOM attributes object
* The parseJSONAttrs function takes a JSON object
* and adds the given attributes to the _target associative array. This
* function also parses the legacyOptions.
*
* @param _attrsObj is the XML DOM attributes object
* @param _attrsObj is the JSON object
* @param {object} _target is the object to which the attributes should be written.
* @param {et2_widget} _proto prototype with attributes and legacyOptions attribute
*/
parseXMLAttrs: function(_attrsObj, _target, _proto) {
parseJSONAttrs: function(_attrsObj, _target, _proto) {
// Check whether the attributes object is really existing, if not abort
if (typeof _attrsObj == "undefined")
{
@ -488,71 +487,62 @@ var et2_widget = ClassWithAttributes.extend(
}
// Iterate over the given attributes and parse them
var mgr = this.getArrayMgr("content");
for (var i = 0; i < _attrsObj.length; i++)
for (var name in _attrsObj)
{
var attrName = _attrsObj[i].name;
var attrValue = _attrsObj[i].value;
this._parseAttr(name, _attrsObj[name], _target, _proto);
}
},
// Special handling for the legacy options
if (attrName == "options" && _proto.legacyOptions.length > 0)
/**
* Parse a single attribute
*
* @param {string} attrName Attribute name
* @param {object} attrValue Attribute value
* @param {object} _target is the object to which the attributes should be written.
* @param {et2_widget} _proto prototype with attributes and legacyOptions attribute
*/
_parseAttr: function(attrName, attrValue, _target, _proto)
{
var mgr = this.getArrayMgr("content");
// Special handling for the legacy options
if (attrName == "options" && _proto.legacyOptions.length > 0)
{
// Check for modifications on legacy options here. Normal modifications
// are handled in widget constructor, but it's too late for legacy options then
if(_target.id && this.getArrayMgr("modifications").getEntry(_target.id))
{
// Check for modifications on legacy options here. Normal modifications
// are handled in widget constructor, but it's too late for legacy options then
if(_target.id && this.getArrayMgr("modifications").getEntry(_target.id))
{
var mod = this.getArrayMgr("modifications").getEntry(_target.id);
if(typeof mod.options != "undefined") attrValue = _attrsObj[i].value = mod.options;
}
// expand legacyOptions with content
if(attrValue.charAt(0) == '@' || attrValue.indexOf('$') != -1)
{
attrValue = mgr.expandName(attrValue);
}
// Parse the legacy options (as a string, other types not allowed)
var splitted = et2_csvSplit(attrValue+"");
for (var j = 0; j < splitted.length && j < _proto.legacyOptions.length; j++)
{
// Blank = not set
if(splitted[j].trim().length == 0) continue;
// Check to make sure we don't overwrite a current option with a legacy option
if(typeof _target[_proto.legacyOptions[j]] === "undefined")
{
attrValue = splitted[j];
/**
If more legacy options than expected, stuff them all in the last legacy option
Some legacy options take a comma separated list.
*/
if(j == _proto.legacyOptions.length - 1 && splitted.length > _proto.legacyOptions.length)
{
attrValue = splitted.slice(j);
}
var attr = _proto.attributes[_proto.legacyOptions[j]];
// If the attribute is marked as boolean, parse the
// expression as bool expression.
if (attr.type == "boolean")
{
attrValue = mgr.parseBoolExpression(attrValue);
}
else if (typeof attrValue != "object")
{
attrValue = mgr.expandName(attrValue);
}
_target[_proto.legacyOptions[j]] = attrValue;
}
}
var mod = this.getArrayMgr("modifications").getEntry(_target.id);
if(typeof mod.options != "undefined") attrValue = attrValue = mod.options;
}
else
// expand legacyOptions with content
if(attrValue.charAt(0) == '@' || attrValue.indexOf('$') != -1)
{
if (mgr != null && typeof _proto.attributes[attrName] != "undefined")
attrValue = mgr.expandName(attrValue);
}
// Parse the legacy options (as a string, other types not allowed)
var splitted = et2_csvSplit(attrValue+"");
for (var j = 0; j < splitted.length && j < _proto.legacyOptions.length; j++)
{
// Blank = not set
if(splitted[j].trim().length == 0) continue;
// Check to make sure we don't overwrite a current option with a legacy option
if(typeof _target[_proto.legacyOptions[j]] === "undefined")
{
var attr = _proto.attributes[attrName];
attrValue = splitted[j];
/**
If more legacy options than expected, stuff them all in the last legacy option
Some legacy options take a comma separated list.
*/
if(j == _proto.legacyOptions.length - 1 && splitted.length > _proto.legacyOptions.length)
{
attrValue = splitted.slice(j);
}
var attr = _proto.attributes[_proto.legacyOptions[j]];
// If the attribute is marked as boolean, parse the
// expression as bool expression.
@ -560,16 +550,35 @@ var et2_widget = ClassWithAttributes.extend(
{
attrValue = mgr.parseBoolExpression(attrValue);
}
else
else if (typeof attrValue != "object")
{
attrValue = mgr.expandName(attrValue);
}
_target[_proto.legacyOptions[j]] = attrValue;
}
// Set the attribute
_target[attrName] = attrValue;
}
}
else
{
if (mgr != null && typeof _proto.attributes[attrName] != "undefined")
{
var attr = _proto.attributes[attrName];
// If the attribute is marked as boolean, parse the
// expression as bool expression.
if (attr.type == "boolean")
{
attrValue = mgr.parseBoolExpression(attrValue);
}
else
{
attrValue = mgr.expandName(attrValue);
}
}
// Set the attribute
_target[attrName] = attrValue;
}
},
/**
@ -622,47 +631,51 @@ var et2_widget = ClassWithAttributes.extend(
},
/**
* Create a et2_widget from an XML node.
* Create a et2_widget from a JSON object.
*
* First the type and attributes are read from the node. Then the readonly & modifications
* arrays are checked for changes specific to the loaded data. Then the appropriate
* constructor is called. After the constructor returns, the widget has a chance to
* further initialize itself from the XML node when the widget's loadFromXML() method
* is called with the node.
* further initialize itself from the object when the widget's loadFromObject() method
* is called with the object.
*
* @param _node XML node to read
* @param _node Object to read
*
* @return et2_widget
*/
createElementFromNode: function(_node) {
createElementFromObject: function(_node) {
var attributes = {};
if(typeof _node.attributes === 'undefined')
{
_node.attributes = {};
}
// Parse the "readonly" and "type" flag for this element here, as they
// determine which constructor is used
var _nodeName = attributes["type"] = _node.getAttribute("type") ?
_node.getAttribute("type") : _node.nodeName.toLowerCase();
var _nodeName = attributes["type"] = _node.attributes.type ?
_node.attributes.type : _node.tag;
var readonly = attributes["readonly"] =
this.getArrayMgr("readonlys").isReadOnly(
_node.getAttribute("id"), _node.getAttribute("readonly"),
_node.attributes.id, _node.attributes.readonly,
typeof this.readonly !== 'undefined' ? this.readonly : this.options.readonly );
// Check to see if modifications change type
var modifications = this.getArrayMgr("modifications");
if(modifications && _node.getAttribute("id")) {
var entry = modifications.getEntry(_node.getAttribute("id"));
if(modifications && _node.attributes.id) {
var entry = modifications.getEntry(_node.attributes.id);
if(entry == null)
{
// Try again, but skip the fancy stuff
// TODO: Figure out why the getEntry() call doesn't always work
var entry = modifications.data[_node.getAttribute("id")];
var entry = modifications.data[_node.attributes.id];
if(entry)
{
this.egw().debug("warn", "getEntry("+_node.getAttribute("id")+") failed, but the data is there.", modifications, entry);
this.egw().debug("warn", "getEntry("+_node.attributes.id+") failed, but the data is there.", modifications, entry);
}
else
{
// Try the root, in case a namespace got missed
var entry = modifications.getRoot().getEntry(_node.getAttribute("id"));
var entry = modifications.getRoot().getEntry(_node.attributes.id);
}
}
if(entry && entry.type)
@ -689,7 +702,7 @@ var et2_widget = ClassWithAttributes.extend(
}
// Parse the attributes from the given XML attributes object
this.parseXMLAttrs(_node.attributes, attributes, constructor.prototype);
this.parseJSONAttrs(_node.attributes, attributes, constructor.prototype);
// Do an sanity check for the attributes
constructor.prototype.generateAttributeSet(attributes);
@ -699,39 +712,31 @@ var et2_widget = ClassWithAttributes.extend(
var widget = new constructor(this, attributes);
// Load the widget itself from XML
widget.loadFromXML(_node);
widget.loadFromJSON(_node);
return widget;
},
/**
* Loads the widget tree from an XML node
* Loads the widget tree from JSON object
*
* @param _node xml node
* @param {Object} _content
*/
loadFromXML: function(_node) {
// Load the child nodes.
for (var i = 0; i < _node.childNodes.length; i++)
loadFromJSON: function(object)
{
if(object.content)
{
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;
}
this.loadContent(object.content);
}
if(!object.children) return;
// Load the child nodes.
for (var i = 0; i < object.children.length; i++)
{
var node = object.children[i];
node.parentNode = object;
// Create the new element
this.createElementFromNode(node);
this.createElementFromObject(node);
}
},

View File

@ -64,11 +64,14 @@ function et2_directChildrenByTagName(_node, _tagName)
_tagName = _tagName.toLowerCase();
var result = [];
for (var i = 0; i < _node.childNodes.length; i++)
var children = _node.childNodes || _node.children || [];
for (var i = 0; i < children.length; i++)
{
if (_tagName == _node.childNodes[i].nodeName.toLowerCase())
var child = children[i];
child.parentNode = _node;
if (child.nodeName && _tagName === child.nodeName.toLowerCase() || child.tag && _tagName === child.tag)
{
result.push(_node.childNodes[i]);
result.push(child);
}
}
@ -77,10 +80,12 @@ function et2_directChildrenByTagName(_node, _tagName)
function et2_filteredNodeIterator(_node, _callback, _context)
{
for (var i = 0; i < _node.childNodes.length; i++)
if(!_node.children) return;
for (var i = 0; i < _node.children.length; i++)
{
var node = _node.childNodes[i];
var nodeName = node.nodeName.toLowerCase();
var node = _node.children[i];
node.parentNode = _node;
var nodeName = node.tag;
if (nodeName.charAt(0) != "#")
{
_callback.call(_context, node, nodeName);
@ -90,9 +95,15 @@ function et2_filteredNodeIterator(_node, _callback, _context)
function et2_readAttrWithDefault(_node, _name, _default)
{
if( _node.getAttribute)
{
var val = _node.getAttribute(_name);
return (val === null) ? _default : val;
}
else if (_node.attributes)
{
var val = _node.attributes[_name];
}
return (val === null || typeof val === 'undefined') ? _default : val;
}

View File

@ -328,13 +328,13 @@ var et2_customfields_list = et2_valueWidget.extend([et2_IDetachedDOM, et2_IInput
}
},
loadFromXML: function(_node) {
loadFromJSON: function(_node) {
this.loadFields();
// Load the nodes as usual
this._super.apply(this, arguments);
},
set_value: function(_value) {
if(!this.options.customfields) return;
for(var field_name in this.options.customfields)

View File

@ -53,7 +53,7 @@ var et2_box = et2_baseWidget.extend([et2_IDetachedDOM],
*
* @param {object} _node
*/
loadFromXML: function(_node) {
loadFromJSON: function(_node) {
if(this._type != "box")
{
return this._super.apply(this, arguments);
@ -61,30 +61,16 @@ var et2_box = et2_baseWidget.extend([et2_IDetachedDOM],
// Load the child nodes.
var childIndex = 0;
var repeatNode = null;
for (var i=0; i < _node.childNodes.length; i++)
for (var i=0; i < _node.children.length; i++)
{
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;
}
var node = _node.children[i];
var widgetType = node.tag;
// Create the new element, if no expansion needed
var id = et2_readAttrWithDefault(node, "id", "");
if(id.indexOf('$') < 0 || widgetType != 'box')
{
this.createElementFromNode(node);
this.createElementFromObject(node);
childIndex++;
}
else
@ -109,7 +95,7 @@ var et2_box = et2_baseWidget.extend([et2_IDetachedDOM],
}
}
this.createElementFromNode(repeatNode);
this.createElementFromObject(repeatNode);
}
// Reset

View File

@ -86,7 +86,7 @@ var et2_entry = et2_valueWidget.extend(
this.setDOMNode(document.createElement('span'));
},
loadFromXML: function(_node) {
loadFromJSON: function(_node) {
// Load the nodes as usual
this._super.apply(this, arguments);

View File

@ -352,9 +352,9 @@ var et2_grid = et2_DOMWidget.extend([et2_IDetachedDOM, et2_IAligned, et2_IResize
var cell = this._getCell(cells, x, y);
// Read the span value of the element
if (node.getAttribute("span"))
if (node.attributes.span)
{
cell.rowSpan = node.getAttribute("span");
cell.rowSpan = node.attributes.span;
}
else
{
@ -423,9 +423,9 @@ var et2_grid = et2_DOMWidget.extend([et2_IDetachedDOM, et2_IAligned, et2_IResize
var cell = this._getCell(cells, x, y);
// Read the span value of the element
if (node.getAttribute("span"))
if (node.attributes && node.attributes.span)
{
cell.colSpan = node.getAttribute("span");
cell.colSpan = node.attributes.span;
}
else
{
@ -441,20 +441,20 @@ var et2_grid = et2_DOMWidget.extend([et2_IDetachedDOM, et2_IAligned, et2_IResize
var span = cell.colSpan = this._forceNumber(cell.colSpan);
// Read the align value of the element
if (node.getAttribute("align"))
if (node.attributes && node.attributes.align)
{
cell.align = node.getAttribute("align");
cell.align = node.attributes.align;
}
// store id of nextmatch-*headers, so it is available for disabled widgets, which get not instanciated
if (nodeName.substr(0, 10) == 'nextmatch-')
{
cell.nm_id = node.getAttribute('id');
cell.nm_id = node.attributes.id;
}
// Apply widget's class to td, for backward compatability
if(node.getAttribute("class"))
if(node.attributes && node.attributes.class)
{
cell.class += (cell.class ? " " : "") + node.getAttribute("class");
cell.class += (cell.class ? " " : "") + node.attributes.class;
}
// Create the element
@ -480,7 +480,7 @@ var et2_grid = et2_DOMWidget.extend([et2_IDetachedDOM, et2_IAligned, et2_IResize
}
var widget = this.createElementFromNode(node, nodeName);
var widget = this.createElementFromObject(node, nodeName);
}
// Fill all cells the widget is spanning
@ -520,7 +520,7 @@ var et2_grid = et2_DOMWidget.extend([et2_IDetachedDOM, et2_IAligned, et2_IResize
}
// If row disabled, just skip it
var disabled = false;
if(node.getAttribute("disabled") == "1")
if(et2_readAttrWithDefault(node, "disabled", false) == "1")
{
disabled = true;
}
@ -595,23 +595,23 @@ var et2_grid = et2_DOMWidget.extend([et2_IDetachedDOM, et2_IAligned, et2_IResize
/**
* As the does not fit very well into the default widget structure, we're
* overwriting the loadFromXML function and doing a two-pass reading -
* overwriting the loadFromJSON function and doing a two-pass reading -
* in the first step the
*
* @param {object} _node xml node to process
*/
loadFromXML: function(_node) {
loadFromJSON: function(_node) {
// Keep the node for later changing / reloading
this.template_node = _node;
// Get the columns and rows tag
var rowsElems = et2_directChildrenByTagName(_node, "rows");
var columnsElems = et2_directChildrenByTagName(_node, "columns");
var rowsElems = _node.children[1];
var columnsElems = _node.children[0];
if (rowsElems.length == 1 && columnsElems.length == 1)
if (rowsElems && columnsElems)
{
var columns = columnsElems[0];
var rows = rowsElems[0];
var columns = columnsElems;
var rows = rowsElems;
var colData = [];
var rowData = [];
@ -870,7 +870,7 @@ var et2_grid = et2_DOMWidget.extend([et2_IDetachedDOM, et2_IAligned, et2_IResize
}
// Rebuild grid
this.loadFromXML(this.template_node);
this.loadFromJSON(this.template_node);
// New widgets need to finish
this.loadingFinished();

View File

@ -96,15 +96,15 @@ var et2_hbox = et2_baseWidget.extend(
},
/**
* The overwritten loadFromXML function checks whether any child element has
* The overwritten function checks whether any child element has
* a special align value.
*
* @param {object} _node
*/
loadFromXML: function(_node) {
loadFromJSON: function(_node) {
// Check whether any child node has an alignment tag
et2_filteredNodeIterator(_node, function(_node) {
var align = _node.getAttribute("align");
var align = et2_readAttrWithDefault(_node, 'align','');
if (!align)
{

View File

@ -398,9 +398,9 @@ var et2_selectbox = et2_inputWidget.extend(
return true;
},
loadFromXML: function(_node) {
loadFromJSON: function(_node) {
// Handle special case where legacy option for empty label is used (conflicts with rows), and rows is set as an attribute
var legacy = _node.getAttribute("options");
var legacy = _node.attributes.options || false;
if(legacy)
{
var legacy = legacy.split(",");

View File

@ -114,7 +114,7 @@ var et2_split = et2_DOMWidget.extend([et2_IResizeable,et2_IPrint],
* Tap in here to check if we have real children, because all children should be created
* by this point. If there are, replace the placeholders.
*/
loadFromXML: function() {
loadFromJSON: function() {
this._super.apply(this, arguments);
if(this._children.length > 0)
{

View File

@ -132,7 +132,7 @@ var et2_tabbox = et2_valueWidget.extend([et2_IInput,et2_IResizeable],
"contentDiv": null,
"flagDiv": null,
"hidden": hide,
"XMLNode": null,
"JSON": null,
"promise": null
});
}
@ -157,7 +157,7 @@ var et2_tabbox = et2_valueWidget.extend([et2_IInput,et2_IResizeable],
if (i < tabData.length)
{
// Store node for later evaluation
tabData[i].XMLNode = node;
tabData[i].JSON = node;
}
else
{
@ -167,7 +167,7 @@ var et2_tabbox = et2_valueWidget.extend([et2_IInput,et2_IResizeable],
}, this);
},
loadFromXML: function(_node) {
loadFromJSON: function(_node) {
// Get the tabs and tabpanels tags
var tabsElems = et2_directChildrenByTagName(_node, "tabs");
var tabpanelsElems = et2_directChildrenByTagName(_node, "tabpanels");
@ -219,7 +219,7 @@ var et2_tabbox = et2_valueWidget.extend([et2_IInput,et2_IResizeable],
"contentDiv": null,
"flagDiv": null,
"hidden": typeof tab.hidden != "undefined" ? tab.hidden : readonly[tab_id] || false,
"XMLNode": null,
"JSON": null,
"promise": null
});
}
@ -274,12 +274,12 @@ var et2_tabbox = et2_valueWidget.extend([et2_IInput,et2_IResizeable],
_loadTab: function(index,promises) {
var tabData = this.tabData[index];
if(!tabData || tabData.loaded) return;
if(tabData.XMLNode != null)
if(tabData.JSON != null)
{
tabData.widget = this.createElementFromNode(tabData.XMLNode,tabData.XMLNode.nodeName.toLowerCase());
tabData.widget = this.createElementFromObject(tabData.JSON,tabData.JSON.tag);
// Release the XML node
tabData.XMLNode = null;
// Release the JSON object
tabData.JSON = null;
}
else if (tabData.widget_options)
{

View File

@ -86,10 +86,10 @@ var et2_template = et2_DOMWidget.extend(
var cache_buster = parts.length > 1 ? parts.pop() : null;
var template_name = parts.pop();
// Check to see if XML is known
var xml = null;
// Check to see if the template is known
var template = null;
var templates = etemplate2.prototype.templates; // use global eTemplate cache
if(!(xml = templates[template_name]))
if(!(template = templates[template_name]))
{
// Check to see if ID is short form --> prepend parent/top-level name
if(template_name.indexOf('.') < 0)
@ -98,42 +98,52 @@ var et2_template = et2_DOMWidget.extend(
var top_name = root && root._inst ? root._inst.name : null;
if (top_name && template_name.indexOf('.') < 0) template_name = top_name+'.'+template_name;
}
xml = templates[template_name];
if(!xml)
template = templates[template_name];
if(!template)
{
// Ask server
var splitted = template_name.split('.');
// use template base url from initial template, to continue using webdav, if that was loaded via webdav
var path = this.getRoot()._inst.template_base_url + splitted.shift() + "/templates/default/" +
splitted.join('.')+ ".xet" + (cache_buster ? '?download='+cache_buster :
var path = this.getRoot()._inst.template_base_url +
splitted.join('.') + (cache_buster ? '&download='+cache_buster :
// if server did not give a cache-buster, fall back to current time
'?download='+(new Date).valueOf());
'&download='+(new Date).valueOf());
if(splitted.length)
{
et2_loadXMLFromURL(path, function(_xmldoc) {
// Scan for templates and store them
for(var i = 0; i < _xmldoc.childNodes.length; i++) {
var template = _xmldoc.childNodes[i];
if(template.nodeName.toLowerCase() != "template") continue;
templates[template.getAttribute("id")] = template;
jQuery.ajax({
url: path,
context: this,
type: 'GET',
dataType: 'json',
success: function(_data, _status, _xmlhttp){
for(var i = 0; i < _data.children.length; i++)
{
var template = _data.children[i];
if(template.tag !== "template") continue;
templates[template.attributes.id] = template;
}// Read the structure of the requested template
if (typeof templates[template_name] != 'undefined') this.loadFromJSON(templates[template_name]);
// Update flag
this.loading.resolve();
},
error: function(_xmlhttp, _err) {
egw().debug('error', 'Loading eTemplate from '+_url+' failed! '+_xmlhttp.status+' '+_xmlhttp.statusText);
}
// Read the XML structure of the requested template
if (typeof templates[template_name] != 'undefined') this.loadFromXML(templates[template_name]);
// Update flag
this.loading.resolve();
}, this);
});
}
return;
}
}
if(xml !== null && typeof xml !== "undefined")
if(template !== null && typeof template !== "undefined")
{
this.egw().debug("log", "Loading template from XML: ", template_name);
this.loadFromXML(xml);
this.egw().debug("log", "Loading template: ", template_name);
if(template.tag)
{
this.loadFromJSON(template);
}
// Don't call this here - done by caller, or on whole widget tree
//this.loadingFinished();
@ -142,7 +152,7 @@ var et2_template = et2_DOMWidget.extend(
}
else
{
this.egw().debug("warn", "Unable to find XML for ", template_name);
this.egw().debug("warn", "Unable to find ", template_name);
this.loading.reject();
}
}

View File

@ -446,8 +446,11 @@ etemplate2.prototype.load = function(_name, _url, _data, _callback)
}
etemplate2._byTemplate[_name].push(this);
// Read the XML structure of the requested template
this.widgetContainer.loadFromXML(this.templates[this.name]);
// Read the structure of the requested template
if (this.templates[this.name].children)
{
this.widgetContainer.loadFromJSON(this.templates[this.name]);
}
// List of Promises from widgets that are not quite fully loaded
var deferred = [];
@ -543,18 +546,25 @@ etemplate2.prototype.load = function(_name, _url, _data, _callback)
// Load & process
if(!this.templates[_name])
{
// Asynchronously load the XET file
et2_loadXMLFromURL(_url, function(_xmldoc) {
// Scan for templates and store them
for(var i = 0; i < _xmldoc.childNodes.length; i++) {
var template = _xmldoc.childNodes[i];
if(template.nodeName.toLowerCase() != "template") continue;
this.templates[template.getAttribute("id")] = template;
if(!_name) this.name = template.getAttribute("id");
jQuery.ajax({
url: _url,
context: this,
type: 'GET',
dataType: 'json',
success: function(_data, _status, _xmlhttp){
for(var i = 0; i < _data.children.length; i++)
{
var template = _data.children[i];
if(template.tag !== "template") continue;
this.templates[template.attributes.id] = template;
if(!_name) this.name = template.attributes.id;
}
_load.apply(this,[]);
},
error: function(_xmlhttp, _err) {
egw().debug('error', 'Loading eTemplate from '+_url+' failed! '+_xmlhttp.status+' '+_xmlhttp.statusText);
}
_load.apply(this,[]);
}, this);
});
// Split the given data into array manager objects and pass those to the
// widget container - do this here because file is loaded async

126
etemplate/template.php Normal file
View File

@ -0,0 +1,126 @@
<?php
/*
* Egroupware
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link http://www.egroupware.org
* @author Nathan Gray
* @version $Id$
*/
const CACHE_TIME = 3600;
//Set all necessary info and fire up egroupware
$GLOBALS['egw_info']['flags'] = array(
'currentapp' => 'etemplate',
'noheader' => true,
'nonavbar' => true
);
include ('../header.inc.php');
if (!ajaxtoJSON($_GET['name']))
{
header('404 Not found');
http_response_code(404);
}
/**
* Gets the specified template XML file converted to JSON representation
*
* @param String $name
* @return JSON
*/
function ajaxtoJSON($name)
{
if(!$name)
{
$name = get_var('name');
}
$filename = etemplate_widget_template::rel2path(etemplate_widget_template::relPath($name));
// Bad template name
if(trim($filename) == '')
{
return false;
}
error_log("Filename: $filename");
$mtime = filemtime($filename);
// First, check cache
$cached = egw_cache::getInstance('etemplate', $name);
// Not found, or modified
if(!$cached || !is_array($cached) || is_array($cached) && $cached['mtime'] != $mtime)
{
// Load XML & parse into JSON
$reader = simplexml_load_file($filename);
$template = json_encode(nodeToArray($reader));
$cached = array(
'template' => $template,
'mtime' => $mtime
);
}
else if ($cached);
{
$template = $cached['template'];
}
if($cached)
{
// Keep in instance cache so we don't have to regenerate it
egw_cache::setInstance('etemplate', $name, $cached, CACHE_TIME);
}
else
{
return false;
}
// Should set some headers so the browser can cache it too
header('Cache-Control: public, max-age='.CACHE_TIME);
header('Expires: ' . gmdate('D, d M Y H:i:s', time()+CACHE_TIME) . ' GMT');
header('Content-type: application/json');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $mtime));
header('Content-Length: ' . mb_strlen($template));
echo $template;
return true;
}
function nodeToArray($xmlnode, &$jsnode = false)
{
if(!$xmlnode) return;
if(!($xmlnode instanceof SimpleXMLElement) && trim($xmlnode))
{
$jsnode['content'] = $xmlnode;
return '';
}
$nodename = $xmlnode->getName();
$node =& $jsnode ? $jsnode : array();
$node['tag'] = strtolower($nodename);
$node['attributes'] = array();
if (count($xmlnode->attributes()) > 0)
{
$node["attributes"] = array();
foreach($xmlnode->attributes() as $key => $value)
{
$node["attributes"][$key] = (string)$value;
}
}
if(trim($xmlnode->__toString()) != '')
{
$node['content'] = $xmlnode->__toString();
}
// Load children
$child_index = 0;
foreach ($xmlnode->children() as $childxmlnode)
{
$node['children'][$child_index] = array('tag' => $childxmlnode->getName());
nodeToArray($childxmlnode, $node['children'][$child_index++]);
}
return $node;
}