forked from extern/egroupware
556 lines
14 KiB
JavaScript
556 lines
14 KiB
JavaScript
/**
|
|
* eGroupWare eTemplate2 - Class which contains a factory method for rows
|
|
*
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @package etemplate
|
|
* @subpackage dataview
|
|
* @link http://www.egroupware.org
|
|
* @author Andreas Stöckel
|
|
* @copyright Stylite 2011
|
|
* @version $Id$
|
|
*/
|
|
|
|
/*egw:uses
|
|
jquery.jquery;
|
|
et2_core_inheritance;
|
|
et2_core_interfaces;
|
|
et2_core_arrayMgr;
|
|
et2_core_widget;
|
|
*/
|
|
|
|
/**
|
|
* The row provider contains prototypes (full clonable dom-trees)
|
|
* for all registered row types.
|
|
*/
|
|
var et2_dataview_rowProvider = Class.extend({
|
|
|
|
init: function(_outerId, _columnIds) {
|
|
// Copy the given parameters
|
|
this._outerId = _outerId;
|
|
this._columnIds = _columnIds;
|
|
this._prototypes = {};
|
|
|
|
this._template = null;
|
|
this._mgrs = null;
|
|
this._rootWidget = null;
|
|
|
|
// Create the default row "prototypes"
|
|
this._createFullRowPrototype();
|
|
this._createDefaultPrototype();
|
|
this._createEmptyPrototype();
|
|
},
|
|
|
|
/**
|
|
* Returns a clone of the prototype with the given name. If the generator
|
|
* callback function is given, this function is called if the prototype
|
|
* does not yet registered.
|
|
*/
|
|
getPrototype: function(_name, _generator, _context) {
|
|
if (typeof this._prototypes[_name] == "undefined")
|
|
{
|
|
if (typeof _generator != "undefined")
|
|
{
|
|
this._prototypes[_name] = _generator.call(_context, this._outerId,
|
|
this._columnIds);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return this._prototypes[_name].clone();
|
|
},
|
|
|
|
/**
|
|
* Returns an array containing objects which have variable attributes
|
|
*/
|
|
_getVariableAttributeSet: function(_widget) {
|
|
var variableAttributes = [];
|
|
|
|
_widget.iterateOver(function(_widget) {
|
|
// Create the attribtues
|
|
var hasAttr = false;
|
|
var widgetData = {
|
|
"widget": _widget,
|
|
"data": []
|
|
};
|
|
|
|
// Get all attribute values
|
|
for (var key in _widget.attributes)
|
|
{
|
|
if (!_widget.attributes[key].ignore &&
|
|
typeof _widget.options[key] != "undefined")
|
|
{
|
|
var val = _widget.options[key];
|
|
|
|
// TODO: Improve detection
|
|
if (typeof val == "string" && val.indexOf("$") >= 0)
|
|
{
|
|
hasAttr = true;
|
|
widgetData.data.push({
|
|
"attribute": key,
|
|
"expression": val
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the entry if there is any data in it
|
|
if (hasAttr)
|
|
{
|
|
variableAttributes.push(widgetData);
|
|
}
|
|
|
|
}, this);
|
|
|
|
return variableAttributes;
|
|
},
|
|
|
|
_seperateWidgets: function(_varAttrs) {
|
|
// The detachable array contains all widgets which implement the
|
|
// et2_IDetachedDOM interface for all needed attributes
|
|
var detachable = [];
|
|
|
|
// The remaining array creates all widgets which have to be completely
|
|
// cloned when the widget tree is created
|
|
var remaining = [];
|
|
|
|
// Iterate over the widgets
|
|
for (var i = 0; i < _varAttrs.length; i++)
|
|
{
|
|
var widget = _varAttrs[i].widget;
|
|
|
|
// Check whether the widget parents are not allready in the "remaining"
|
|
// slot - if this is the case do not include the widget at all.
|
|
var insertWidget = true;
|
|
var checkWidget = function (_widget) {
|
|
if (_widget.parent != null)
|
|
{
|
|
for (var i = 0; i < remaining.length; i++)
|
|
{
|
|
if (remaining[i].widget == _widget.parent)
|
|
{
|
|
insertWidget = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
checkWidget(_widget.parent);
|
|
}
|
|
};
|
|
checkWidget(widget);
|
|
|
|
// Handle the next widget if this one should not be included.
|
|
if (!insertWidget)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Check whether the widget implements the et2_IDetachedDOM interface
|
|
var isDetachable = false;
|
|
if (widget.implements(et2_IDetachedDOM))
|
|
{
|
|
// Get all attributes the widgets supports to be set in the
|
|
// "detached" mode
|
|
var supportedAttrs = [];
|
|
widget.getDetachedAttributes(supportedAttrs);
|
|
supportedAttrs.push("id");
|
|
isDetachable = true;
|
|
|
|
for (var j = 0; j < _varAttrs[i].data.length/* && isDetachable*/; j++)
|
|
{
|
|
var data = _varAttrs[i].data[j];
|
|
|
|
var supportsAttr = supportedAttrs.indexOf(data.attribute) != -1;
|
|
|
|
if (!supportsAttr)
|
|
{
|
|
et2_debug("warn", "et2_IDetachedDOM widget " +
|
|
widget._type + " does not support " + data.attribute);
|
|
}
|
|
|
|
isDetachable &= supportsAttr;
|
|
}
|
|
}
|
|
|
|
// Insert the widget into the correct slot
|
|
if (isDetachable)
|
|
{
|
|
detachable.push(_varAttrs[i]);
|
|
}
|
|
else
|
|
{
|
|
remaining.push(_varAttrs[i]);
|
|
}
|
|
}
|
|
|
|
return {
|
|
"detachable": detachable,
|
|
"remaining": remaining
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Removes to DOM code for all widgets in the "remaining" slot
|
|
*/
|
|
_stripTemplateRow: function(_rowTemplate) {
|
|
_rowTemplate.placeholders = [];
|
|
|
|
for (var i = 0; i < _rowTemplate.seperated.remaining.length; i++)
|
|
{
|
|
var entry = _rowTemplate.seperated.remaining[i];
|
|
|
|
// Issue a warning - widgets which do not implement et2_IDOMNode
|
|
// are very slow
|
|
et2_debug("warn", "Non-clonable widget '"+ entry.widget._type + "' in dataview row - this " +
|
|
"might be slow", entry);
|
|
|
|
// Set the placeholder for the entry to null
|
|
entry.placeholder = null;
|
|
|
|
// Get the outer DOM-Node of the widget
|
|
if (entry.widget.implements(et2_IDOMNode))
|
|
{
|
|
var node = entry.widget.getDOMNode(entry.widget);
|
|
|
|
if (node && node.parentNode)
|
|
{
|
|
// Get the parent node and replace the node with a placeholder
|
|
entry.placeholder = document.createElement("span");
|
|
node.parentNode.replaceChild(entry.placeholder, node);
|
|
_rowTemplate.placeholders.push({
|
|
"widget": entry.widget,
|
|
"func": this._compileDOMAccessFunc(_rowTemplate.row,
|
|
entry.placeholder)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
_nodeIndex: function(_node) {
|
|
if(_node.parentNode == null)
|
|
{
|
|
return 0;
|
|
}
|
|
for (var i = 0; i < _node.parentNode.childNodes.length; i++)
|
|
{
|
|
if (_node.parentNode.childNodes[i] == _node)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
},
|
|
|
|
/**
|
|
* Returns a function which does a relative access on the given DOM-Node
|
|
*/
|
|
_compileDOMAccessFunc: function(_root, _target) {
|
|
function recordPath(_root, _target, _path)
|
|
{
|
|
if (typeof _path == "undefined")
|
|
{
|
|
_path = [];
|
|
}
|
|
|
|
if (_root != _target && _target)
|
|
{
|
|
// Get the index of _target in its parent node
|
|
var idx = this._nodeIndex(_target);
|
|
if (idx >= 0)
|
|
{
|
|
// Add the access selector
|
|
_path.unshift("childNodes[" + idx + "]");
|
|
|
|
// Record the remaining path
|
|
return recordPath.call(this, _root, _target.parentNode, _path);
|
|
}
|
|
|
|
throw("Internal error while compiling DOM access function.");
|
|
}
|
|
else
|
|
{
|
|
_path.unshift("_node");
|
|
return "return " + _path.join(".") + ";";
|
|
}
|
|
}
|
|
|
|
return new Function("_node", recordPath.call(this, _root, _target));
|
|
},
|
|
|
|
/**
|
|
* Builds relative paths to the DOM-Nodes and compiles fast-access functions
|
|
*/
|
|
_buildNodeAccessFuncs: function(_rowTemplate) {
|
|
for (var i = 0; i < _rowTemplate.seperated.detachable.length; i++)
|
|
{
|
|
var entry = _rowTemplate.seperated.detachable[i];
|
|
|
|
// Get all needed nodes from the widget
|
|
var nodes = entry.widget.getDetachedNodes();
|
|
var nodeFuncs = entry.nodeFuncs = new Array(nodes.length);
|
|
|
|
// Record the path to each DOM-Node
|
|
for (var j = 0; j < nodes.length; j++)
|
|
{
|
|
nodeFuncs[j] = this._compileDOMAccessFunc(_rowTemplate.row,
|
|
nodes[j]);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Creates the data row prototype
|
|
*/
|
|
setDataRowTemplate: function(_widgets, _rootWidget) {
|
|
// Copy the root widget
|
|
this._rootWidget = _rootWidget;
|
|
|
|
// Create the base row
|
|
var row = this.getPrototype("default");
|
|
|
|
// Copy the row template
|
|
var rowTemplate = {
|
|
"row": row[0],
|
|
"widgets": _widgets,
|
|
"root": _rootWidget,
|
|
"seperated": null,
|
|
"mgrs": _rootWidget.getArrayMgrs()
|
|
};
|
|
|
|
// Create the row widget and insert the given widgets into the row
|
|
var rowWidget = new et2_dataview_rowWidget(rowTemplate.mgrs, row[0]);
|
|
rowWidget._parent = _rootWidget;
|
|
rowWidget.createWidgets(_widgets);
|
|
|
|
// Get the set containing all variable attributes
|
|
var variableAttributes = this._getVariableAttributeSet(rowWidget);
|
|
|
|
// Filter out all widgets which do not implement the et2_IDetachedDOM
|
|
// interface or do not support all attributes listed in the et2_IDetachedDOM
|
|
// interface. A warning is issued for all those widgets as they heavily
|
|
// degrade the performance of the dataview
|
|
var seperated = rowTemplate.seperated =
|
|
this._seperateWidgets(variableAttributes);
|
|
|
|
// Remove all DOM-Nodes of all widgets inside the "remaining" slot from
|
|
// the row-template, then build the access functions for the detachable
|
|
// widgets
|
|
this._stripTemplateRow(rowTemplate);
|
|
this._buildNodeAccessFuncs(rowTemplate);
|
|
|
|
this._prototypes["dataRow"] = row;
|
|
this._template = rowTemplate;
|
|
},
|
|
|
|
getDataRow: function(_data, _row, _idx) {
|
|
|
|
// Create array managers with the given data merged in
|
|
var mgrs = et2_arrayMgrs_expand(rowWidget, this._template.mgrs,
|
|
_data, _idx);
|
|
|
|
// Insert the widgets into the row which do not provide the functions
|
|
// to set the _data directly
|
|
var rowWidget = null;
|
|
if (this._template.seperated.remaining.length > 0)
|
|
{
|
|
// Transform the variable attributes
|
|
for (var i = 0; i < this._template.seperated.remaining.length; i++)
|
|
{
|
|
var entry = this._template.seperated.remaining[i];
|
|
|
|
for (var j = 0; j < entry.data.length; j++)
|
|
{
|
|
var set = entry.data[j];
|
|
entry.widget.options[set.attribute] = mgrs["content"].expandName(set.expression);
|
|
}
|
|
}
|
|
|
|
// Create the row widget
|
|
var rowWidget = new et2_dataview_rowTemplateWidget(this._rootWidget,
|
|
_row[0]);
|
|
|
|
// Let the row widget create the widgets
|
|
rowWidget.createWidgets(mgrs, this._template.placeholders);
|
|
}
|
|
|
|
// Update the content of all other widgets
|
|
for (var i = 0; i < this._template.seperated.detachable.length; i++)
|
|
{
|
|
var entry = this._template.seperated.detachable[i];
|
|
|
|
// Parse the attribute expressions
|
|
var data = {};
|
|
for (var j = 0; j < entry.data.length; j++)
|
|
{
|
|
var set = entry.data[j];
|
|
data[set.attribute] = mgrs["content"].expandName(set.expression);
|
|
}
|
|
|
|
// Retrieve all DOM-Nodes
|
|
var nodes = new Array(entry.nodeFuncs.length);
|
|
for (var j = 0; j < nodes.length; j++)
|
|
{
|
|
// Use the previously compiled node function to get the node
|
|
// from the entry
|
|
nodes[j] = entry.nodeFuncs[j](_row[0]);
|
|
}
|
|
|
|
// Set the array managers first
|
|
entry.widget._mgrs = mgrs;
|
|
if (typeof data.id != "undefined")
|
|
{
|
|
entry.widget.id = data.id;
|
|
}
|
|
|
|
// Adjust data for that row
|
|
entry.widget.transformAttributes.call(entry.widget,data);
|
|
|
|
// Call the setDetachedAttributes function
|
|
entry.widget.setDetachedAttributes(nodes, data);
|
|
}
|
|
|
|
return rowWidget;
|
|
},
|
|
|
|
/* ---- PRIVATE FUNCTIONS ---- */
|
|
|
|
_createFullRowPrototype: function() {
|
|
var tr = $j(document.createElement("tr"));
|
|
var td = $j(document.createElement("td"))
|
|
.attr("span", this._columnIds.length)
|
|
.appendTo(tr);
|
|
var div = $j(document.createElement("div"))
|
|
.addClass(this._outerId + "_div_fullRow")
|
|
.appendTo(td);
|
|
|
|
this._prototypes["fullRow"] = tr;
|
|
},
|
|
|
|
_createDefaultPrototype: function() {
|
|
var tr = $j(document.createElement("tr"));
|
|
|
|
// Append a td for each column
|
|
for (var i = 0; i < this._columnIds.length; i++)
|
|
{
|
|
var td = $j(document.createElement("td"))
|
|
.addClass(this._outerId + "_td_" + this._columnIds[i])
|
|
.appendTo(tr);
|
|
var div = $j(document.createElement("div"))
|
|
.addClass(this._outerId + "_div_" + this._columnIds[i])
|
|
.addClass("innerContainer")
|
|
.appendTo(td);
|
|
}
|
|
|
|
this._prototypes["default"] = tr;
|
|
},
|
|
|
|
_createEmptyPrototype: function() {
|
|
this._prototypes["empty"] = $j(document.createElement("tr"));
|
|
}
|
|
|
|
});
|
|
|
|
var et2_dataview_rowWidget = et2_widget.extend(et2_IDOMNode, {
|
|
|
|
init: function(_mgrs, _row) {
|
|
// Call the parent constructor with some dummy attributes
|
|
this._super(null, {"id": "", "type": "rowWidget"});
|
|
|
|
// Initialize some variables
|
|
this._widgets = [];
|
|
|
|
// Copy the given DOM node and the content arrays
|
|
this._mgrs = _mgrs;
|
|
this._row = _row;
|
|
},
|
|
|
|
/**
|
|
* Copies the given array manager and clones the given widgets and inserts
|
|
* them into the row which has been passed in the constructor.
|
|
*/
|
|
createWidgets: function(_widgets) {
|
|
// Clone the given the widgets with this element as parent
|
|
this._widgets = new Array(_widgets.length);
|
|
for (var i = 0; i < _widgets.length; i++)
|
|
{
|
|
this._widgets[i] = _widgets[i].clone(this);
|
|
this._widgets[i].loadingFinished();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns the column node for the given sender
|
|
*/
|
|
getDOMNode: function(_sender) {
|
|
for (var i = 0; i < this._widgets.length; i++)
|
|
{
|
|
if (this._widgets[i] == _sender)
|
|
{
|
|
return this._row.childNodes[i].childNodes[0]; // Return the i-th td tag
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
});
|
|
|
|
var et2_dataview_rowTemplateWidget = et2_widget.extend(et2_IDOMNode, {
|
|
|
|
init: function(_root, _row) {
|
|
// Call the parent constructor with some dummy attributes
|
|
this._super(null, {"id": "", "type": "rowTemplateWidget"});
|
|
|
|
this._root = _root;
|
|
this._mgrs = {};
|
|
this._row = _row;
|
|
|
|
// Set parent to root widget, so sub-widget calls still work
|
|
this._parent = _root;
|
|
|
|
// Clone the widgets inside the placeholders array
|
|
this._widgets = [];
|
|
},
|
|
|
|
createWidgets: function(_mgrs, _widgets) {
|
|
// Set the array managers - don't use setArrayMgrs here as this creates
|
|
// an unnecessary copy of the object
|
|
this._mgrs = _mgrs;
|
|
|
|
this._widgets = new Array(_widgets.length);
|
|
for (var i = 0; i < _widgets.length; i++)
|
|
{
|
|
this._row.childNodes[0].childNodes[0];
|
|
|
|
this._widgets[i] = {
|
|
"widget": _widgets[i].widget.clone(this),
|
|
"node": _widgets[i].func(this._row)
|
|
};
|
|
this._widgets[i].widget.loadingFinished();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns the column node for the given sender
|
|
*/
|
|
getDOMNode: function(_sender) {
|
|
|
|
for (var i = 0; i < this._widgets.length; i++)
|
|
{
|
|
if (this._widgets[i].widget == _sender)
|
|
{
|
|
return this._widgets[i].node;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
});
|
|
|
|
|