/** * EGroupware eTemplate2 - dataview code * * @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 2012 * @version $Id$ */ "use strict"; /*egw:uses jquery.jquery; et2_dataview_interfaces; */ /** * The et2_dataview_container class is the main object each dataview consits of. * Each row, spacer as well as the grid itself are containers. A container is * described by its parent element and a certain height. On the DOM-Level a * container may consist of multiple "tr" nodes, which are treated as a unit. * Some containers (like grid containers) are capable of managing a set of child * containers. Each container can indicate, that it thinks that it's height * might have changed. In that case it informs its parent element about that. * The only requirement for the parent element is, that it implements the * et2_dataview_IInvalidatable interface. * A container does not know where it resides inside the grid, or whether it is * currently visible or not -- this information is efficiently managed by the * et2_dataview_grid container. * * @augments Class */ var et2_dataview_container = Class.extend(et2_dataview_IInvalidatable, { /** * Initializes the container object. * * @param _parent is an object which implements the IInvalidatable * interface. _parent may not be null. * @memberOf et2_dataview_container */ init: function(_parent) { // Copy the given invalidation element this._parent = _parent; this._nodes = []; // contains all DOM-Nodes this container exists of this._inTree = false; // this._attachData = {"node": null, "prepend": false}; this._destroyCallback = null; this._destroyContext = null; this._height = false; this._index = 0; this._top = 0; }, /** * Destroys this container. Classes deriving from et2_dataview_container * should override this method and take care of unregistering all event * handlers etc. */ destroy: function() { // Remove the nodes from the tree this.removeFromTree(); // Call the callback function (if one is registered) if (this._destroyCallback) { this._destroyCallback.call(this._destroyContext, this); } }, /** * Sets the "destroyCallback" -- the given function gets called whenever * the container is destroyed. This instance is passed as an parameter to * the callback. * * @param {function} _callback * @param {object} _context */ setDestroyCallback: function(_callback, _context) { this._destroyCallback = _callback; this._destroyContext = _context; }, /** * Inserts all container nodes into the DOM tree after or before the given * element. * * @param _node is the node after/before which the container "tr"s should * get inserted. _node should be a simple DOM node, not a jQuery object. * @param _prepend specifies whether the container should be inserted before * or after the given node. Inserting before is needed for inserting the * first element in front of an spacer. */ insertIntoTree: function(_node, _prepend) { if (!this._inTree && _node != null && this._nodes.length > 0) { // Store the parent node and indicate that this element is now in // the tree. this._attachData = {"node": _node, "prepend": _prepend}; this._inTree = true; for (var i = 0; i < this._nodes.length; i++) { if (i == 0) { if (_prepend) { _node.before(this._nodes[0]); } else { _node.after(this._nodes[0]); } } else { // Insert all following nodes after the previous node this._nodes[i - 1].after(this._nodes[i]); } } // Invalidate this element in order to update the height of the // parent 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 node to the container. * * @param _node is the DOM-Node which should be appended. */ 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 (this._attachData.prepend) { this._attachData.node.before(_node); } else { this._attachData.node.after(_node); } } else { this._nodes[this._nodes.length - 2].after(_node); } this.invalidate(); } }, /** * Removes a certain node from the container * * @param {DOMElement} _node */ 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.parentNode.removeChild(_node); } // 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]; } return null; }, /** * Returns the first node of the container. */ getFirstNode: function() { return this._nodes.length > 0 ? this._nodes[0] : null; }, /** * Returns the accumulated height of all container nodes. Only visible nodes * (without "display: none" etc.) are taken into account. */ getHeight: function() { if (this._height === false && this._inTree) { this._height = 0; // Increment the height value for each visible container node for (var i = 0; i < this._nodes.length; i++) { if (this._isVisible(this._nodes[i][0])) { this._height += this._nodeHeight(this._nodes[i][0]); } } } return this._height === false ? 0 : this._height; }, /** * Returns a datastructure containing information used for calculating the * average row height of a grid. * The datastructure has the * { * avgHeight: <the calculated average height of this element>, * avgCount: <the element count this calculation was based on> * } */ getAvgHeightData: function() { return { "avgHeight": this.getHeight(), "avgCount": 1 }; }, /** * Returns the previously set "pixel top" of the container. */ getTop: function() { return this._top; }, /** * Returns the "pixel bottom" of the container. */ getBottom: function() { return this._top + this.getHeight(); }, /** * Returns the range of the element. */ getRange: function() { return et2_bounds(this.getTop(), this.getBottom()); }, /** * Returns the index of the element. */ getIndex: function() { return this._index; }, /** * Returns how many elements this container represents. */ getCount: function() { return 1; }, /** * Sets the top of the element. * * @param {number} _value */ setTop: function(_value) { this._top = _value; }, /** * Sets the index of the element. * * @param {number} _value */ setIndex: function(_value) { this._index = _value; }, /* -- et2_dataview_IInvalidatable -- */ /** * Broadcasts an invalidation through the container tree. Marks the own * height as invalid. */ invalidate: function() { // Abort if this element is already marked as invalid. if (this._height !== false) { // Delete the own, probably computed height this._height = false; // Broadcast the invalidation to the parent element this._parent.invalidate(); } }, /* -- PRIVATE FUNCTIONS -- */ /** * Used to check whether an element is visible or not (non recursive). * * @param _obj is the element which should be checked for visibility, it is * only checked whether some stylesheet makes the element invisible, not if * the given object is actually inside the DOM. */ _isVisible: function(_obj) { // Check whether the element is localy invisible if (_obj.style && (_obj.style.display === "none" || _obj.style.visiblity === "none")) { return false; } // Get the computed style of the element var style = window.getComputedStyle ? window.getComputedStyle(_obj, null) : _obj.currentStyle; if (style.display === "none" || style.visibility === "none") { return false; } return true; }, /** * Returns the height of a node in pixels and zero if the element is not * visible. The height is clamped to positive values. * * @param {DOMElement} _node */ _nodeHeight: function(_node) { return _node.offsetHeight; } });