Implemented system for fast cloning of rows in the dataview - widgets which want to support this method have to implement et2_IDetachedDOM

This commit is contained in:
Andreas Stöckel 2011-09-06 16:50:38 +00:00
parent a363919485
commit 8d5b990826
9 changed files with 473 additions and 99 deletions

View File

@ -43,7 +43,7 @@ var et2_arrayMgr = Class.extend({
this.perspectiveData = {
"owner": null,
"key": null,
"row": 0
"row": null
}
},
@ -161,7 +161,12 @@ var et2_arrayMgr = Class.extend({
var pos_var = _ident.indexOf('$');
if (pos_var >= 0)
{
// TODO
console.log("blub", _ident, this.perspectiveData);
if (this.perspectiveData.row !== null)
{
_ident.replace(/\$\{row\}/, this.perspectiveData.row);
console.log(_ident);
}
}
if (is_index_in_content)
@ -266,6 +271,7 @@ var et2_readonlysArrayMgr = et2_arrayMgr.extend({
// If the attribute is set, return that
if (typeof _attr != "undefined" && _attr !== null)
{
console.log(_attr, et2_evalBool(_attr));
return et2_evalBool(_attr);
}
@ -295,8 +301,11 @@ function et2_arrayMgrs_expand(_owner, _mgrs, _data, _row)
if (typeof _mgrs[key] != "undefined")
{
// Open a perspective for the given data row
var rowData = {};
rowData[_row] = _data[key];
result[key] = _mgrs[key].openPerspective(_owner,
_data[key], _row);
_data[key], rowData);
}
}

View File

@ -529,6 +529,12 @@ function et2_activateLinks(_content)
*/
function et2_insertLinkText(_text, _node, _target)
{
// Clear the node
for (var i = _node.childNodes.length - 1; i >= 0; i--)
{
_node.removeChild(_node.childNodes[i]);
}
for (var i = 0; i < _text.length; i++)
{
var s = _text[i];

View File

@ -85,4 +85,38 @@ var et2_ISubmitListener = new Interface({
submit: function(_values) {}
});
/**
* Interface all widgets must support which can operate on given DOM-Nodes. This
* is used in grid lists, when only a single row gets really stored in the widget
* tree and this instance has to work on multiple copies of the DOM-Tree elements.
*/
var et2_IDetachedDOM = new Interface({
/**
* Returns a list of attributes which can be set when working in the
* "detached" mode. The result is stored in the _attrs array which is provided
* by the calling code.
*/
getDetachedAttributes: function(_attrs) {},
/**
* Returns an array of DOM nodes. The (relativly) same DOM-Nodes have to be
* passed to the "setDetachedAttributes" function in the same order.
*/
getDetachedNodes: function() {},
/**
* Sets the given associative attribute->value array and applies the
* attributes to the given DOM-Node.
*
* @param _nodes is an array of nodes which has to be in the same order as
* the nodes returned by "getDetachedNodes"
* @param _values is an associative array which contains a subset of attributes
* returned by the "getDetachedAttributes" function and sets them to the
* given values.
*/
setDetachedAttributes: function(_nodes, _values) {}
});

View File

@ -50,7 +50,8 @@ var et2_dataview_dataProvider = Class.extend({
// All data rows are updated independently of all others - this allows
// user input between generation of the widgets.
window.setTimeout(function() {_dataRow.updateData({"readonlys": {"__ALL__": true}});}, 0);
//window.setTimeout(function() {_dataRow.updateData({"readonlys": {"__ALL__": true}});}, 0);
_dataRow.updateData({});
},
unregisterDataRow: function(_dataRow) {

View File

@ -28,7 +28,7 @@ var ET2_GRID_VIEW_EXT = 25;
/**
* Determines the timeout after which the scroll-event is processed.
*/
var ET2_GRID_SCROLL_TIMEOUT = 100;
var ET2_GRID_SCROLL_TIMEOUT = 25;
var partitionTree = null;

View File

@ -28,7 +28,7 @@ var et2_dataview_row = et2_dataview_container.extend(et2_dataview_IDataRow, {
this.hasAvgHeight = false;
// Get the default row object and scale the row to the average height
this.tr = this.rowProvider.getPrototype("default");
this.tr = this.rowProvider.getPrototype("dataRow");
// Append the row
this.appendNode(this.tr);

View File

@ -30,7 +30,7 @@ var et2_dataview_rowProvider = Class.extend({
this._columnIds = _columnIds;
this._prototypes = {};
this._dataRowTemplate = null;
this._template = null;
this._mgrs = null;
this._rootWidget = null;
@ -62,21 +62,330 @@ var et2_dataview_rowProvider = Class.extend({
return this._prototypes[_name].clone();
},
setDataRowTemplate: function(_template, _rootWidget) {
this._dataRowTemplate = _template;
/**
* 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": []
};
// Include the "value" attribute if the widget is derrived from
// et2_valueWidget
if (_widget instanceof et2_valueWidget)
{
hasAttr = true;
widgetData.data.push({
"attribute": "value",
"expression": "@${row}"
});
}
// 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);
isDetachable = true;
for (var j = 0; j < _varAttrs[i].data.length && isDetachable; j++)
{
var data = _varAttrs[i].data[j];
isDetachable &= supportedAttrs.indexOf(data.attribute) != -1;
}
}
// 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 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.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) {
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)
{
// 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[i] = 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
};
// Create the row widget and insert the given widgets into the row
var rowWidget = new et2_dataview_rowWidget(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 widgets
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 the row widget
var rowWidget = new et2_dataview_rowWidget(this._rootWidget, _row[0]);
// Create array managers with the given data merged in
var mgrs = et2_arrayMgrs_expand(rowWidget, this._rootWidget.getArrayMgrs(),
_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)
{
// 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._dataRowTemplate);
// Create array managers with the given data merged in
var mgrs = et2_arrayMgrs_expand(rowWidget, this._rootWidget.getArrayMgrs(),
_data, _idx);
// 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[i];
data[set.attribute] = set.expression + " for " + _idx; // TODO: Parsing of the 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]);
}
// Call the setDetachedAttributes function
entry.widget.setDetachedAttributes(nodes, data);
}
return rowWidget;
},
@ -121,9 +430,9 @@ var et2_dataview_rowProvider = Class.extend({
var et2_dataview_rowWidget = et2_widget.extend(et2_IDOMNode, {
init: function(_parent, _row) {
init: function(_row) {
// Call the parent constructor with some dummy attributes
this._super(_parent, {"id": "", "type": "rowWidget"});
this._super(null, {"id": "", "type": "rowWidget"});
// Initialize some variables
this._widgets = [];
@ -136,11 +445,7 @@ var et2_dataview_rowWidget = et2_widget.extend(et2_IDOMNode, {
* 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(_mgrs, _widgets) {
// Set the array managers - don't use setArrayMgrs here as this creates
// an unnecessary copy of the object
this._mgrs = _mgrs;
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++)
@ -154,19 +459,65 @@ var et2_dataview_rowWidget = et2_widget.extend(et2_IDOMNode, {
* Returns the column node for the given sender
*/
getDOMNode: function(_sender) {
if (typeof _sender == "undefined" || !_sender)
{
return this.row;
}
for (var i = 0; i < this._widgets.length; i++)
{
if (this._widgets[i] == _sender)
{
return this._row.childNodes[i]; // Return the i-th td tag
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;
}
});

View File

@ -20,7 +20,7 @@
/**
* Class which implements the "description" XET-Tag
*/
var et2_description = et2_baseWidget.extend({
var et2_description = et2_baseWidget.extend([et2_IDetachedDOM], {
attributes: {
"value": {
@ -94,26 +94,27 @@ var et2_description = et2_baseWidget.extend({
this.span.attr("for", this.options["for"]);
}
et2_insertLinkText(this._parseText(), this.span[0], this.options.extra_link_target);
et2_insertLinkText(this._parseText(this.options.value), this.span[0],
this.options.extra_link_target);
this.setDOMNode(this.span[0]);
},
_parseText: function() {
_parseText: function(_value) {
if (this.options.href)
{
return [{
"href": this.options.href,
"text": this.options.value
"text": _value
}];
}
else if (this.options.activate_links)
{
return et2_activateLinks(this.options.value);
return et2_activateLinks(_value);
}
else
{
return [this.options.value];
return [_value];
}
},
@ -122,8 +123,30 @@ var et2_description = et2_baseWidget.extend({
this.span.toggleClass("et2_bold", _value.indexOf("b") >= 0);
this.span.toggleClass("et2_italic", _value.indexOf("i") >= 0);
}
},
/**
* Code for implementing et2_IDetachedDOM
*/
getDetachedAttributes: function(_attrs)
{
_attrs.push("value");
},
getDetachedNodes: function()
{
return [this.span[0]];
},
setDetachedAttributes: function(_nodes, _values)
{
if (typeof _values["value"] != "undefined")
{
et2_insertLinkText(this._parseText(_values["value"]), _nodes[0],
this.options.extra_link_target);
}
}
});
et2_register_widget(et2_description, ["description", "label"]);

View File

@ -63,69 +63,19 @@
<nextmatch-sortheader label="last changed" id="info_datemodified" options="DESC"/>
</row>
<row class="$row_cont[info_cat] $row_cont[class]" valign="top">
<hbox align="center" options="5">
<image label="$row_cont[info_type]" src="${row}[info_type]"/>
<button statustext="Change the status of an entry, eg. close it" label="$row_cont[info_status_label]" id="edit_status[$row_cont[info_id]]" onclick="window.open(egw::link('/index.php','menuaction=infolog.infolog_ui.edit&amp;info_id=$row_cont[info_id]'),'_blank','dependent=yes,width=750,height=600,scrollbars=yes,status=yes'); return false;" image="$row_cont[info_status_label]" ro_image="$row_cont[info_status_label]"/>
<button statustext="Change the status of an entry, eg. close it" label="$row_cont[info_percent]" id="edit_percent[$row_cont[info_id]]" onclick="window.open(egw::link('/index.php','menuaction=infolog.infolog_ui.edit&amp;info_id=$row_cont[info_id]'),'_blank','dependent=yes,width=750,height=600,scrollbars=yes,status=yes'); return false;" image="$row_cont[info_percent]"/>
<image label="$row_cont[info_percent2]" src="{$row}[info_percent2]" onclick="window.open(egw::link('/index.php','menuaction=infolog.infolog_ui.edit&amp;info_id=$row_cont[info_id]'),'_blank','dependent=yes,width=750,height=600,scrollbars=yes,status=yes'); return false;"/>
</hbox>
<vbox options="0,0" class="fullWidth">
<link label="%s $row_cont[info_addr]" id="${row}[info_link]" options="b"/>
<hbox options="0,0">
<description id="${row}[info_subject]" no_lang="1" class="$row_cont[sub_class]"/>
<description align="right" id="{$row}[info_number]" no_lang="1" class="infoId"/>
</hbox>
<box class="infoDes">
<description id="${row}[info_des]" no_lang="1" options=",,1"/>
</box>
<link-string id="${row}[filelinks]"/>
</vbox>
<customfields-list id="$row" class="customfields"/>
<menulist>
<menupopup type="select-cat" id="${row}[info_cat]" readonly="true"/>
</menulist>
<vbox cols="1" rows="3" options="0,0,1">
<date-time id="${row}[info_startdate]" readonly="true" options=",8" class="fixedHeight"/>
<date id="${row}[info_enddate]" readonly="true" class="$row_cont[end_class] fixedHeight"/>
<date-time id="${row}[info_datecompleted]" readonly="true" class="fixedHeight"/>
</vbox>
<vbox cols="1" rows="3" options="0,0">
<hbox readonly="true">
<hbox readonly="true" options="1,0">
<date-duration id="${row}[info_used_time]" readonly="true" options="@duration_format"/>
<date-duration id="${row}[info_sum_timesheets]" readonly="true" options="@duration_format" class="timesheet"/>
</hbox>
<description/>
</hbox>
<date-duration id="${row}[info_planned_time]" readonly="true" options="@duration_format" span="all" class="planned"/>
</vbox>
<vbox cols="1" rows="3" options="0,0">
<hbox id="r_used_time" options="1,0">
<image label="Times" src="timesheet"/>
<date-duration id="${row}[info_used_time]" readonly="true" options="@duration_format"/>
<date-duration id="${row}[info_sum_timesheets]" readonly="true" options="@duration_format" class="timesheet"/>
</hbox>
<hbox id="planified" options="1,0">
<image label="planned time" src="k_alarm.png"/>
<date-duration id="${row}[info_planned_time]" readonly="true" options="@duration_format" span="all" class="planned"/>
</hbox>
<hbox id="replanified" options="1,0">
<image label="Re-planned time" src="agt_reload.png"/>
<date-duration id="${row}[info_replanned_time]" readonly="true" options="@duration_format" span="all" class="replanned"/>
</hbox>
</vbox>
<vbox options="0,0">
<menulist>
<menupopup type="select-account" id="${row}[info_owner]" readonly="true"/>
</menulist>
<listbox type="select-account" id="${row}[info_responsible]" readonly="true" rows="5"/>
</vbox>
<vbox options="0" orient="0">
<date-time id="${row}[info_datemodified]" readonly="true"/>
<menulist>
<menupopup type="select-account" id="${row}[info_modifier]" readonly="true"/>
</menulist>
<vbox>
<description value="Dynamic description tag:" />
<description value="${row}" />
<button label="This is a button" />
</vbox>
<description value="test" />
<description value="test" />
<description value="test" />
<description value="test" />
<description value="test" />
<description value="test" />
<description value="test" />
<description value="test2" />
</row>
</rows>
</grid>