diff --git a/etemplate/js/et2_dataview_interfaces.js b/etemplate/js/et2_dataview_interfaces.js index 57f9a6b067..f3084f3f83 100644 --- a/etemplate/js/et2_dataview_interfaces.js +++ b/etemplate/js/et2_dataview_interfaces.js @@ -10,14 +10,26 @@ * @version $Id$ */ +"use strict" + +/*egw:uses + et2_core_inheritance; +*/ + var et2_dataview_IInvalidatable = new Interface({ - invalidate: function(); + invalidate: function() {} }); var et2_dataview_IDataRow = new Interface({ - updateData: function(_data); + updateData: function(_data) {} + +}); + +var et2_dataview_IViewRange = new Interface({ + + setViewRange: function(_range) {} }); diff --git a/etemplate/js/et2_dataview_model_columns.js b/etemplate/js/et2_dataview_model_columns.js index 89203a40ba..b4e8b4c178 100755 --- a/etemplate/js/et2_dataview_model_columns.js +++ b/etemplate/js/et2_dataview_model_columns.js @@ -1,14 +1,17 @@ /** - * eGroupWare egw_action framework - egw action framework + * eGroupWare eTemplate2 - Class which contains a the columns model * - * @link http://www.egroupware.org - * @author Andreas Stöckel - * @copyright 2011 by Andreas Stöckel * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @package egw_action + * @package etemplate + * @subpackage dataview + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright Stylite 2011 * @version $Id$ */ +"use strict" + /*egw:uses et2_inheritance; */ diff --git a/etemplate/js/et2_dataview_model_dataProvider.js b/etemplate/js/et2_dataview_model_dataProvider.js new file mode 100644 index 0000000000..c91afe6124 --- /dev/null +++ b/etemplate/js/et2_dataview_model_dataProvider.js @@ -0,0 +1,53 @@ +/** + * eGroupWare eTemplate2 - Class which contains a the data model + * + * @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$ + */ + +"use strict" + +/*egw:uses + et2_inheritance; + et2_dataview_interfaces; +*/ + +var et2_dataview_dataProvider = Class.extend({ + + getCount: function() { + return 1000; + }, + + registerDataRow: function(_idx, _dataRow) { + var row = { + "type": "dataRow", + "data": { + "ts_title": "Row " + _idx + } + }; + + // Get a random value which is used to simulate network latency and time + // it needs to load the data. + var rnd = Math.round(Math.random() * 1000); + + if (rnd < 200) + { + _dataRow.updateData(row); + } + + window.setTimeout(function() {_dataRow.updateData(row); }, + Math.round(rnd / 2)); + }, + + unregisterDataRow: function(_dataRow) { + // + } + +}); + + diff --git a/etemplate/js/et2_dataview_view_container.js b/etemplate/js/et2_dataview_view_container.js index 9b4b4df501..da5b7c8c2a 100644 --- a/etemplate/js/et2_dataview_view_container.js +++ b/etemplate/js/et2_dataview_view_container.js @@ -13,32 +13,189 @@ "use strict" /*egw:uses + jquery.jquery; et2_dataview_interfaces; */ -var et2_dataview_container = Class.extend({ +var et2_dataview_container = Class.extend(et2_dataview_IInvalidatable, { - init: function(_data, _invalidationElem) { - this._dataProvider = _data; + /** + * Initializes the container object. + * + * @param _dataProvider is the data provider for the element + * @param _rowProvider is the "rowProvider" of the element + * @param _invalidationElem is the element of which the "invalidate" method + * will be called if the height of the elements changes. It has to + * implement the et2_dataview_IInvalidatable interface. + */ + init: function(_dataProvider, _rowProvider, _invalidationElem) { + this.dataProvider = _dataProvider; + this.rowProvider = _rowProvider; this._invalidationElem = _invalidationElem; - this._node = null; + this._nodes = []; + this._inTree = false; + this._attachData = {"node": null, "prepend": false}; }, - setJNode: function(_node) { - // Replace the old node with the new one - if (this._node[0].parent) + destroy: function() { + // Remove the nodes from the tree + this.removeFromTree(); + }, + + /** + * Setter function which can be used to update the invalidation element. + * + * @param _invalidationElem is the element of which the "invalidate" method + * will be called if the height of the elements changes. It has to + * implement the et2_dataview_IInvalidatable interface. + */ + setInvalidationElement: function(_invalidationElem) { + this._invalidationElem = _invalidationElem; + }, + + /** + * Inserts all container nodes into the DOM tree after the given element + */ + insertIntoTree: function(_afterNode, _prepend) { + if (!this._inTree && _afterNode != null) { - this._node.replaceWith(_node); + for (var i = 0; i < this._nodes.length; i++) + { + if (i == 0) + { + if (_prepend) + { + _afterNode.prepend(this._nodes[i]); + } + else + { + _afterNode.after(this._nodes[i]); + } + } + else + { + // Insert all following nodes after the previous node + this._nodes[i - 1].after(this._nodes[i]); + } + } + + // Save the "attachData" + this._inTree = true; + this._attachData = {"node": _afterNode, "prepend": _prepend}; + + this.invalidate(); + } + }, + + /** + * Removes all container nodes from the tree. + */ + removeFromTree: function() { + if (this._inTree) + { + // Call the jQuery remove function to remove all nodes from the tree + // again. + for (var i = 0; i < this._nodes.length; i++) + { + this._nodes[i].remove(); + } + + // Reset the "attachData" + this._inTree = false; + this._attachData = {"node": null, "prepend": false}; + } + }, + + /** + * Appends a jQuery node to the container + */ + appendNode: function(_node) { + // Add the given node to the "nodes" array + this._nodes.push(_node); + + // If the container is already in the tree, attach the given node to the + // tree. + if (this._inTree) + { + if (this._nodes.length == 1) + { + if (_prepend) + { + this._attachData.node.prepend(this._nodes[0]); + } + else + { + this._attachData.node.after(this._nodes[0]); + } + } + else + { + this._nodes[_nodes.length - 2].after(_node); + } + + this.invalidate(); + } + }, + + /** + * Removes a jQuery node from the container + */ + removeNode: function(_node) { + // Get the index of the node in the nodes array + var idx = this._nodes.indexOf(_node); + + if (idx >= 0) + { + // Remove the node if the container is currently attached + if (this._inTree) + { + _node.remove(); + } + + // Remove the node from the nodes array + this._nodes.splice(idx, 1); + } + }, + + /** + * Returns the last node of the container - new nodes have to be appended + * after it. + */ + getLastNode: function() { + + if (this._nodes.length > 0) + { + return this._nodes[this._nodes.length - 1]; } - this._node = _node; + return null; }, - getJNode: function() { - return this._node; + /** + * Returns the accumulated height of all container nodes. Only visible nodes + * (without "display: none") are taken into account. + */ + getHeight: function() { + var height = 0; + + if (this._inTree) + { + // Increment the height value for each visible container node + var self = this; + $j(this._nodes, ":visible").each(function() { + height += self._nodeHeight(this); + }); + } + + console.log(height); + + return height; }, + /** + * Calls the "invalidate" function of the connected invalidation element. + */ invalidate: function() { this._invalidationElem.invalidate(); } @@ -54,41 +211,33 @@ var et2_dataview_container = Class.extend({ */ if ($j.browser.mozilla) { - et2_dataview_container.prototype.getHeight = function() + et2_dataview_container.prototype._nodeHeight = function(_node) { - if (this.node) + var height = 0; + // Firefox sometimes provides fractional pixel values - we are + // forced to use those - we can obtain the fractional pixel height + // by using the window.getComputedStyle function + var compStyle = getComputedStyle(_node, null); + if (compStyle) { - // Firefox sometimes provides fractional pixel values - we are - // forced to use those - we can obtain the fractional pixel height - // by using the window.getComputedStyle function - var compStyle = getComputedStyle(this._node, null); - if (compStyle) + var styleHeightStr = compStyle.getPropertyValue("height"); + height = parseFloat(styleHeightStr.substr(0, + styleHeightStr.length - 2)); + + if (isNaN(height) || height < 1) { - var styleHeightStr = compStyle.getPropertyValue("height"); - var height = parseFloat(styleHeightStr.substr(0, styleHeightStr.length - 2)); - - if (isNaN(height) || height < 1) - { - height = false; - } + height = 0; } - - return height; } - return 0; + return height; } } else { - et2_dataview_container.prototype.getHeight = function() + et2_dataview_container.prototype._nodeHeight = function(_node) { - if (this.node) - { - return this._node.offsetHeight; - } - - return 0; + return $j(_node).outerHeight(true); } } diff --git a/etemplate/js/et2_dataview_view_grid.js b/etemplate/js/et2_dataview_view_grid.js index 15eb817ab8..6ed1e1a7ba 100644 --- a/etemplate/js/et2_dataview_view_grid.js +++ b/etemplate/js/et2_dataview_view_grid.js @@ -13,24 +13,236 @@ "use strict"; /*egw:uses + jquery.jquery; + et2_core_common; + et2_dataview_interfaces; et2_dataview_view_row; et2_dataview_view_partitionTree; */ -var et2_dataview_grid = Class.extend({ +var et2_dataview_grid = Class.extend(et2_dataview_IViewRange, { + + /** + * Creates the grid. + * + * @param _parent is the parent grid class - if null, this means that this + * is the outer grid which manages the scrollarea. If not null, all other + * parameters are ignored and copied from the given grid instance. + * @param _outerId is the id of the grid container it uses for the css + * classes. + * @param _columnIds is the id of the individual columns used for the css + * classes. + * @param _avgHeight is the starting average height of the column rows. + */ + init: function(_parent, _outerId, _columnIds, _dataProvider, _avgHeight) { + + // If the parent is given, copy all other parameters from it + if (_parent != null) + { + this._outerId = _parent._outerId; + this._columnIds = _parent._columnIds; + this._dataProvider = _parent._dataProvider; + this._avgHeight = _parent._partitionTree.getAverageHeight(); + this._rowProvider = _parent._rowProvider; + } + else + { + // Otherwise copy the given parameters + this._outerId = _outerId; + this._columnIds = _columnIds; + this._dataProvider = _dataProvider; + this._avgHeight = _avgHeight; + + // Create the row provider + this._rowProvider = new et2_dataview_rowProvider(_outerId, + _columnIds); + + this._scrollHeight = 0; + this._scrollTimeout = null; + } + + // The "treeChanged" variable is called whenever the viewrange has been + // set - changing the viewrange is the function which causes new elements + // to be generated and thus the partition tree to degenerate + this._treeChanged = false; + + // Count of elements which are buffered at top and bottom of the viewrange + // before they get replaced with placeholders + this._holdCount = 50; + + // The current range holds the currently visible nodes + this._currentRange = et2_range(0, 0); + + // Build the grid outer nodes + this._createNodes(); - init: function(_dataProvider, _count, _avgHeight) { // Create the partition tree object which is used to organize the tree // items. - this._partitionTree = new et2_dataview_partitionTree(_dataProvider, - _count, _avgHeight); + this._partitionTree = new et2_dataview_partitionTree(this._dataProvider, + this._rowProvider, this._avgHeight, this.innerTbody); + + // Setup the "rebuild" timer - it rebuilds the complete partition tree + // if any change has been done to it. Rebuilding the partition tree is + // necessary as the tree structure happens to degenerate. + var self = this; + this._rebuildTimer = window.setInterval(function() { + self._checkTreeRebuild(); + }, 10 * 1000); }, destroy: function() { - // Free the partition tree + // Stop the scroll timeout + if (this._scrollTimeout) + { + window.clearTimeout(this._scrollTimeout); + } + + // Stop the rebuild timer + window.clearInterval(this._rebuildTimer); + + // Free the partition tree and the row provider this._partitionTree.free(); + this._rowProvider.free(); }, - + /** + * The setViewRange function updates the range in which columns are shown. + */ + setViewRange: function(_range) { + this._treeChanged = true; + + // Copy the given range + this._currentRange = et2_bounds(_range.top, _range.bottom); + + // Display all elements in the given range + var nodes = this._partitionTree.getRangeNodes(_range); + + for (var i = 0; i < nodes.length; i++) + { + if (nodes[i].implements(et2_dataview_IViewRange)) + { + nodes[i].setViewRange(_range); + } + } + + // Deactivated the code below for testing purposes + + // Calculate the range of the actually shown elements + /*var displayTop = _range.top; + var displayBottom = _range.bottom; + + if (nodes.length > 0) + { + displayTop = nodes[0].getPosTop(); + displayBottom = nodes[nodes.length - 1].getPosBottom(); + } + + // Hide everything except for _holdCount elements at the top and bottom + // of the viewrange + var reduceHeight = this._partitionTree.getAverageHeight() * this._holdCount; + + if (displayTop > reduceHeight) + { + this._partitionTree.reduce(et2_bounds(0, displayTop - reduceHeight)); + } + + if (displayBottom + reduceHeight < this._partitionTree.getHeight()) + { + this._partitionTree.reduce(et2_bounds(displayBottom + reduceHeight, + this._partitionTree.getHeight())); + }*/ + }, + + /** + * Updates the scrollheight + */ + setScrollHeight: function(_height) { + this._height = _height; + + // Update the height of the outer container + if (this.scrollarea) + { + this.scrollarea.height(_height); + } + + // Update the viewing range + this.setViewRange(et2_range(this._currentRange.top, this._height)); + }, + + /** + * Returns the JQuery outer DOM-Node + */ + getJNode: function() { + return this.outerCell; + }, + + /* ---- PRIVATE FUNCTIONS ---- */ + + /** + * Checks whether the partition tree has to be rebuilt and if yes, does + * that. + */ + _checkTreeRebuild: function() { + if (this._treeChanged) + { + var depth = this._partitionTree.getDepth(); + var count = this._partitionTree.getManagedCount(); + + // Check whether the depth of the tree is very unproportional + // regarding to the count of elements managed in it + if (count < Math.pow(ET2_PARTITION_TREE_WIDTH, depth - 1)) + { + this._partitionTree.rebuild(); + } + + // Reset the "treeChanged" function. + this._treeChanged = false; + } + }, + + /** + * Creates the grid DOM-Nodes + */ + _createNodes: function() { + this.outerCell = $j(document.createElement("td")) + .addClass("frame") + .attr("colspan", this._columnIds.length + (this._parent ? 0 : 1)); + + // Create the scrollarea div if this is the outer grid + this.scrollarea = null; + if (this._parent == null) + { + this.scrollarea = $j(document.createElement("div")) + .addClass("egwGridView_scrollarea") + .scroll(this, function(e) { + + // Clear any older scroll timeout + if (e.data._scrollTimeout) + { + window.clearTimeout(e.data._scrollTimeout); + } + + // Set a new timeout which calls the setViewArea + // function + e.data._scrollTimeout = window.setTimeout(function() { + e.data.setViewRange(et2_range( + e.data.scrollarea.scrollTop(), + e.data._height + )); + }, 25); + }) + .height(this._scrollHeight) + .appendTo(this.outerCell); + } + + // Create the inner table + var table = $j(document.createElement("table")) + .addClass("egwGridView_grid") + .appendTo(this.scrollarea ? this.scrollarea : this.outerCell); + + this.innerTbody = $j(document.createElement("tbody")) + .appendTo(table); + } }); + diff --git a/etemplate/js/et2_dataview_view_gridcontainer.js b/etemplate/js/et2_dataview_view_gridcontainer.js index 3e55f77f51..fcd10a5ab5 100644 --- a/etemplate/js/et2_dataview_view_gridcontainer.js +++ b/etemplate/js/et2_dataview_view_gridcontainer.js @@ -40,16 +40,19 @@ var et2_dataview_gridContainer = Class.extend({ * Constructor for the grid container * @param object _parentNode is the DOM-Node into which the grid view will be inserted */ - init: function(_parentNode) { + init: function(_parentNode, _dataProvider) { - // Copy the parent node parameter + // Copy the arguments this.parentNode = $j(_parentNode); + this.dataProvider = _dataProvider; // Initialize some variables this.columnNodes = []; // Array with the header containers this.columns = []; this.columnMgr = null; + this.grid = null; + this.width = 0; this.height = 0; @@ -69,6 +72,12 @@ var et2_dataview_gridContainer = Class.extend({ // Clear the columns this._clearHeader(); + // Free the grid + if (this.grid) + { + this.grid.free(); + } + // Detatch the outer element this.table.remove(); }, @@ -99,10 +108,13 @@ var et2_dataview_gridContainer = Class.extend({ this.columnMgr = new et2_dataview_columns(_columnData); // Create the stylesheets - this._updateColumns(); + this.updateColumns(); // Build the header row this._buildHeader(); + + // Build the grid + this._buildGrid(); }, /** @@ -115,14 +127,20 @@ var et2_dataview_gridContainer = Class.extend({ // Rebuild the column stylesheets this.columnMgr.setTotalWidth(_w - this.scrollbarWidth); + et2_debug("log", _w - this.scrollbarWidth); this._updateColumns(); } if (this.height != _h) { this.height = _h; + // Set the height of the grid. - // TODO + if (this.grid) + { + this.grid.setScrollHeight(this.height - + this.headTr.outerHeight(true)); + } } }, @@ -236,14 +254,12 @@ var et2_dataview_gridContainer = Class.extend({ vis_col++; this.visibleColumnCount++; + // Update the visibility of the column styleSheet.updateRule("." + col.tdClass, - "display: " + (col.visible ? "table-cell" : "none") + "; " + + "display: table-cell; " + ((vis_col == total_cnt) ? "border-right-width: 0 " : "border-right-width: 1px ") + "!important;"); - styleSheet.updateRule(".egwGridView_outer ." + col.divClass, - "width: " + (col.width - this.headerBorderWidth) + "px;"); - // Ugly browser dependant code - each browser seems to treat the // right (collapsed) border of the row differently var addBorder = 0; @@ -270,10 +286,15 @@ var et2_dataview_gridContainer = Class.extend({ addBorder += 1; } - var width = Math.max(0, (col.width - this.columnBorderWidth - addBorder)); - + // Write the width of the body-columns + var columnWidth = Math.max(0, (col.width - this.columnBorderWidth - addBorder)); styleSheet.updateRule(".egwGridView_grid ." + col.divClass, - "width: " + width + "px;"); + "width: " + columnWidth + "px;"); + + // Write the width of the header columns + var headerWidth = Math.max(0, (col.width - this.headerBorderWidth)); + styleSheet.updateRule(".egwGridView_outer ." + col.divClass, + "width: " + headerWidth + "px;"); totalWidth += col.width; @@ -281,8 +302,7 @@ var et2_dataview_gridContainer = Class.extend({ } else { - styleSheet.updateRule("." + col.tdClass, - "display: " + (col.visible ? "table-cell" : "none") + ";"); + styleSheet.updateRule("." + col.tdClass, "display: none;"); } } @@ -338,6 +358,24 @@ var et2_dataview_gridContainer = Class.extend({ + this.selectCol.width() + 1); }, + /** + * Builds the inner grid class + */ + _buildGrid: function() { + // Create the collection of column ids + var colIds = new Array(this.columns.length); + for (var i = 0; i < this.columns.length; i++) + { + colIds[i] = this.columns[i].id; + } + + // Create the grid class and pass "19" as the starting average row height + this.grid = new et2_dataview_grid(null, this.uniqueId, colIds, + this.dataProvider, 19); + + // Insert the grid into the DOM-Tree + this.containerTr.append(this.grid.getJNode()); + }, /* --- Code for calculating the browser/css depending widths --- */ diff --git a/etemplate/js/et2_dataview_view_partitionTree.js b/etemplate/js/et2_dataview_view_partitionTree.js index 8fb013445b..0dff5b2007 100644 --- a/etemplate/js/et2_dataview_view_partitionTree.js +++ b/etemplate/js/et2_dataview_view_partitionTree.js @@ -49,8 +49,9 @@ var et2_dataview_IPartitionHeight = new Interface({ var et2_dataview_partitionNode = Class.extend([et2_dataview_IPartitionHeight, et2_dataview_IInvalidatable], { - init: function() { + init: function(_root) { + this._root = _root; this._parent = null; this._pidx = 0; @@ -126,12 +127,7 @@ var et2_dataview_partitionNode = Class.extend([et2_dataview_IPartitionHeight, * Returns the root node of the partition tree */ getRoot: function() { - if (this.parent != null) - { - return this.parent.getRoot(); - } - - return this; + return this._root; }, /** @@ -293,6 +289,18 @@ var et2_dataview_partitionNode = Class.extend([et2_dataview_IPartitionHeight, _data.height += this.getHeight(); }, + getNodeIdx: function(_idx, _create) { + return null; + }, + + getRowProvider: function() { + return this.getRoot().getRowProvider(); + }, + + getDataProvider: function() { + return this.getRoot().getDataProvider(); + }, + /* ---- PRIVATE FUNCTIONS ---- */ _accumulateValue: function(_f1, _f2) { @@ -314,7 +322,7 @@ var et2_dataview_partitionNode = Class.extend([et2_dataview_IPartitionHeight, }); /*var et2_dataview_IIndexOperations = new Interface({ - getIdxNode: function(_idx), + getIdxNode: function(_idx, _create), removeIdxNode: function(_idx), insertNodes: function(_idx, _nodes) });*/ @@ -326,7 +334,7 @@ var et2_dataview_partitionNode = Class.extend([et2_dataview_IPartitionHeight, var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( /*et2_dataview_IIndexOperations, */{ - init: function(_parent, _pidx) { + init: function(_root, _parent, _pidx) { if (typeof _parent == "undefined") { @@ -339,7 +347,7 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( } // Call the parent constructor - this._super(); + this._super(_root); this._children = []; @@ -351,7 +359,7 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( destroy: function() { // Free all child nodes - for (var i = this._children.length - 1; i >= 0; i++) + for (var i = this._children.length - 1; i >= 0; i--) { this._children[i].free(); } @@ -367,6 +375,7 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( this._count = false; this._depth = false; + this._avgHeightData = false; }, /** @@ -451,7 +460,7 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( * Removes the child with the given overall index */ removeIdxNode: function(_idx) { - this._iterateToIndx(_idx, function(ei, bi, child) { + return this._iterateToIndex(_idx, function(ei, bi, child) { if (child.implements(et2_dataview_IIndexOperations)) { return child.removeIdxNode(_idx); @@ -465,7 +474,7 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( * Returns the node with the given overall index and null if it is not found */ getIdxNode: function(_idx) { - this._iterateToIndx(_idx, function(ei, bi, child) { + return this._iterateToIndex(_idx, function(ei, bi, child) { if (child.implements(et2_dataview_IIndexOperations)) { return child.getIdxNode() @@ -478,6 +487,15 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( }, null); }, + /** + * getNodeAt returns the DOM node at the given index + */ + getNodeAt: function(_idx) { + return this._iterateToIndex(_idx, function(ei, bi, child) { + return child.getNodeAt(_idx); + }, null); + }, + /** * Returns all nodes in the given range */ @@ -574,14 +592,14 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( }, /** - * Reduces the given range to a placeholder + * Reduces the given range to a spacer */ reduceRange: function(_range) { this._reduce(this.getRangeNodes(_range, false)) }, /** - * Reduces the given index range to a placeholder + * Reduces the given index range to a spacer */ reduceIdxRange: function(_range) { this._reduce(this.getIdxRangeNodes(_range, false)); @@ -747,6 +765,25 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( this.insertNodes(0, children); }, + /** + * Accumulates the "average height" data + */ + getAvgHeightData: function(_data) { + if (this._avgHeightData == false) + { + this._avgHeightData = {"count": 0, "height": 0}; + + for (var i = 0; i < this._children.length; i++) + { + this._children[i].getAvgHeightData(_data); + } + } + + // Increment the data object entries by the buffered avg height data + _data.count += this._avgHeightData.count; + _data.height += this._avgHeightData.height; + }, + /* ---- PRIVATE FUNCTIONS ---- */ _copyChildren: function() { @@ -760,7 +797,7 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( return children; }, - _iterateToIndx: function(_idx, _func, _res) { + _iterateToIndex: function(_idx, _func, _res) { for (var i = 0; i < this._children.length; i++) { var child = this._children[i]; @@ -783,7 +820,7 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( }, /** - * Reduces the given nodes to a single placeholder + * Reduces the given nodes to a single spacer */ _reduce: function(_nodes) { if (_nodes.length == 0) @@ -791,26 +828,26 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( return; } - // Check whether the first or last node is a placeholder, if not create + // Check whether the first or last node is a spacer, if not create // a new one var ph; - if (_nodes[0] instanceof et2_dataview_partitionPlaceholderNode) + if (_nodes[0] instanceof et2_dataview_partitionSpacerNode) { ph = _nodes[0] } - else if (_nodes[_nodes.length - 1] instanceof et2_dataview_partitionPlaceholderNode) + else if (_nodes[_nodes.length - 1] instanceof et2_dataview_partitionSpacerNode) { ph = _nodes[_nodes.length - 1]; } else { - // Create a new placeholder node an insert it at the place of the + // Create a new spacer node an insert it at the place of the // first node of the range - ph = new et2_dataview_partitionPlaceholderNode(); + ph = new et2_dataview_partitionSpacerNode(this.getRoot()); this.getRoot().insertNodes(_nodes[0].getStartIndex(), [ph]); } - // Get the height of the resulting placeholder + // Get the height of the resulting spacer var height = _nodes[_nodes.length - 1].getBottom() - _nodes[0].getTop(); // Get the count of actual elements in the nodes @@ -820,11 +857,11 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( count += _nodes[i].getCount(); } - // Update the placeholder parameters + // Update the spacer parameters ph.setAvgHeight(height / count); ph.setCount(count); - // Free all elements (except for the placeholder) + // Free all elements (except for the spacer) for (var i = _nodes.length - 1; i >= 0; i--) { if (_nodes[i] != ph) @@ -862,22 +899,112 @@ var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend( this._children = []; } +}); + +/** + * The partition container node base class implements most functionality for + * nodes which have a container. + */ +var et2_dataview_partitionContainerNode = et2_dataview_partitionNode.extend({ + + /** + * Copies the given container object and inserts the container into the tree + * - if it is not already in there. + */ + init: function(_root, _container, _args) { + this._super(_root); + + // Copy the container parameter and set its "invalidationElement" to + // this node + this.container = _container; + this.container.setInvalidationElement(this); + }, + + /** + * Inserts the container into the tree if it is not already inserted. + */ + initializeContainer: function() { + // Obtain the node index and insert the container nodes at the node + // returned by getNodeAt. If idx is zero, the returned node will be + // the outer container, so the container nodes have to be prepended + // to it. + var idx = this.getStartIndex(); + this.container.insertIntoTree(this.getRoot().getNodeAt(idx - 1), + idx == 0); + + this.invalidate(); + }, + + /** + * Destroys the container if it is still set - e.g. the spacer node + * sets the container to null before "free" ist called in some cases, in + * order to pass the container object to another spacer node. + */ + destroy: function() { + if (this.container) + { + this.container.free(); + } + + this._super(); + }, + + /** + * Returns the height of the container + */ + calculateHeight: function() { + return this.container.getHeight(); + }, + + /** + * Calls the "insertNodeAfter" function of the container to insert the node. + */ + insertNodeAfter: function(_node) { + this.container.insertNodeAfter(_node); + }, + + /** + * Returns the "lastNode" of the container + */ + getNodeAt: function(_idx) { + if (_idx >= this.getStartIndex() && _idx < this.getStopIndex()) + { + return this.container.getLastNode(); + } + + return null; + } }); /** - * Node which represents a placeholder. Complete parts of the tree can be - * transformed into placeholder nodes. + * Node which represents a spacer. Complete parts of the tree can be + * transformed into spacer nodes. */ -var et2_dataview_partitionPlaceholderNode = et2_dataview_partitionNode.extend({ +var et2_dataview_partitionSpacerNode = et2_dataview_partitionContainerNode.extend({ - init: function(_count, _avgHeight) { + init: function(_root, _count, _avgHeight, _container) { + + // Create the container if it has not been passed as a third parameter + var container; + if (typeof _container != "undefined") + { + container = _container; + } + else + { + container = new et2_dataview_spacer(_root.getDataProvider(), + _root.getRowProvider(), this); + } // Call the inherited constructor - this._super(); + this._super(_root, container); + // Copy the count and average height parameters and update the height + // of the container this._count = _count; this._avgHeight = _avgHeight; + this.container.setHeight(_count * _avgHeight); }, getCount: function() { @@ -889,6 +1016,7 @@ var et2_dataview_partitionPlaceholderNode = et2_dataview_partitionNode.extend({ { this._count = _count; this.invalidate(); + this.container.setHeight(_count * _avgHeight); } }, @@ -897,6 +1025,7 @@ var et2_dataview_partitionPlaceholderNode = et2_dataview_partitionNode.extend({ { this._avgHeight = _height; this.invalidate(); + this.container.setHeight(_count * _avgHeight); } }, @@ -927,23 +1056,29 @@ var et2_dataview_partitionPlaceholderNode = et2_dataview_partitionNode.extend({ if (startIdx > 0 && startIdx < this._count) { - // Create a placeholder which contains the elements until startIdx - insertNodes.push(new et2_dataview_partitionPlaceholderNode(startIdx, ah)); + // Create a spacer which contains the elements until startIdx + insertNodes.push(new et2_dataview_partitionSpacerNode(this.getRoot(), + startIdx, ah, this.container)); + this.container = null; } + // Calculate the current average height + ah = this.getRoot().getAverageHeight(); + // Create the elements from start to stop index for (var i = startIdx; i < stopIdx; i++) { - var rowNode = new et2_dataview_partitionRowNode(ah); + var rowNode = new et2_dataview_partitionRowNode(this.getRoot(), ah); insertNodes.push(rowNode); } if (stopIdx < this._count - 1 && stopIdx > 0) { - // Create a placeholder which contains the elements starting from + // Create a spacer which contains the elements starting from // stop index var l = this._count - stopIdx; - insertNodes.push(new et2_dataview_partitionPlaceholderNode(l, ah)); + insertNodes.push(new et2_dataview_partitionSpacerNode(this.getRoot(), + l, ah)); } // Check whether insertNodes really has entrys - this is not the case @@ -956,6 +1091,12 @@ var et2_dataview_partitionPlaceholderNode = et2_dataview_partitionNode.extend({ // Insert the newly created nodes at the original place of this node parent.insertNodes(pidx, insertNodes); + // Insert the newly created elements into the DOM-Tree + for (var i = 0; i < insertNodes.length; i++) + { + insertNodes[i].initializeContainer(); + } + return false; } @@ -963,7 +1104,7 @@ var et2_dataview_partitionPlaceholderNode = et2_dataview_partitionNode.extend({ }, getAvgHeightData: function(_data) { - // Do nothing here, as the placeholders should not be inside the average + // Do nothing here, as the spacers should not be inside the average // height statistic. }, @@ -974,33 +1115,95 @@ var et2_dataview_partitionPlaceholderNode = et2_dataview_partitionNode.extend({ */ var et2_dataview_partitionTree = et2_dataview_partitionOrganizationNode.extend({ - init: function(_count, _avgHeight) { - this._super(); + init: function(_dataProvider, _rowProvider, _avgHeight, _node) { + this._super(this); - // Append a placeholder node to the children - var ph = new et2_dataview_partitionPlaceholderNode(_count, _avgHeight); + this._avgHeight = _avgHeight; + this._node = _node; + this._count = _dataProvider.getCount(); + + this._dataProvider = _dataProvider; + this._rowProvider = _rowProvider; + + et2_debug("Creating partition tree with ", this._count, + " elements of avgHeight ", this._avgHeight); + + // Append a spacer node to the children + var ph = new et2_dataview_partitionSpacerNode(this, this._count, + this._avgHeight); ph.setParent(this); + ph.initializeContainer(); this._children = [ph]; + }, + + getAverageHeight: function() { + var data = {"count": 0, "height": 0}; + + this.getAvgHeightData(data); + + if (data.count == 0) + { + return this._avgHeight; + } + + return data.height / data.count; + }, + + /** + * Returns the actual count of managed objects inside of the tree - getCount + * in contrast returns the count of "virtual" objects including the + * spacers. + */ + getManagedCount: function() { + var data = {"count": 0, "height": 0}; + this.getAvgHeightData(data); + + return data.count; + }, + + /** + * Returns the node after which new nodes have to be inserted for the given + * index. + */ + getNodeAt: function(_idx) { + + // Insert the given node to the top of the parent container + if (_idx < 0) + { + return this._node; + } + + // Otherwise search for the tree node with that index + return this._super(_idx); + }, + + getRowProvider: function() { + return this._rowProvider; + }, + + getDataProvider: function() { + return this._dataProvider; } }); -var et2_dataview_partitionRowNode = et2_dataview_partitionNode.extend({ +var et2_dataview_partitionRowNode = et2_dataview_partitionContainerNode.extend({ + + init: function(_root, _avgHeight) { + + var container = new et2_dataview_row(_root.getDataProvider(), + _root.getRowProvider(), this, 0); + + this._super(_root, container); - init: function(_avgHeight) { this._avgHeight = _avgHeight; }, - calculateHeight: function() { - return this._avgHeight; + getIdxNode: function(_node, _create) { + return this.node; } }); -/* -var tree = new et2_dataview_partitionTree(1000, 20); -tree.getRangeNodes(et2_range(0, 100)); -//tree.getRangeNodes(et2_range(0, 1000)); -*/ diff --git a/etemplate/js/et2_dataview_view_row.js b/etemplate/js/et2_dataview_view_row.js index 67b509b254..68839259b6 100644 --- a/etemplate/js/et2_dataview_view_row.js +++ b/etemplate/js/et2_dataview_view_row.js @@ -16,29 +16,31 @@ et2_dataview_interfaces; */ -var et2_dataview_row = et2_dataview_container.extend({ +var et2_dataview_row = et2_dataview_container.extend(et2_dataview_IDataRow, { - init: function(_dataProvider, _rowProvider, _idx) { - this._dataProvider = _dataProvider; - this._rowProvider = _rowProvider; - this._idx = _idx; - this._node = null; - this._rowImpl = null; + init: function(_dataProvider, _rowProvider, _invalidationElem, _idx) { + + this._super(_dataProvider, _rowProvider, _invalidationElem); + + this.tr = this.rowProvider.getPrototype("default"); + $j("div", this.tr).text("Blub"); + this.appendNode(this.tr); // Register this row in the dataprovider - if data is available for this // row the "updateData" function will be called immediately. - this._dataProvider.registerDataRow(_idx, this); + //this.dataProvider.registerDataRow(_idx, this); - if (this._node == null) - { - } +// if (this.tr == null) +// { +// } }, destroy: function() { - this._dataProvider.unregisterDataRow(_idx); + //this.dataProvider.unregisterDataRow(_idx); }, updateData: function(_data) { } }); + diff --git a/etemplate/js/et2_dataview_view_rowProvider.js b/etemplate/js/et2_dataview_view_rowProvider.js index 93e8d94347..a821a2a18f 100644 --- a/etemplate/js/et2_dataview_view_rowProvider.js +++ b/etemplate/js/et2_dataview_view_rowProvider.js @@ -21,16 +21,16 @@ */ var et2_dataview_rowProvider = Class.extend({ - init: function(_gridId, _columnIds) { + init: function(_outerId, _columnIds) { // Copy the given parameters - this._dataProvider = _dataProvider; - this._gridId = _gridId; + this._outerId = _outerId; this._columnIds = _columnIds; this._prototypes = {}; // Create the default row "prototypes" this._createFullRowPrototype(); this._createDefaultPrototype(); + this._createEmptyPrototype(); }, /** @@ -43,8 +43,8 @@ var et2_dataview_rowProvider = Class.extend({ { if (typeof _generator != "undefined") { - this._prototypes[_name] = _generator.call(_context, _gridId, - _columnIds); + this._prototypes[_name] = _generator.call(_context, this._outerId, + this._columnIds); } else { @@ -63,7 +63,7 @@ var et2_dataview_rowProvider = Class.extend({ .attr("span", this._columnIds.length) .appendTo(tr); var div = $j(document.createElement("div")) - .addClass(this._gridId + "_div_fullRow") + .addClass(this._outerId + "_div_fullRow") .appendTo(td); this._prototypes["fullRow"] = tr; @@ -76,14 +76,18 @@ var et2_dataview_rowProvider = Class.extend({ for (var i = 0; i < this._columnIds.length; i++) { var td = $j(document.createElement("td")) - .addClass(this._gridId + "_td_" + this._columnIds[i]) + .addClass(this._outerId + "_td_" + this._columnIds[i]) .appendTo(tr); var div = $j(document.createElement("div")) - .addClass(this._gridId + "_div_" + this._columnIds[i]) + .addClass(this._outerId + "_div_" + this._columnIds[i]) .appendTo(td); } this._prototypes["default"] = tr; + }, + + _createEmptyPrototype: function() { + this._prototypes["empty"] = $j(document.createElement("tr")); } }); diff --git a/etemplate/js/et2_dataview_view_spacer.js b/etemplate/js/et2_dataview_view_spacer.js new file mode 100644 index 0000000000..96677d33a8 --- /dev/null +++ b/etemplate/js/et2_dataview_view_spacer.js @@ -0,0 +1,53 @@ +/** + * eGroupWare eTemplate2 - Class which contains the spacer container + * + * @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$ + */ + +"use strict"; + +/*egw:uses + jquery.jquery; + et2_dataview_view_container; +*/ + +var et2_dataview_spacer = et2_dataview_container.extend({ + + init: function(_dataProvider, _rowProvider, _invalidationElem) { + + // Call the inherited container constructor + this._super(_dataProvider, _rowProvider, _invalidationElem); + + // Get the spacer row and append it to the container + this.spacerNode = this.rowProvider.getPrototype("spacer", + this._createSpacerPrototype, this); + this._phDiv = $j("td", this.spacerNode); + this.appendNode(this.spacerNode); + }, + + setHeight: function(_height) { + this._phDiv.height(_height); + }, + + /* ---- PRIVATE FUNCTIONS ---- */ + + _createSpacerPrototype: function(_outerId, _columnIds) { + var tr = $j(document.createElement("tr")); + + var td = $j(document.createElement("td")) + .addClass("egwGridView_spacer") + .addClass(_outerId + "_spacer_fullRow") + .attr("colspan", _columnIds.length) + .appendTo(tr); + + return tr; + } + +}); + diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js index e20d77d39a..1d2b86ced4 100644 --- a/etemplate/js/et2_extension_nextmatch.js +++ b/etemplate/js/et2_extension_nextmatch.js @@ -21,6 +21,8 @@ et2_widget_grid; et2_widget_selectbox; et2_extension_nextmatch_dynheight; + et2_dataview_gridContainer; + et2_dataview_dataProvider; */ /** @@ -67,8 +69,13 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, { // container. this.dynheight = new et2_dynheight(null, this.div, 150); + // Create the data provider which cares about streaming the row data + // efficiently to the rows + this.dataProvider = new et2_dataview_dataProvider(); + // Create the outer grid container - this.dataviewContainer = new et2_dataview_gridContainer(this.div); + this.dataviewContainer = new et2_dataview_gridContainer(this.div, + this.dataProvider); this.activeFilters = {}; }, @@ -78,6 +85,7 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, { */ destroy: function() { this.dataviewContainer.free(); + this.dataProvider.free(); this.dynheight.free(); this._super.apply(this, arguments); diff --git a/etemplate/js/test/grid.css b/etemplate/js/test/grid.css index cf029db2f4..65339e9422 100644 --- a/etemplate/js/test/grid.css +++ b/etemplate/js/test/grid.css @@ -32,6 +32,8 @@ .egwGridView_scrollarea { width: 100%; overflow: auto; + + background-color: white; } .egwGridView_spacer { diff --git a/etemplate/js/test/test_xml.html b/etemplate/js/test/test_xml.html index 1310f1f361..bea41795b1 100644 --- a/etemplate/js/test/test_xml.html +++ b/etemplate/js/test/test_xml.html @@ -35,7 +35,16 @@ + + + + + + + + +