egroupware/etemplate/js/etemplate2.js

473 lines
12 KiB
JavaScript
Raw Normal View History

/**
2011-09-09 14:39:27 +02:00
* EGroupware eTemplate2 - JS file which contains the complete et2 module
*
* @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
// Include all widget classes here
et2_widget_template;
et2_widget_grid;
et2_widget_box;
et2_widget_hbox;
et2_widget_groupbox;
et2_widget_button;
et2_widget_color;
et2_widget_description;
et2_widget_textbox;
et2_widget_number;
et2_widget_url;
et2_widget_selectbox;
et2_widget_checkbox;
et2_widget_radiobox;
et2_widget_date;
2012-05-24 17:45:29 +02:00
et2_widget_diff;
et2_widget_styles;
et2_widget_html;
2012-06-06 06:13:19 +02:00
et2_widget_htmlarea;
et2_widget_tabs;
2012-03-08 17:55:12 +01:00
et2_widget_tree;
2012-05-24 17:45:29 +02:00
et2_widget_historylog;
et2_widget_hrule;
et2_widget_image;
2011-09-01 01:37:30 +02:00
et2_widget_file;
et2_widget_link;
2011-09-09 14:39:27 +02:00
et2_widget_progress;
et2_widget_selectAccount;
et2_widget_ajaxSelect;
2012-03-26 21:46:51 +02:00
et2_widget_vfs;
et2_widget_itempicker;
et2_extension_nextmatch;
et2_extension_customfields;
// Requirements for the etemplate2 object
et2_core_common;
et2_core_xml;
et2_core_arrayMgr;
et2_core_interfaces;
et2_core_legacyJSFunctions;
// Include the client side api core
jsapi.egw_core;
jsapi.egw_json;
*/
/**
* The etemplate2 class manages a certain etemplate2 instance.
*
* @param _container is the DOM-Node into which the DOM-Nodes of this instance
* should be inserted
* @param _submitURL is the URL to which the form data should be submitted.
*/
function etemplate2(_container, _menuaction)
{
if (typeof _menuaction == "undefined")
{
_menuaction = "etemplate::ajax_process_content";
}
// Copy the given parameters
this.DOMContainer = _container;
this.menuaction = _menuaction;
// Preset the object variable
this.widgetContainer = null;
// List of templates (XML) that are known, but not used. Indexed by id.
this.templates = {};
// Connect to the window resize event
$j(window).resize(this, function(e) {e.data.resize()});
}
/**
* Calls the resize event of all widgets
*/
etemplate2.prototype.resize = function()
{
if (this.widgetContainer)
{
// Call the "resize" event of all functions which implement the
// "IResizeable" interface
this.widgetContainer.iterateOver(function(_widget) {
_widget.resize();
}, this, et2_IResizeable);
}
}
/**
* Clears the current instance.
*/
etemplate2.prototype.clear = function()
{
if (this.widgetContainer != null)
{
Major update of the et2_widget internal structure. The following changes were made: - All attributes of the widgets are now parsed from XML before the widget itself is created. These attributes plus all default values are then added to an associative array. The associative array is passed as second parameter to the init function of et2_widget, but is also available as this.options *after* the constructor of the et2_widget baseclass has been called. The et2_widget constructor also calls a function parseArrayMgrAttrs(_attrs) - in this function widget implementations can read the values from e.g. the content and validation_errors array and merge it into the given _attrs associative array. After the complete internal widgettree is completely loaded and created the "loadingFinished" function gets called and invokes all given setter functions. After that it "glues" the DOM tree together. This should also (I didn't measure it) be a bit faster than before, when the DOM-Tree was created on the fly. Please have a look at the changes of the et2_textbox widget to see how this affects writing widgets. Note: The "id" property is copied to the object scope on the top of the et2_widget constructor. - When widgets are cloned the "options" array gets passed along to the newly created widget. This means that changes made on the widgets during runtime are not automatically copied to the clone - as this didn't happen anyhow it is not a really disadvantage. On the other side there should be no difference between widgets directly inside the "overlay" xet tag and widgets which are inside instanciated templates. - The selbox widget doesn't work anymore - it relied on the loadAttributes function which isn't available anymore. et2_selbox should use the parseArrayMgrAttrs function to access - I've commented out some of the "validator"-code in etemplate2.js as it created some error messages when destroying the widget tree.
2011-08-19 18:00:44 +02:00
// $j(':input',this.DOMContainer).validator().data("validator").destroy();
this.widgetContainer.free();
this.widgetContainer = null;
}
this.templates = {};
}
/**
* Creates an associative array containing the data array managers for each part
* of the associative data array. A part is something like "content", "readonlys"
* or "sel_options".
*/
etemplate2.prototype._createArrayManagers = function(_data)
{
if (typeof _data == "undefined")
{
_data = {};
}
// Create all neccessary _data entries
2011-08-23 17:27:34 +02:00
var neededEntries = ["content", "sel_options", "readonlys", "modifications",
"validation_errors"];
for (var i = 0; i < neededEntries.length; i++)
{
if (typeof _data[neededEntries[i]] == "undefined" || !_data[neededEntries[i]])
{
egw.debug("log", "Created not passed entry '" + neededEntries[i] +
2011-08-23 17:27:34 +02:00
"' in data array.");
_data[neededEntries[i]] = {};
}
}
var result = {};
// Create an array manager object for each part of the _data array.
for (var key in _data)
{
switch (key) {
case "etemplate_exec_id": // already processed
case "app_header":
break;
case "readonlys":
result[key] = new et2_readonlysArrayMgr(_data[key]);
break;
default:
result[key] = new et2_arrayMgr(_data[key]);
}
}
return result;
}
/**
* Loads the template from the given URL and sets the data object
*/
etemplate2.prototype.load = function(_name, _url, _data)
{
2012-05-07 19:40:59 +02:00
egw().debug("info", "Loaded data", _data);
// Appname should be first part of the template name
var split = _name.split('.');
var appname = split[0];
// Create the document fragment into which the HTML will be injected
var frag = document.createDocumentFragment();
// Asynchronously load the XET file (code below is executed ahead of the
// code in the callback function)
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;
}
// Read the XML structure of the requested template
this.widgetContainer.loadFromXML(this.templates[_name]);
Major update of the et2_widget internal structure. The following changes were made: - All attributes of the widgets are now parsed from XML before the widget itself is created. These attributes plus all default values are then added to an associative array. The associative array is passed as second parameter to the init function of et2_widget, but is also available as this.options *after* the constructor of the et2_widget baseclass has been called. The et2_widget constructor also calls a function parseArrayMgrAttrs(_attrs) - in this function widget implementations can read the values from e.g. the content and validation_errors array and merge it into the given _attrs associative array. After the complete internal widgettree is completely loaded and created the "loadingFinished" function gets called and invokes all given setter functions. After that it "glues" the DOM tree together. This should also (I didn't measure it) be a bit faster than before, when the DOM-Tree was created on the fly. Please have a look at the changes of the et2_textbox widget to see how this affects writing widgets. Note: The "id" property is copied to the object scope on the top of the et2_widget constructor. - When widgets are cloned the "options" array gets passed along to the newly created widget. This means that changes made on the widgets during runtime are not automatically copied to the clone - as this didn't happen anyhow it is not a really disadvantage. On the other side there should be no difference between widgets directly inside the "overlay" xet tag and widgets which are inside instanciated templates. - The selbox widget doesn't work anymore - it relied on the loadAttributes function which isn't available anymore. et2_selbox should use the parseArrayMgrAttrs function to access - I've commented out some of the "validator"-code in etemplate2.js as it created some error messages when destroying the widget tree.
2011-08-19 18:00:44 +02:00
// Inform the widget tree that it has been successfully loaded.
this.widgetContainer.loadingFinished();
// Insert the document fragment to the DOM Container
this.DOMContainer.appendChild(frag);
// Trigger the "resize" event
this.resize();
}, this);
// Clear any existing instance
this.clear();
// Create the basic widget container and attach it to the DOM
this.widgetContainer = new et2_container(null);
this.widgetContainer.setApiInstance(egw(appname, egw.elemWindow(this.DOMContainer)));
this.widgetContainer.setInstanceManager(this);
this.widgetContainer.setParentDOMNode(this.DOMContainer);
// store the id to submit it back to server
if(_data) {
this.etemplate_exec_id = _data.etemplate_exec_id;
}
// set app_header
if (window.opener) { // popup
document.title = _data.app_header;
} else {
// todo for idots or jdots framework
}
// Split the given data into array manager objects and pass those to the
// widget container
this.widgetContainer.setArrayMgrs(this._createArrayManagers(_data));
}
etemplate2.prototype.submit = function(button)
{
// Validator
Major update of the et2_widget internal structure. The following changes were made: - All attributes of the widgets are now parsed from XML before the widget itself is created. These attributes plus all default values are then added to an associative array. The associative array is passed as second parameter to the init function of et2_widget, but is also available as this.options *after* the constructor of the et2_widget baseclass has been called. The et2_widget constructor also calls a function parseArrayMgrAttrs(_attrs) - in this function widget implementations can read the values from e.g. the content and validation_errors array and merge it into the given _attrs associative array. After the complete internal widgettree is completely loaded and created the "loadingFinished" function gets called and invokes all given setter functions. After that it "glues" the DOM tree together. This should also (I didn't measure it) be a bit faster than before, when the DOM-Tree was created on the fly. Please have a look at the changes of the et2_textbox widget to see how this affects writing widgets. Note: The "id" property is copied to the object scope on the top of the et2_widget constructor. - When widgets are cloned the "options" array gets passed along to the newly created widget. This means that changes made on the widgets during runtime are not automatically copied to the clone - as this didn't happen anyhow it is not a really disadvantage. On the other side there should be no difference between widgets directly inside the "overlay" xet tag and widgets which are inside instanciated templates. - The selbox widget doesn't work anymore - it relied on the loadAttributes function which isn't available anymore. et2_selbox should use the parseArrayMgrAttrs function to access - I've commented out some of the "validator"-code in etemplate2.js as it created some error messages when destroying the widget tree.
2011-08-19 18:00:44 +02:00
/*var valid = true;
var inputs = $j(':input',this.DOMContainer).each(function() {
if(typeof $j(this).data("validator") == "undefined") return true;
valid = valid && $j(this).data("validator").checkValidity();
return true;
});
Major update of the et2_widget internal structure. The following changes were made: - All attributes of the widgets are now parsed from XML before the widget itself is created. These attributes plus all default values are then added to an associative array. The associative array is passed as second parameter to the init function of et2_widget, but is also available as this.options *after* the constructor of the et2_widget baseclass has been called. The et2_widget constructor also calls a function parseArrayMgrAttrs(_attrs) - in this function widget implementations can read the values from e.g. the content and validation_errors array and merge it into the given _attrs associative array. After the complete internal widgettree is completely loaded and created the "loadingFinished" function gets called and invokes all given setter functions. After that it "glues" the DOM tree together. This should also (I didn't measure it) be a bit faster than before, when the DOM-Tree was created on the fly. Please have a look at the changes of the et2_textbox widget to see how this affects writing widgets. Note: The "id" property is copied to the object scope on the top of the et2_widget constructor. - When widgets are cloned the "options" array gets passed along to the newly created widget. This means that changes made on the widgets during runtime are not automatically copied to the clone - as this didn't happen anyhow it is not a really disadvantage. On the other side there should be no difference between widgets directly inside the "overlay" xet tag and widgets which are inside instanciated templates. - The selbox widget doesn't work anymore - it relied on the loadAttributes function which isn't available anymore. et2_selbox should use the parseArrayMgrAttrs function to access - I've commented out some of the "validator"-code in etemplate2.js as it created some error messages when destroying the widget tree.
2011-08-19 18:00:44 +02:00
if(!valid) return false;*/
// Get the form values
var values = this.getValues(this.widgetContainer);
// Trigger the submit event
var canSubmit = true;
this.widgetContainer.iterateOver(function(_widget) {
if (_widget.submit(values) === false)
{
canSubmit = false;
}
}, this, et2_ISubmitListener);
if (canSubmit)
{
// Button parameter used for submit buttons in datagrid
// TODO: This should probably go in nextmatch's getValues(), along with selected rows somehow.
// I'm just not sure how.
2012-03-02 19:35:49 +01:00
if(button && !values.button)
{
values.button = button.id
var path = button.getPath();
var target = values;
for(var i = 0; i < path.length; i++)
{
if(!values[path[i]]) values[path[i]] = {};
target = values[path[i]];
}
if(target != values)
{
var indexes = button.id.split('[');
if (indexes.length > 1)
{
indexes = [indexes.shift(), indexes.join('[')];
indexes[1] = indexes[1].substring(0,indexes[1].length-1);
var children = indexes[1].split('][');
if(children.length)
{
indexes = jQuery.merge([indexes[0]], children);
}
}
var idx = '';
for(var i = 0; i < indexes.length; i++)
{
idx = indexes[i];
if(!target[idx] || target[idx]['$row_cont']) target[idx] = i < indexes.length -1 ? {} : true;
target = target[idx];
}
}
}
// Create the request object
if (typeof egw_json_request != "undefined" && this.menuaction)
{
var api = this.widgetContainer.egw();
var request = api.json(this.menuaction, [this.etemplate_exec_id,values], null, this);
request.sendRequest();
}
else
{
egw.debug("info", "Form got submitted with values: ", values);
}
}
}
/**
* Does a full form post submit.
* Only use this one if you need it, use the ajax submit() instead
*/
etemplate2.prototype.postSubmit = function()
{
// Get the form values
var values = this.getValues(this.widgetContainer);
// Trigger the submit event
var canSubmit = true;
this.widgetContainer.iterateOver(function(_widget) {
if (_widget.submit(values) === false)
{
canSubmit = false;
}
}, this, et2_ISubmitListener);
if (canSubmit)
{
var form = jQuery("<form id='form' action='"+egw().webserverUrl +
"/etemplate/process_exec.php?menuaction=" + this.widgetContainer.egw().getAppName()+ "' method='POST'>");
var etemplate_id = jQuery(document.createElement("input"))
.attr("name",'etemplate_exec_id')
2012-05-22 22:16:33 +02:00
.attr("type",'hidden')
.val(this.etemplate_exec_id)
.appendTo(form);
var input = document.createElement("input");
2012-05-14 18:55:38 +02:00
input.type = "hidden";
input.name = 'value';
input.value = egw().jsonEncode(values);
form.append(input);
form.appendTo(jQuery('body')).submit();
}
}
/**
* Fetches all input element values and returns them in an associative
* array. Widgets which introduce namespacing can use the internal _target
* parameter to add another layer.
*/
etemplate2.prototype.getValues = function(_root)
{
var result = {};
// Iterate over the widget tree
_root.iterateOver(function(_widget) {
// The widget must have an id to be included in the values array
if (_widget.id == "")
{
return;
}
// Get the path to the node we have to store the value at
var path = _widget.getPath();
// check if id contains a hierachical name, eg. "button[save]"
var id = _widget.id;
var indexes = id.split('[');
if (indexes.length > 1)
{
indexes = [indexes.shift(), indexes.join('[')];
indexes[1] = indexes[1].substring(0,indexes[1].length-1);
2012-03-02 19:35:49 +01:00
var children = indexes[1].split('][');
if(children.length)
{
indexes = jQuery.merge([indexes[0]], children);
}
path = path.concat(indexes);
2012-03-02 19:35:49 +01:00
// Take the last one as the ID
id = path.pop();
}
// Set the _target variable to that node
var _target = result;
for (var i = 0; i < path.length; i++)
{
// Create a new object for not-existing path nodes
if (typeof _target[path[i]] === 'undefined')
{
_target[path[i]] = {};
}
// Check whether the path node is really an object
if (typeof _target[path[i]] === 'object')
{
_target = _target[path[i]];
}
else
{
egw.debug("error", "ID collision while writing at path " +
"node '" + path[i] + "'");
}
}
// Handle arrays, eg radio[]
if(id === "")
{
id = typeof _target == "undefined" ? 0 : Object.keys(_target).length;
}
var value = _widget.getValue();
// Check whether the entry is really undefined
if (typeof _target[id] != "undefined" && (typeof _target[id] != 'object' || typeof value != 'object'))
{
egw.debug("error", _widget, "Overwriting value of '" + _widget.id +
"', id exists twice!");
}
// Store the value of the widget and reset its dirty flag
if (value !== null)
{
// Merge, if possible (link widget)
if(typeof _target[id] == 'object' && typeof value == 'object')
{
_target[id] = jQuery.extend({},_target[id],value);
}
else
{
_target[id] = value;
}
}
else if (jQuery.isEmptyObject(_target))
{
// Avoid sending back empty sub-arrays
_target = result
for (var i = 0; i < path.length-1; i++)
{
_target = _target[path[i]];
}
delete _target[path[path.length-1]];
}
_widget.resetDirty();
}, this, et2_IInput);
2012-05-14 18:55:38 +02:00
egw().debug("info", "Value", result);
return result;
}
function etemplate2_handle_load(_type, _response)
{
// Check the parameters
var data = _response.data;
if (typeof data.url == "string" && typeof data.data === 'object')
{
this.load(data.name, data.url, data.data);
return true;
}
throw("Error while parsing et2_load response");
}
function etemplate2_handle_validation_error(_type, _response)
{
// Display validation errors
//$j(':input',this.DOMContainer).data("validator").invalidate(_response.data);
2012-06-19 00:56:20 +02:00
egw().debug("warn","Validation errors", _response.data);
}
// Calls etemplate2_handle_response in the context of the object which
// requested the response from the server
egw(window).registerJSONPlugin(etemplate2_handle_load, null, 'et2_load');
egw(window).registerJSONPlugin(etemplate2_handle_validation_error, null, 'et2_validation_error');