diff --git a/etemplate/inc/class.etemplate_new.inc.php b/etemplate/inc/class.etemplate_new.inc.php index 9fa896933f..54ca498bfe 100644 --- a/etemplate/inc/class.etemplate_new.inc.php +++ b/etemplate/inc/class.etemplate_new.inc.php @@ -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, ); diff --git a/etemplate/inc/class.etemplate_widget_template.inc.php b/etemplate/inc/class.etemplate_widget_template.inc.php index 47965567b9..a796f8850b 100644 --- a/etemplate/inc/class.etemplate_widget_template.inc.php +++ b/etemplate/inc/class.etemplate_widget_template.inc.php @@ -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 * diff --git a/etemplate/js/et2_core_widget.js b/etemplate/js/et2_core_widget.js index f3b3a78636..2f7d0ceb9d 100644 --- a/etemplate/js/et2_core_widget.js +++ b/etemplate/js/et2_core_widget.js @@ -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); } }, diff --git a/etemplate/js/et2_core_xml.js b/etemplate/js/et2_core_xml.js index ffbdd727ae..8616b9b484 100644 --- a/etemplate/js/et2_core_xml.js +++ b/etemplate/js/et2_core_xml.js @@ -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; } diff --git a/etemplate/js/et2_extension_customfields.js b/etemplate/js/et2_extension_customfields.js index dcbae50553..49d960cd3a 100644 --- a/etemplate/js/et2_extension_customfields.js +++ b/etemplate/js/et2_extension_customfields.js @@ -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) diff --git a/etemplate/js/et2_widget_box.js b/etemplate/js/et2_widget_box.js index 7aa45b1363..6ee5fa278f 100644 --- a/etemplate/js/et2_widget_box.js +++ b/etemplate/js/et2_widget_box.js @@ -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 diff --git a/etemplate/js/et2_widget_entry.js b/etemplate/js/et2_widget_entry.js index 26cfbdbecf..2aa2e088d8 100644 --- a/etemplate/js/et2_widget_entry.js +++ b/etemplate/js/et2_widget_entry.js @@ -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); diff --git a/etemplate/js/et2_widget_grid.js b/etemplate/js/et2_widget_grid.js index 487ec4352e..553cb754ee 100644 --- a/etemplate/js/et2_widget_grid.js +++ b/etemplate/js/et2_widget_grid.js @@ -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(); diff --git a/etemplate/js/et2_widget_hbox.js b/etemplate/js/et2_widget_hbox.js index 53f56bbe8f..a30c255d31 100644 --- a/etemplate/js/et2_widget_hbox.js +++ b/etemplate/js/et2_widget_hbox.js @@ -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) { diff --git a/etemplate/js/et2_widget_selectbox.js b/etemplate/js/et2_widget_selectbox.js index 6cce32cfbc..87ac181274 100644 --- a/etemplate/js/et2_widget_selectbox.js +++ b/etemplate/js/et2_widget_selectbox.js @@ -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(","); diff --git a/etemplate/js/et2_widget_split.js b/etemplate/js/et2_widget_split.js index f0c09e8a61..15e780fe46 100644 --- a/etemplate/js/et2_widget_split.js +++ b/etemplate/js/et2_widget_split.js @@ -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) { diff --git a/etemplate/js/et2_widget_tabs.js b/etemplate/js/et2_widget_tabs.js index e947d8d79f..f1b71ac416 100644 --- a/etemplate/js/et2_widget_tabs.js +++ b/etemplate/js/et2_widget_tabs.js @@ -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) { diff --git a/etemplate/js/et2_widget_template.js b/etemplate/js/et2_widget_template.js index a4f21a375c..a95ca5ddda 100644 --- a/etemplate/js/et2_widget_template.js +++ b/etemplate/js/et2_widget_template.js @@ -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(); } } diff --git a/etemplate/js/etemplate2.js b/etemplate/js/etemplate2.js index d7832f4193..01ff8c4a73 100644 --- a/etemplate/js/etemplate2.js +++ b/etemplate/js/etemplate2.js @@ -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 diff --git a/etemplate/template.php b/etemplate/template.php new file mode 100644 index 0000000000..7c816e4207 --- /dev/null +++ b/etemplate/template.php @@ -0,0 +1,126 @@ + '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; +}