mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-16 13:03:12 +01:00
812242a224
-- Don't know what's happening with events in the grid. Something happens if you double click, but it's not the button's click method.
552 lines
14 KiB
JavaScript
552 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.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(_mgrs, _row) {
|
|
// Call the parent constructor with some dummy attributes
|
|
this._super(null, {"id": "", "type": "rowTemplateWidget"});
|
|
|
|
// Copy the managers - do not use "setArrayMgrs" here
|
|
this._mgrs = _mgrs;
|
|
this._row = _row;
|
|
|
|
// 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;
|
|
}
|
|
|
|
});
|
|
|
|
|