egroupware/etemplate/js/et2_dataview_view_container.js

380 lines
8.8 KiB
JavaScript

/**
* 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;
}
});