mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-26 16:48:49 +01:00
Basic grid functionality including dynamic generation of grid rows is now working in all browsers
and performs quite well (just some non-objective data): Lines | IE 7/8 | FF | Chrome --------------------------------------------- 1000 | fast | very fast | very fast 10000 | ok | fast | very fast 100000 | still ok | ok | fast (Performance might still be optimized but this does not really help right now). The code for dynamic data loading has been written but still has to be tested. Work which still has to be done to have a fully functional grid view: - Data columns have to be generated correctly - Displaying trees has to be tested, but should work more or less out-of-the-box due to the design of the grid containers - Client side manipulation of data (sorting/filtering...) - most functionality is already there but not yet used (will be tested alongside with the filemanager)
This commit is contained in:
parent
17f00ad134
commit
c7122b1006
@ -42,8 +42,8 @@ function egwAction(_id, _handler, _caption, _iconUrl, _onExecute, _allowOnMultip
|
||||
throw "egwAction _id must be a non-empty string!";
|
||||
if (typeof _handler == "undefined")
|
||||
this.handler = null;
|
||||
if (typeof _label == "undefined")
|
||||
_label = "";
|
||||
if (typeof _caption == "undefined")
|
||||
_caption = "";
|
||||
if (typeof _iconUrl == "undefined")
|
||||
_iconUrl = "";
|
||||
if (typeof _onExecute == "undefined")
|
||||
@ -394,15 +394,35 @@ function egwActionObject(_id, _parent, _iface, _manager, _flags)
|
||||
this.selectedChildren = [];
|
||||
this.focusedChild = null;
|
||||
|
||||
this.iface = _iface;
|
||||
this.setAOI(_iface);
|
||||
this.iface.setStateChangeCallback(this._ifaceCallback, this);
|
||||
this.iface.setReconnectActionsCallback(this._reconnectCallback, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the action object interface - if "NULL" is given, the iface is set
|
||||
* to a dummy interface which is used to store the temporary data.
|
||||
*/
|
||||
egwActionObject.prototype.setAOI = function(_aoi)
|
||||
{
|
||||
if (_aoi == null)
|
||||
{
|
||||
_aoi = new egwActionObjectDummyInterface();
|
||||
}
|
||||
|
||||
// Copy the state from the old interface
|
||||
if (this.iface)
|
||||
{
|
||||
_aoi.setState(this.iface.getState());
|
||||
}
|
||||
|
||||
// Replace the interface object
|
||||
this.iface = _aoi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object from the tree with the given ID
|
||||
*/
|
||||
//TODO: Add "ById"-Suffix to all other of those functions.
|
||||
//TODO: Add search function to egw_action_commons.js
|
||||
egwActionObject.prototype.getObjectById = function(_id)
|
||||
{
|
||||
@ -927,7 +947,7 @@ egwActionObject.prototype.updateFocusedChild = function(_child, _focused)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.focusedChild = _child)
|
||||
if (this.focusedChild == _child)
|
||||
{
|
||||
this.focusedChild = null;
|
||||
}
|
||||
@ -1358,6 +1378,13 @@ egwActionObjectInterface.prototype.getState = function()
|
||||
return this._state;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** -- egwActionObjectDummyInterface Class -- **/
|
||||
|
||||
var egwActionObjectDummyInterface = egwActionObjectInterface;
|
||||
|
||||
/** egwActionObjectManager Object **/
|
||||
|
||||
/**
|
||||
|
@ -9,578 +9,66 @@
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains classes which are able to display an dynamic data view which
|
||||
*/
|
||||
|
||||
/*
|
||||
uses
|
||||
egw_action,
|
||||
egw_action_common,
|
||||
egw_menu,
|
||||
jquery;
|
||||
egw_action_view,
|
||||
egw_action_data,
|
||||
egw_action_columns
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main class for the grid view. The grid view is a combination of a classic
|
||||
* list view with multiple columns and the tree view.
|
||||
*
|
||||
* @param object _parentNode is the DOM-Node the grid-view should be inserted into
|
||||
* @param array _columns is an array of all colloumns the grid should be able to
|
||||
* display.
|
||||
* TODO: A column should be an object and become much more mighty (sorting,
|
||||
* visibility, etc.)
|
||||
*/
|
||||
function egwGrid(_parentNode, _columns)
|
||||
function egwGrid(_parentNode, _columns, _objectManager, _fetchCallback, _context)
|
||||
{
|
||||
this.parentNode = _parentNode;
|
||||
this.columns = _columns;
|
||||
this.objectManager = _objectManager;
|
||||
|
||||
this.updateElems = [];
|
||||
this.children = [];
|
||||
this.inUpdate = false;
|
||||
this.tbody = null;
|
||||
this.width = 0;
|
||||
this.height = 0;
|
||||
|
||||
// Append the grid base elements to the parent node
|
||||
$(this.parentNode).append(this._buildBase());
|
||||
// Create the column handler and connect its update event to this object
|
||||
this.columns = new egwGridColumns(_columns, this.columnsUpdate, this);
|
||||
|
||||
// Create the read queue
|
||||
this.readQueue = new egwGridDataQueue(_fetchCallback, _context);
|
||||
|
||||
// Create the root data element
|
||||
this.dataRoot = new egwGridDataElement("", null, this.columns, this.readQueue,
|
||||
_objectManager);
|
||||
|
||||
// Create the outer view component and pass the dataRoot element so that
|
||||
// the grid outer element will be capable of fetching the root data and
|
||||
// can create a spacer for that.
|
||||
this.gridOuter = new egwGridViewOuter(_parentNode, this.dataRoot);
|
||||
this.gridOuter.updateColumns(this.columns.getColumnData());
|
||||
}
|
||||
|
||||
egwGrid.prototype.resize = function(_w, _h)
|
||||
{
|
||||
if (_w != this.width)
|
||||
{
|
||||
this.columns.setTotalWidth(_w - this.gridOuter.scrollbarWidth);
|
||||
this.gridOuter.updateColumns(this.columns.getColumnData());
|
||||
this.height = -1;
|
||||
}
|
||||
|
||||
if (_h != this.height)
|
||||
{
|
||||
this.gridOuter.setHeight(_h);
|
||||
}
|
||||
|
||||
this.height = _h;
|
||||
this.width = _w;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new item to the grid and returns it
|
||||
*
|
||||
* @param string _id is an unique identifier of the new grid item. It can be searched
|
||||
* lateron with the "getItemById" function.
|
||||
* @param string _caption is the caption of the new element
|
||||
* @param string _icon is the URL to the icon image
|
||||
* @param object _columns is an object which can contain string entries with html
|
||||
* for every column_id available in the grid.
|
||||
* @returns object the newly created egwGridItem
|
||||
* If the columns have changed, call the gridOuter "updateColumns" function,
|
||||
* which will rebuild the view.
|
||||
*/
|
||||
egwGrid.prototype.addItem = function(_id, _caption, _icon, _columns)
|
||||
egwGrid.prototype.columnsUpdate = function(_column)
|
||||
{
|
||||
var item = new egwGridItem(this, null, _id, _caption, _icon, _columns);
|
||||
this.children.push(item);
|
||||
this.update(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given element - if the element is visible, it is added to the
|
||||
* DOM-Tree if it hasn't been attached to it yet. If the object is already in
|
||||
* the DOM-Tree, it will be rebuilt, which means, that the DOM-Data of its row
|
||||
* will be replaced by a new one.
|
||||
* If multiple updates are in progress, it is wise to group those by using
|
||||
* the beginUpdate and endUpdate functions, as this saves some redundancy
|
||||
* like re-colorizing the rows if a new one has been added.
|
||||
*
|
||||
* @param object _elem is the egwGridItem object, which will be updated
|
||||
*/
|
||||
egwGrid.prototype.update = function(_elem)
|
||||
{
|
||||
if (_elem.isVisible())
|
||||
if (this.gridOuter)
|
||||
{
|
||||
if (!this.inUpdate)
|
||||
{
|
||||
if (this._updateElement(_elem))
|
||||
{
|
||||
this._colorizeRows();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.updateElems.indexOf(_elem) == -1)
|
||||
{
|
||||
this.updateElems.push(_elem);
|
||||
}
|
||||
}
|
||||
this.gridOuter.updateColumns(this.columns.getColumnData());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a group of updates: After beginUpdate has been called, the update function
|
||||
* will collect the update wishes and execute them as soon as endUpdate is called.
|
||||
* This brings a major performance improvement if lots of elements are added.
|
||||
*/
|
||||
egwGrid.prototype.beginUpdate = function()
|
||||
{
|
||||
this.inUpdate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the update grouping and actually executes the updates.
|
||||
*/
|
||||
egwGrid.prototype.endUpdate = function()
|
||||
{
|
||||
// Call the update function for all elements which wanted to be updated
|
||||
// since the last beginUpdate call.
|
||||
var added = false;
|
||||
this.inUpdate = false;
|
||||
for (var i = 0; i < this.updateElems.length; i++)
|
||||
{
|
||||
added = this._updateElement(this.updateElems[i]) || added;
|
||||
}
|
||||
this.updateElems = [];
|
||||
|
||||
// If elements have been (visibly) added to the tree, call the colorize rows
|
||||
// function.
|
||||
if (added)
|
||||
{
|
||||
this._colorizeRows();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the base DOM-Structure of the grid and returns the outer object.
|
||||
*/
|
||||
egwGrid.prototype._buildBase = function()
|
||||
{
|
||||
var table = $(document.createElement("table"));
|
||||
table.addClass("grid");
|
||||
|
||||
this.tbody = $(document.createElement("tbody"));
|
||||
table.append(this.tbody);
|
||||
|
||||
this.tbody.append(this._buildHeader());
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the header row and returns it
|
||||
*/
|
||||
egwGrid.prototype._buildHeader = function()
|
||||
{
|
||||
var row = document.createElement("tr");
|
||||
|
||||
for (var i = 0; i < this.columns.length; i++)
|
||||
{
|
||||
var column = $(document.createElement("th"));
|
||||
if (i == 0)
|
||||
{
|
||||
column.addClass("front");
|
||||
}
|
||||
column.html(this.columns[i].caption);
|
||||
if (typeof this.columns[i].width != "undefined")
|
||||
{
|
||||
column.css("width", this.columns[i].width);
|
||||
}
|
||||
$(row).append(column);
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives odd rows the additional "odd" CSS-class which creates the "zebra" structure.
|
||||
*/
|
||||
egwGrid.prototype._colorizeRows = function()
|
||||
{
|
||||
this.tbody.children().removeClass("odd");
|
||||
$("tr:not(.hidden):odd", this.tbody).addClass("odd");
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function which actually performs the update of the given element as
|
||||
* it is described in the update function.
|
||||
*/
|
||||
egwGrid.prototype._updateElement = function(_elem)
|
||||
{
|
||||
// If the element has to be inserted into the dom tree first, search for the
|
||||
// proper position:
|
||||
var insertAfter = null;
|
||||
var oldRow = null;
|
||||
if (_elem.isVisible())
|
||||
{
|
||||
if (!_elem.domData)
|
||||
{
|
||||
var parentChildren = _elem.parent ? _elem.parent.children : this.children;
|
||||
var index = _elem.index();
|
||||
|
||||
// Fetch the node after which this one should be inserted
|
||||
if (index > 0)
|
||||
{
|
||||
insertAfter = parentChildren[index - 1].lastInsertedChild().domData.row;
|
||||
}
|
||||
else
|
||||
{
|
||||
insertAfter = _elem.parent ? _elem.parent.domData.row :
|
||||
this.tbody.children().get(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether the element already has a row attached to it
|
||||
var row = _elem.buildRow(_elem.domData ? _elem.domData.row : null);
|
||||
|
||||
// Insert the row after the fetched element
|
||||
if (insertAfter)
|
||||
{
|
||||
$(row).insertAfter(insertAfter);
|
||||
return _elem.isVisible();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** egwGridItem Class **/
|
||||
|
||||
/**
|
||||
* The egwGridItem represents a single row inside an egwGrid. Each egwGridItem
|
||||
* contains an egwActionObjectInterface-Object (can be recieved by calling getAOI()),
|
||||
* which is used to interconnect with the egw_action framework.
|
||||
*
|
||||
* Don't create new egwGridItems yourself, use the addItem functions supplied by
|
||||
* egwGrid and egwGridItem.
|
||||
*
|
||||
* @param object _grid is the parent egwGrid object
|
||||
* @param object _parent is the parent egwGridItem. Null if no parent exists.
|
||||
* @param string _caption is the caption of the grid item
|
||||
* @param string _icon is the url to the icon image
|
||||
* @param object _columns is an object which can contain string entries with html
|
||||
* for every column_id available in the grid.
|
||||
* TODO: Remove _canHaveChildren and replace with something better
|
||||
*/
|
||||
function egwGridItem(_grid, _parent, _id, _caption, _icon, _columns, _canHaveChildren)
|
||||
{
|
||||
if (typeof _canHaveChildren == "undefined")
|
||||
{
|
||||
_canHaveChildren = true;
|
||||
}
|
||||
|
||||
// Setup the egwActionObjectInterface
|
||||
this._setupAOI();
|
||||
|
||||
this.clickCallback = null;
|
||||
|
||||
this.grid = _grid;
|
||||
this.parent = _parent;
|
||||
this.id = _id;
|
||||
this.caption = _caption;
|
||||
this.icon = _icon;
|
||||
this.columns = _columns;
|
||||
if (_canHaveChildren)
|
||||
{
|
||||
this.children = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
this.children = false;
|
||||
}
|
||||
|
||||
this.opened = false;
|
||||
this.domData = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates AOI instance of the item and overwrites all necessary functions.
|
||||
*/
|
||||
egwGridItem.prototype._setupAOI = function()
|
||||
{
|
||||
this.aoi = new egwActionObjectInterface;
|
||||
|
||||
// The default state of an aoi is EGW_AO_STATE_NORMAL || EGW_AO_STATE_VISIBLE -
|
||||
// egwGridItems are not necessarily visible by default
|
||||
this.aoi._state = EGW_AO_STATE_NORMAL;
|
||||
|
||||
this.aoi.gridItem = this;
|
||||
|
||||
this.aoi.doSetState = gridAOIDoSetState;
|
||||
this.aoi.getDOMNode = gridAOIGetDOMNode;
|
||||
}
|
||||
|
||||
function gridAOIDoSetState(_state, _shiftState)
|
||||
{
|
||||
if (this.gridItem.domData)
|
||||
{
|
||||
$(this.gridItem.domData.row).toggleClass("selected", egwBitIsSet(_state,
|
||||
EGW_AO_STATE_SELECTED));
|
||||
$(this.gridItem.domData.row).toggleClass("focused", egwBitIsSet(_state,
|
||||
EGW_AO_STATE_FOCUSED));
|
||||
}
|
||||
}
|
||||
|
||||
function gridAOIGetDOMNode()
|
||||
{
|
||||
return this.gridItem.domData ? this.gridItem.domData.row : null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns the actionObjectInterface object of this grid item.
|
||||
*/
|
||||
egwGridItem.prototype.getAOI = function()
|
||||
{
|
||||
return this.aoi;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the grid item is actually visible - in this case this means,
|
||||
* whether the element is a child of an item which is currently not opened.
|
||||
*/
|
||||
egwGridItem.prototype.isVisible = function()
|
||||
{
|
||||
return (this.parent ? this.parent.opened && this.parent.isVisible() : true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the depth of this entry inside the item tree.
|
||||
*/
|
||||
egwGridItem.prototype.getDepth = function()
|
||||
{
|
||||
return (this.parent ? this.parent.getDepth() + 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last child which is inserted into the DOM-Tree. Used by the update
|
||||
* function to determine where new rows should be inserted into the dom tree.
|
||||
*/
|
||||
egwGridItem.prototype.lastInsertedChild = function()
|
||||
{
|
||||
for (var i = (this.children.length - 1); i >= 0; i--)
|
||||
{
|
||||
if (this.children[i].domData)
|
||||
{
|
||||
return this.children[i].lastInsertedChild();
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new child item to this item. Parameters are equivalent to those of
|
||||
* egwGrid.addItem.
|
||||
*/
|
||||
egwGridItem.prototype.addItem = function(_id, _caption, _icon, _columns)
|
||||
{
|
||||
//If the element was not designed to have children, update it in the grid
|
||||
if (!this.children)
|
||||
{
|
||||
this.children = [];
|
||||
this.grid.update(this);
|
||||
}
|
||||
|
||||
var item = new egwGridItem(this.grid, this, _id, _caption, _icon, _columns);
|
||||
this.children.push(item);
|
||||
this.grid.update(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of this element inside it's parent children list.
|
||||
*/
|
||||
egwGridItem.prototype.index = function()
|
||||
{
|
||||
if (this.parent)
|
||||
{
|
||||
return this.parent.children.indexOf(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.grid.children.indexOf(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function which updates the visibility of this item and its children
|
||||
* if the open state of the element is changed.
|
||||
*/
|
||||
egwGridItem.prototype._updateVisibility = function(_visible)
|
||||
{
|
||||
// Set the visibility of this object - if it is at all inserted in the dom-
|
||||
// tree.
|
||||
if (this.domData)
|
||||
{
|
||||
$(this.domData.row).toggleClass("hidden", !_visible);
|
||||
|
||||
// Deselect this row, if it is no longer visible
|
||||
this.aoi.updateState(EGW_AO_STATE_VISIBLE, _visible);
|
||||
|
||||
// Update the visibility of all children
|
||||
for (var i = 0; i < this.children.length; i++)
|
||||
{
|
||||
this.children[i]._updateVisibility(_visible && this.opened);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the visibility of the child elements.
|
||||
*
|
||||
* @param boolean _open specifies whether this item is opened or closed.
|
||||
*/
|
||||
egwGridItem.prototype.setOpened = function(_open)
|
||||
{
|
||||
var self = this;
|
||||
function doSetOpen()
|
||||
{
|
||||
// Set the arrow direction
|
||||
if (_open)
|
||||
{
|
||||
self.domData.arrow.addClass("opened");
|
||||
self.domData.arrow.removeClass("closed");
|
||||
}
|
||||
else
|
||||
{
|
||||
self.domData.arrow.addClass("closed");
|
||||
self.domData.arrow.removeClass("opened");
|
||||
}
|
||||
|
||||
// Update all DOM rows
|
||||
if (_open)
|
||||
{
|
||||
self.grid.beginUpdate();
|
||||
for (var i = 0; i < self.children.length; i++)
|
||||
{
|
||||
var child = self.children[i];
|
||||
if (!child.domData)
|
||||
{
|
||||
self.grid.update(child);
|
||||
}
|
||||
}
|
||||
self.grid.endUpdate();
|
||||
}
|
||||
|
||||
// And make them (in)visible
|
||||
self._updateVisibility(true);
|
||||
|
||||
self.grid._colorizeRows();
|
||||
}
|
||||
|
||||
if (this.opened != _open && this.children !== false)
|
||||
{
|
||||
this.opened = _open;
|
||||
if (this.children.length == 0 && _open)
|
||||
{
|
||||
// alert("JSON Callback")
|
||||
}
|
||||
else
|
||||
{
|
||||
doSetOpen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds the actual DOM-representation of the item and attaches all events to the
|
||||
* DOM-Nodes.
|
||||
*
|
||||
* @param object _row If an existing DOM-Item should be updated, it can be passed
|
||||
* here and the updated objects will be inserted inside of the row object. Defaults
|
||||
* to null.
|
||||
*/
|
||||
egwGridItem.prototype.buildRow = function(_row)
|
||||
{
|
||||
// Build the container row
|
||||
var row = null;
|
||||
if (typeof _row == "undefined" || !_row)
|
||||
{
|
||||
row = document.createElement("tr");
|
||||
}
|
||||
else
|
||||
{
|
||||
row = _row;
|
||||
$(row).empty();
|
||||
}
|
||||
$(row).toggleClass("hidden", !this.isVisible())
|
||||
|
||||
// Build the indentation object
|
||||
var indentation = $(document.createElement("span"));
|
||||
indentation.addClass("indentation");
|
||||
indentation.css("width", (this.getDepth() * 12) + "px");
|
||||
|
||||
// Build the arrow element
|
||||
var arrow = $(document.createElement("span"));
|
||||
arrow.addClass("arrow");
|
||||
if (this.children !== false)
|
||||
{
|
||||
arrow.addClass(this.opened ? "opened" : "closed");
|
||||
arrow.click(this, function(e) {
|
||||
e.data.setOpened(!e.data.opened);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Build the icon element
|
||||
icon = $(document.createElement("img"));
|
||||
if (this.icon)
|
||||
{
|
||||
icon.attr("src", this.icon);
|
||||
}
|
||||
icon.addClass("icon");
|
||||
|
||||
// Build the caption
|
||||
var caption = $(document.createElement("span"));
|
||||
caption.text(this.caption);
|
||||
caption.addClass("caption");
|
||||
|
||||
// Build the td surrounding those elements
|
||||
var column_caption = $(document.createElement("td"));
|
||||
column_caption.append(indentation, arrow, icon, caption);
|
||||
column_caption.mousedown(egwPreventSelect);
|
||||
column_caption.click(this, function(e) {
|
||||
egwResetPreventSelect(this);
|
||||
this.onselectstart = null;
|
||||
e.data._columnClick(egwGetShiftState(e), 0);
|
||||
});
|
||||
|
||||
// Append the column to the row
|
||||
$(row).append(column_caption);
|
||||
|
||||
for (var i = 1; i < this.grid.columns.length; i++) // Skips the front column
|
||||
{
|
||||
var content = "";
|
||||
var gridcol = this.grid.columns[i];
|
||||
|
||||
if (typeof this.columns[gridcol.id] != "undefined")
|
||||
{
|
||||
content = this.columns[gridcol.id];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (typeof gridcol["default"] != "undefined")
|
||||
{
|
||||
content = gridcol["default"];
|
||||
}
|
||||
}
|
||||
|
||||
// Create a column and append it to the row
|
||||
var column = $(document.createElement("td"));
|
||||
column.html(content);
|
||||
column.mousedown(egwPreventSelect);
|
||||
column.click({"item": this, "col": gridcol.id}, function(e) {
|
||||
egwPreventSelect(this);
|
||||
e.data.item._columnClick(egwGetShiftState(e), e.data.col);
|
||||
});
|
||||
$(row).append(column);
|
||||
}
|
||||
|
||||
this.domData = {
|
||||
"row": row,
|
||||
"arrow": arrow,
|
||||
"icon": icon,
|
||||
"caption": caption
|
||||
}
|
||||
|
||||
// The item is now visible
|
||||
this.aoi.updateState(EGW_AO_STATE_VISIBLE, true);
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
egwGridItem.prototype._columnClick = function(_shiftState, _column)
|
||||
{
|
||||
var state = this.aoi.getState();
|
||||
var isSelected = egwBitIsSet(state, EGW_AO_STATE_SELECTED);
|
||||
|
||||
this.aoi.updateState(EGW_AO_STATE_SELECTED,
|
||||
!egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI) || !isSelected,
|
||||
_shiftState);
|
||||
}
|
||||
|
||||
|
763
phpgwapi/js/egw_action/egw_grid_data.js
Normal file
763
phpgwapi/js/egw_action/egw_grid_data.js
Normal file
@ -0,0 +1,763 @@
|
||||
/**
|
||||
* eGroupWare egw_action framework - egw action framework
|
||||
*
|
||||
* @link http://www.egroupware.org
|
||||
* @author Andreas Stöckel <as@stylite.de>
|
||||
* @copyright 2011 by Andreas Stöckel
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @package egw_action
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
/*
|
||||
uses
|
||||
egw_action,
|
||||
egw_action_common,
|
||||
egw_grid_columns;
|
||||
*/
|
||||
|
||||
/** -- egwGridDataElement Class -- **/
|
||||
|
||||
var EGW_DATA_TYPE_RANGE = 0;
|
||||
var EGW_DATA_TYPE_ELEMENT = 1;
|
||||
|
||||
/**
|
||||
* Contains the data (model) objects which retrieve data from the given source and
|
||||
* pass it to.
|
||||
*
|
||||
* @param object _parent the parent data element in which the new element is contained
|
||||
* @param object _columns the columns object which contains information about the data columns
|
||||
* @param object _readQueue is the queue object which queues data-fetching calls and executes these
|
||||
* asynchronously.
|
||||
* @param object _objectManager if this element is the root element (_parent is null),
|
||||
* specify the _objectManager in order to supply a parent object manager for that
|
||||
* element.
|
||||
*/
|
||||
function egwGridDataElement(_id, _parent, _columns, _readQueue, _objectManager)
|
||||
{
|
||||
// Copy the passed arguments
|
||||
this.id = _id;
|
||||
this.parent = _parent;
|
||||
this.columns = _columns;
|
||||
this.readQueue = _readQueue;
|
||||
|
||||
// Generate the action object associated to this element
|
||||
this.parentActionObject = _parent ? _parent.actionObject : _objectManager;
|
||||
this.actionObject = null;
|
||||
|
||||
// If this is the root object, add the an root action object to the objectManager
|
||||
if (!_parent)
|
||||
{
|
||||
this.actionObject = this.parentActionObject.addObject(_id, null,
|
||||
EGW_AO_FLAG_IS_CONTAINER);
|
||||
this.readQueue.setDataRoot(this);
|
||||
}
|
||||
|
||||
// Preset some parameters
|
||||
this.children = [];
|
||||
|
||||
this.data = {};
|
||||
this.caption = false;
|
||||
this.iconUrl = false;
|
||||
this.opened = _parent == null;
|
||||
this.index = 0;
|
||||
this.canHaveChildren = false;
|
||||
this.type = egwGridViewRow;
|
||||
this.userData = null;
|
||||
|
||||
this.gridViewObj = null;
|
||||
}
|
||||
|
||||
egwGridDataElement.prototype.free = function()
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
|
||||
egwGridDataElement.prototype.set_caption = function(_value)
|
||||
{
|
||||
this.caption = _value;
|
||||
}
|
||||
|
||||
egwGridDataElement.prototype.set_iconUrl = function(_value)
|
||||
{
|
||||
this.iconUrl = _value;
|
||||
}
|
||||
|
||||
egwGridDataElement.prototype.set_opened = function(_value)
|
||||
{
|
||||
this.opened = _value;
|
||||
}
|
||||
|
||||
egwGridDataElement.prototype.set_canHaveChildren = function(_value)
|
||||
{
|
||||
this.canHaveChildren = _value && (this.children.length == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the column data. The column data is an object (used as associative array)
|
||||
* which may be of the following outline:
|
||||
*
|
||||
* {
|
||||
* "[col1_id]": "[data]",
|
||||
* "[col2_id]":
|
||||
* {
|
||||
* "data": "[data]",
|
||||
* "sortData": "[sortData]"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* "sortData" is data which is used for sorting instead of "data" when set.
|
||||
*/
|
||||
egwGridDataElement.prototype.set_data = function(_value)
|
||||
{
|
||||
if (typeof _value == "object" && _value.constructor == Object)
|
||||
{
|
||||
// Update the column data specified in the value
|
||||
for (col_id in _value)
|
||||
{
|
||||
var val = _value[col_id];
|
||||
|
||||
var data = "";
|
||||
var sortData = null;
|
||||
|
||||
if (typeof val == "object")
|
||||
{
|
||||
data = typeof val.data != "undefined" ? val.data : "";
|
||||
sortData = typeof val.sortData != "undefined" ? val.sortData : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
data = val;
|
||||
}
|
||||
|
||||
this.data[col_id] = {
|
||||
"data": data,
|
||||
"sortData": sortData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data into the GridData element. This function has two basic operating modes:
|
||||
*
|
||||
* 1. If an array of objects is passed, the specified objects are added as children.
|
||||
* If a child node with the given ID already exists, it is updated.
|
||||
* The given data array must have the following form:
|
||||
* [
|
||||
* {
|
||||
* ["entryType": (EGW_DATA_TYPE_ELEMENT | EGW_DATA_TYPE_RANGE)] // Defaults to EGW_DATA_TYPE_ELEMENT
|
||||
* "type": "[Typeclass]" // Typeclass of the view-container: specifies the chars after the egwGridView-prefix. Defaults to "Row" which becomes "egwGridViewRow"
|
||||
* IF EGW_DATA_TYPE_ELEMENT:
|
||||
* "children": [ Objects which will be added to the children of the element ]
|
||||
* ELEMENT DATA // See below
|
||||
IF EGW_DATA_TYPE_RANGE:
|
||||
"count": [Count of Elements],
|
||||
"prefix": "[String prefix which will be added to each element including their index in the list]"
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* 2. If an object with element dara is passed, the properties of the element will
|
||||
* be updated to the given values.
|
||||
*
|
||||
* {
|
||||
* "data": { COLUMN DATA OBJECT } // See "set_data" function
|
||||
* "caption": "[Caption]" // Used in the EGW_COL_TYPE_NAME_ICON_FIXED column
|
||||
* "iconUrl": "[IconUrl]" // Used in the EGW_COL_TYPE_NAME_ICON_FIXED column
|
||||
* "opened": [true|false] // Specifies whether the row is "opened" or "closed" (in trees)
|
||||
* "canHaveChildren": [true|false] // Specifies whether the row "open/close" button is displayed
|
||||
* }
|
||||
*/
|
||||
egwGridDataElement.prototype.loadData = function(_data)
|
||||
{
|
||||
if (_data.constructor == Array)
|
||||
{
|
||||
var virgin = this.children.length == 0;
|
||||
var last_element = null;
|
||||
|
||||
for (var i = 0; i < _data.length; i++)
|
||||
{
|
||||
var entry = _data[i];
|
||||
|
||||
if (entry.constructor != Object)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var element = null;
|
||||
|
||||
// Read the entry type and the element type (if they are set)
|
||||
var entryType = typeof entry.entryType == "number" ? entry.entryType :
|
||||
EGW_DATA_TYPE_ELEMENT;
|
||||
var type = (typeof entry.type == "string") && (typeof window["egwGridView" + entry.type] == "function") ?
|
||||
window["egwGridView" + entry.type] : egwGridViewRow;
|
||||
|
||||
// Inserts a range of given dummy elements into the data tree
|
||||
if (entryType == EGW_DATA_TYPE_RANGE)
|
||||
{
|
||||
var count = typeof entry.count == "number" && entry.count >= 0 ? entry.count : 1;
|
||||
var prefix = typeof entry.prefix == "string" ? entry.prefix : "elem_";
|
||||
var index = last_element ? last_element.index + 1 : 0;
|
||||
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
var id = prefix + (index + j);
|
||||
element = this.insertElement(index + j, id);
|
||||
element.type = type; // Type can only be set directly after creation
|
||||
}
|
||||
}
|
||||
else if (entryType == EGW_DATA_TYPE_ELEMENT)
|
||||
{
|
||||
var id = typeof entry.id == "string" ? entry.id : "";
|
||||
element = null;
|
||||
|
||||
if (!virgin && id)
|
||||
{
|
||||
element = this.getElementById(id, 1);
|
||||
}
|
||||
|
||||
if (!element)
|
||||
{
|
||||
element = this.insertElement(false, id);
|
||||
element.type = type; // Type can only be set directly after creation
|
||||
}
|
||||
|
||||
|
||||
element.loadData(entry);
|
||||
}
|
||||
|
||||
last_element = element;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load all the data element for which a setter function exists
|
||||
egwActionStoreJSON(_data, this, true);
|
||||
|
||||
// Load the child data
|
||||
if (typeof _data.children != "undefined" && _data.children.constructor == Array)
|
||||
{
|
||||
this.loadData(_data.children);
|
||||
}
|
||||
|
||||
this.gridViewObj.callGridViewObjectUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new element as child at the given position
|
||||
*
|
||||
* @param integer _index is the index at which the element will be inserted. If
|
||||
* false, the element will be added to the end of the list.
|
||||
* @param string _id is the id of the newly created element
|
||||
* @returns the newly created element
|
||||
*/
|
||||
egwGridDataElement.prototype.insertElement = function(_index, _id)
|
||||
{
|
||||
if (!_index)
|
||||
{
|
||||
_index = this.children.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
_index = Math.max(0, Math.min(this.children.length, _index));
|
||||
}
|
||||
|
||||
// Create the data element
|
||||
var element = new egwGridDataElement(_id, this, this.columns, this.readQueue,
|
||||
null);
|
||||
element.index = _index;
|
||||
|
||||
// Create the action object
|
||||
var object = this.actionObject.insertObject(_index, _id, null, 0);
|
||||
|
||||
// Link the two together
|
||||
element.actionObject = object;
|
||||
|
||||
// As this element now at least has one child, "canHaveChildren" must be true
|
||||
this.canHaveChildren = true;
|
||||
|
||||
// Insert the element at the given index
|
||||
this.children.splice(_index, 0, element);
|
||||
|
||||
// Increment the index of all following elements
|
||||
for (var i = _index + 1; i < this.children.length; i++)
|
||||
{
|
||||
this.children[i].index++;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new data element as child to the end of the list.
|
||||
*
|
||||
* @param string _id is the object identifier
|
||||
* @returns the newly created element
|
||||
*/
|
||||
egwGridDataElement.prototype.addElement = function(_id)
|
||||
{
|
||||
return this.insertElement(false, _id);
|
||||
}
|
||||
|
||||
egwGridDataElement.prototype.removeElement = function()
|
||||
{
|
||||
//TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for the element with the given id and returns it. _depth specifies
|
||||
* the maximum recursion depth. May be omited.
|
||||
*/
|
||||
egwGridDataElement.prototype.getElementById = function(_id, _depth)
|
||||
{
|
||||
if (typeof _depth == "undefined")
|
||||
{
|
||||
_depth = -1;
|
||||
}
|
||||
|
||||
// Check whether this element is the searched one, if yes return it
|
||||
if (_id == this.id)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
// Only continue searching in deeper levels, if the given depth is greater than
|
||||
// zero, or hasn't been defined and is therefore smaller than zero
|
||||
if (_depth < 0 || _depth > 0)
|
||||
{
|
||||
for (var i = 0; i < this.children.length; i++)
|
||||
{
|
||||
var elem = this.children.getElementById(_id, _depth - 1);
|
||||
|
||||
if (elem)
|
||||
{
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all children as array - this list will be used to set the item list
|
||||
* of the egwGridViewSpacer containers.
|
||||
*/
|
||||
egwGridDataElement.prototype.getChildren = function(_callback, _context)
|
||||
{
|
||||
if (this.children.length > 0)
|
||||
{
|
||||
_callback.call(_context, this.children);
|
||||
}
|
||||
else if (this.canHaveChildren)
|
||||
{
|
||||
// If the children havn't been loaded yet, request them via queue call.
|
||||
this.readQueue.queue(this, EGW_DATA_QUEUE_CHILDREN, function() {
|
||||
_callback.call(_context, this.children);
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
egwGridDataElement.prototype.hasColumn = function(_columnId, _returnData)
|
||||
{
|
||||
// Get the column
|
||||
var col = this.columns.getColumnById(_columnId);
|
||||
var res = null;
|
||||
|
||||
if (col)
|
||||
{
|
||||
res = false;
|
||||
|
||||
// Check whether the queried column is the "EGW_COL_TYPE_NAME_ICON_FIXED" column
|
||||
if (col.type == EGW_COL_TYPE_NAME_ICON_FIXED)
|
||||
{
|
||||
if (this.caption !== false)
|
||||
{
|
||||
if (_returnData)
|
||||
{
|
||||
res = {
|
||||
"caption": this.caption,
|
||||
"iconUrl": this.iconUrl
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check whether the column data of this column has been read,
|
||||
// if yes, return it.
|
||||
if (typeof this.data[_columnIds] != "undefined")
|
||||
{
|
||||
if (_returnData)
|
||||
{
|
||||
res = this.data[_columnIds].data;
|
||||
}
|
||||
else
|
||||
{
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
// Probably there is a default value specified for this column...
|
||||
else if (col["default"] !== EGW_COL_DEFAULT_FETCH)
|
||||
{
|
||||
if (_returnData)
|
||||
{
|
||||
res = col["default"];
|
||||
}
|
||||
else
|
||||
{
|
||||
res = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data for the given columns or incomplete data if those columns
|
||||
* are not available now. Those columns are loaded asynchronously in the background
|
||||
* and the GridViewObject is informed about this as soon as the new data has been
|
||||
* loaded.
|
||||
*
|
||||
* @param _columnIds is an array of column ids for which the data should be returned
|
||||
*/
|
||||
egwGridDataElement.prototype.getData = function(_columnIds)
|
||||
{
|
||||
var queryList = [];
|
||||
var result = {};
|
||||
|
||||
for (var i = 0; i < _columnIds.length; i++)
|
||||
{
|
||||
res = this.hasColumn(_columnIds[i], true);
|
||||
|
||||
// Either add the result to the result list (if the column data was available)
|
||||
// or add it to the query list.
|
||||
if (res !== null)
|
||||
{
|
||||
if (res !== false)
|
||||
{
|
||||
result[_columnIds[i]] = res;
|
||||
}
|
||||
else
|
||||
{
|
||||
queryList.push(_columnIds[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If one data entry hasn't been available, queue the request for this data
|
||||
// in the readQueue
|
||||
if (queryList.length > 0)
|
||||
{
|
||||
this.readQueue.queue(this, queryList);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls the row object update function - checks whether the row object implements
|
||||
* this interface and whether it is set.
|
||||
*/
|
||||
egwGridDataElement.prototype.callGridViewObjectUpdate = function()
|
||||
{
|
||||
if (this.gridViewObj && typeof this.gridViewObj.doUpdateData == "function")
|
||||
{
|
||||
this.gridViewObj.doUpdateData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute index of this element
|
||||
*/
|
||||
egwGridDataElement.prototype.getTotalIndex = function()
|
||||
{
|
||||
var idx = this.index;
|
||||
|
||||
if (this.parent && this.parent.opened)
|
||||
{
|
||||
idx += this.parent.getTotalIndex();
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this data element is a odd or even one
|
||||
*/
|
||||
egwGridDataElement.prototype.isOdd = function()
|
||||
{
|
||||
return (this.getTotalIndex() % 2) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function which is called by the grid view container in order to update the
|
||||
* action object aoi.
|
||||
*/
|
||||
egwGridDataElement.prototype.setGridViewObj = function(_obj)
|
||||
{
|
||||
this.gridViewObj = _obj;
|
||||
|
||||
if (_obj && typeof _obj.getAOI == "function")
|
||||
{
|
||||
this.actionObject.setAOI(_obj.getAOI());
|
||||
}
|
||||
else
|
||||
{
|
||||
this.actionObject.setAOI(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** - egwGridDataReadQueue -- **/
|
||||
|
||||
// Some internally used constants
|
||||
var EGW_DATA_QUEUE_ELEM = 0;
|
||||
var EGW_DATA_QUEUE_CHILDREN = 1;
|
||||
|
||||
// Count of elements which are dynamically added to the update list.
|
||||
var EGW_DATA_QUEUE_PREFETCH_COUNT = 50;
|
||||
|
||||
// Timeout after which the queue events are no longer queued but the actual
|
||||
// callback function is called.
|
||||
var EGW_DATA_QUEUE_FLUSH_TIMEOUT = 200;
|
||||
|
||||
// Maximum count of elements in the queue after which the queue is flushed
|
||||
var EGW_DATA_QUEUE_MAX_ELEM_COUNT = 100;
|
||||
|
||||
function egwGridDataQueue(_fetchCallback, _context)
|
||||
{
|
||||
this.fetchCallback = _fetchCallback;
|
||||
this.context = _context;
|
||||
this.dataRoot = null;
|
||||
|
||||
this.queue = [];
|
||||
this.queueColumns = [];
|
||||
this.timeoutId = 0;
|
||||
}
|
||||
|
||||
egwGridDataQueue.prototype.setDataRoot = function(_dataRoot)
|
||||
{
|
||||
this.dataRoot = _dataRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element to the queue and checks whether its element count is larger
|
||||
* than the one specified in EGW_DATA_QUEUE_MAX_ELEM_COUNT. If this is the case,
|
||||
* the queue is flushed and false is returned, otherwise true.
|
||||
*/
|
||||
egwGridDataQueue.prototype._queue = function(_obj)
|
||||
{
|
||||
this.queue.push(_obj);
|
||||
|
||||
if (this.queue.length > EGW_DATA_QUEUE_MAX_ELEM_COUNT)
|
||||
{
|
||||
this.flushQueue();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
egwGridDataQueue.prototype.inQueue = function(_elem)
|
||||
{
|
||||
for (var i = 0; i < this.queue.length; i++)
|
||||
{
|
||||
if (this.queue[i].elem == _elem)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues the given element in the fetch-data queue.
|
||||
*
|
||||
* @param object _elem is the element whose data will be fetched
|
||||
* @param array _columns is an array of column ids which should be fetched. Those
|
||||
* columns will be accumulated over the queue calls. _columns may also take
|
||||
* the value EGW_DATA_QUEUE_CHILDREN in which case a request for the children
|
||||
* of the given element is queued.
|
||||
* @param function _callback is a callback function which will be called after
|
||||
* the data has been sent from the server.
|
||||
* @param object _context is the context in which the callback function will
|
||||
* be executed.
|
||||
*/
|
||||
egwGridDataQueue.prototype.queue = function(_elem, _columns, _callback, _context)
|
||||
{
|
||||
if (typeof _callback == "undefined")
|
||||
{
|
||||
_callback = null;
|
||||
}
|
||||
if (typeof _context == "undefined")
|
||||
{
|
||||
_context = null;
|
||||
}
|
||||
|
||||
if (_columns === EGW_DATA_QUEUE_CHILDREN)
|
||||
{
|
||||
if (!this._queue({
|
||||
"elem": _elem,
|
||||
"type": EGW_DATA_QUEUE_CHILDREN,
|
||||
"proc": _callback,
|
||||
"context": _context
|
||||
}))
|
||||
{
|
||||
this.flushQueue();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Merge the specified columns into the queueColumns variable
|
||||
for (var i = 0; i < _columns.length; i++)
|
||||
{
|
||||
if (this.queueColumns.indexOf(_columns[i]) == -1)
|
||||
{
|
||||
this.queueColumns.push(_columns[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Queue the element and search in the elements around the given one for
|
||||
// elements whose data isn't loaded yet.
|
||||
var done = !this._queue({
|
||||
"elem": _elem,
|
||||
"type": EGW_DATA_QUEUE_ELEM,
|
||||
"proc": _callback,
|
||||
"context": _context
|
||||
});
|
||||
|
||||
// Prefetch other elements around the given element
|
||||
var parent = _elem.parent;
|
||||
if (parent)
|
||||
{
|
||||
// Initialize the start prefetch index and the max prefetch count
|
||||
var prefetch = EGW_DATA_QUEUE_PREFETCH_COUNT;
|
||||
var idx = Math.floor(Math.max(0, _elem.index - prefetch / 2));
|
||||
|
||||
while (!done && prefetch > 0 && idx < parent.children.length)
|
||||
{
|
||||
|
||||
// Don't prefetch the element itself
|
||||
if (idx != _elem.idx)
|
||||
{
|
||||
// Fetch the element with the current index from the children
|
||||
// of the parent of the element.
|
||||
var elem = parent.children[idx];
|
||||
|
||||
// Check whether this element has all data columns loaded and is
|
||||
// not already in the queue
|
||||
if (!this.inQueue(elem))
|
||||
{
|
||||
var hasColumns = true;
|
||||
for (var j = 0; j < this.queueColumns.length; j++)
|
||||
{
|
||||
var res = elem.hasColumn(this.queueColumns[i], false);
|
||||
if (!res)
|
||||
{
|
||||
hasColumns = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasColumns)
|
||||
{
|
||||
done = !this._queue({
|
||||
"elem": elem,
|
||||
"type": EGW_DATA_QUEUE_ELEM,
|
||||
"proc": null,
|
||||
"context": null
|
||||
});
|
||||
prefetch--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the queue and calls the fetch callback which cares about retrieving
|
||||
* the data from the server.
|
||||
*/
|
||||
egwGridDataQueue.prototype.flushQueue = function()
|
||||
{
|
||||
var ids = [];
|
||||
|
||||
// Generate a list of element ids
|
||||
for (var i = 0; i < this.queue.length; i++)
|
||||
{
|
||||
ids.push(this.queue[i].elem.id);
|
||||
}
|
||||
|
||||
// Call the fetch callback and save a snapshot of the current queue
|
||||
var queue = this.queue;
|
||||
this.fetchCallback.call(this.context, ids, this.queueColumns, function(_data) {
|
||||
this.dataCallback(_data, queue);
|
||||
}, this);
|
||||
|
||||
this.queue = [];
|
||||
this.queueColumns = [];
|
||||
}
|
||||
|
||||
egwGridDataQueue.prototype.dataCallback = function(_data, _queue)
|
||||
{
|
||||
var rootData = [];
|
||||
|
||||
// Iterate over the given data and check whether the data coresponds to one
|
||||
// of the queue elements - if yes, call their (probably) specified callback.
|
||||
// All elements for which no queue element can be found are added to the
|
||||
// "rootData" list, which is then loaded by the "dataRoot" data object.
|
||||
for (var i = 0; i < _data.length; i++)
|
||||
{
|
||||
var hasTarget = false;
|
||||
|
||||
// Search for a queue element which belongs to the given data entry.
|
||||
if (_queue.length > 0 && typeof _data[i].id != "undefined")
|
||||
{
|
||||
var id = _data[i].id;
|
||||
|
||||
for (var j = 0; j < _queue.length; j++)
|
||||
{
|
||||
if (_queue[j].elem.id == id)
|
||||
{
|
||||
// The element has been found, update its data
|
||||
_queue[j].elem.loadData(_data[i]);
|
||||
|
||||
// Call the queue object callback (if specified)
|
||||
if (_queue[j].callback)
|
||||
{
|
||||
_queue[j].callback.call(_queue[j].context);
|
||||
}
|
||||
|
||||
// Delete this queue element
|
||||
_queue.splice(i, 1);
|
||||
|
||||
hasTarget = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasTarget)
|
||||
{
|
||||
rootData.push(_queue[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.dataRoot.loadData(rootData);
|
||||
}
|
||||
|
@ -9,15 +9,25 @@
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
/**
|
||||
* View Classes for the egw grid component.
|
||||
*/
|
||||
//TODO: Spacer itemHeight should automatically be set to the average item height in the
|
||||
// grid to solve the "stutters" when scrolling up.
|
||||
//TODO (minor): Do auto cleanup - remove elements from the view again after they
|
||||
// haven't been viewed for a certain time.
|
||||
|
||||
/*
|
||||
uses
|
||||
egw_action_common,
|
||||
egw_action_data,
|
||||
jquery;
|
||||
*/
|
||||
|
||||
/**
|
||||
* Common functions used in all classes
|
||||
* Common functions used in most view classes
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns an "area" object with the given top position and height
|
||||
*/
|
||||
function egwArea(_top, _height)
|
||||
{
|
||||
return {
|
||||
@ -26,11 +36,18 @@ function egwArea(_top, _height)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether two area objects intersect each other
|
||||
*/
|
||||
function egwAreaIntersect(_ar1, _ar2)
|
||||
{
|
||||
return ! (_ar1.bottom < _ar2.top || _ar1.top > _ar2.bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether two areas intersect (result = 0) or their relative position
|
||||
* to each other (used to do a binary search inside a list of sorted area objects).
|
||||
*/
|
||||
function egwAreaIntersectDir(_ar1, _ar2)
|
||||
{
|
||||
if (_ar1.bottom < _ar2.top)
|
||||
@ -47,14 +64,205 @@ function egwAreaIntersectDir(_ar1, _ar2)
|
||||
|
||||
/** -- egwGridViewOuter Class -- **/
|
||||
|
||||
var EGW_GRID_COLUMN_PADDING = 2;
|
||||
var EGW_GRID_SCROLLBAR_WIDTH = false;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Base view class which is responsible for displaying a grid view element.
|
||||
*
|
||||
* @param object _parentNode is the DOM-Node into which the grid view will be inserted
|
||||
* @param object _data is the data-provider object which contains/loads the grid rows
|
||||
* and contains their data.
|
||||
*/
|
||||
function egwGridViewOuter()
|
||||
function egwGridViewOuter(_parentNode, _dataRoot)
|
||||
{
|
||||
this.parentNode = $(_parentNode);
|
||||
this.dataRoot = _dataRoot;
|
||||
|
||||
// Build the base nodes
|
||||
this.outer_table = null;
|
||||
this.outer_thead = null;
|
||||
this.outer_head_tr = null;
|
||||
this.outer_tbody = null;
|
||||
this.outer_tr = null;
|
||||
this.optcol = null;
|
||||
this.selectcols = null;
|
||||
|
||||
this.oldWidth = 0;
|
||||
this.oldHeight = 0;
|
||||
this.headerHeight = 0;
|
||||
this.scrollbarWidth = 0;
|
||||
|
||||
this.headerColumns = [];
|
||||
|
||||
this.buildBase();
|
||||
this.parentNode.append(this.outer_table);
|
||||
|
||||
// Read the scrollbar width
|
||||
this.scrollbarWidth = Math.max(10, this.getScrollbarWidth());
|
||||
|
||||
// Start value for the average row height
|
||||
this.avgRowHeight = 23.0;
|
||||
this.avgRowCnt = 1;
|
||||
|
||||
// Insert the base grid container into the DOM-Tree
|
||||
this.grid = new egwGridViewGrid(null, null, true, this); // (No parent grid, no height change callback, scrollable)
|
||||
this.grid.insertIntoDOM(this.outer_tr, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new element to the average container height counter.
|
||||
*/
|
||||
egwGridViewOuter.prototype.addHeightToAvg = function(_value)
|
||||
{
|
||||
this.avgRowCnt++;
|
||||
|
||||
var frac = 1.0 / this.avgRowCnt;
|
||||
this.avgRowHeight = this.avgRowHeight * (1 - frac) + _value * frac;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all containers from the base grid and replaces it with spacers again.
|
||||
* As only partial data is displayed, this method is faster than updating every
|
||||
* displayed data row. Please note that this may also reset/change the scrollbar
|
||||
* position.
|
||||
*/
|
||||
egwGridViewOuter.prototype.empty = function()
|
||||
{
|
||||
this.grid.empty(this.columns);
|
||||
|
||||
// Create a new spacer container and set the item list to the root level children
|
||||
var spacer = this.grid.insertContainer(-1, egwGridViewSpacer, this.avgRowHeight);
|
||||
this.dataRoot.getChildren(function(_children) {
|
||||
spacer.setItemList(_children);
|
||||
}, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column data which is retrieved by calling egwGridColumns.getColumnData.
|
||||
* The columns will be updated.
|
||||
*/
|
||||
egwGridViewOuter.prototype.updateColumns = function(_columns)
|
||||
{
|
||||
// Copy the columns data
|
||||
this.columns = _columns;
|
||||
|
||||
// Rebuild the header
|
||||
this.buildBaseHeader();
|
||||
|
||||
// Set the grid width
|
||||
this.grid.outerNode.attr("colspan", _columns.length + 1);
|
||||
|
||||
// Empty the grid
|
||||
this.empty();
|
||||
}
|
||||
|
||||
egwGridViewOuter.prototype.buildBase = function()
|
||||
{
|
||||
/*
|
||||
Structure:
|
||||
<table class="egwGridView_outer">
|
||||
<thead>
|
||||
<tr> [HEAD] </tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr> [GRID CONTAINER] </tr>
|
||||
</tbody>
|
||||
</table>
|
||||
*/
|
||||
|
||||
this.outer_table = $(document.createElement("table"));
|
||||
this.outer_table.addClass("egwGridView_outer");
|
||||
this.outer_thead = $(document.createElement("thead"));
|
||||
this.outer_tbody = $(document.createElement("tbody"));
|
||||
this.outer_tr = $(document.createElement("tr"));
|
||||
this.outer_head_tr = $(document.createElement("tr"));
|
||||
|
||||
this.outer_table.append(this.outer_thead, this.outer_tbody);
|
||||
this.outer_tbody.append(this.outer_tr);
|
||||
this.outer_thead.append(this.outer_head_tr);
|
||||
}
|
||||
|
||||
egwGridViewOuter.prototype.buildBaseHeader = function()
|
||||
{
|
||||
// Build the "option-column", if this hasn't been done yet
|
||||
if (!this.optcol)
|
||||
{
|
||||
// Build the "select columns" icon
|
||||
this.selectcols = $(document.createElement("span"));
|
||||
this.selectcols.addClass("selectcols");
|
||||
|
||||
// Build the option column
|
||||
this.optcol = $(document.createElement("th"));
|
||||
this.optcol.addClass("optcol");
|
||||
this.optcol.append(this.selectcols);
|
||||
}
|
||||
|
||||
// Create the head columns
|
||||
this.outer_head_tr.empty();
|
||||
this.headerColumns = [];
|
||||
|
||||
for (var i = 0; i < this.columns.length; i++)
|
||||
{
|
||||
col = this.columns[i];
|
||||
|
||||
// Create the column element and insert it into the DOM-Tree
|
||||
var column = $(document.createElement("th"));
|
||||
column.html(col.caption);
|
||||
this.outer_head_tr.append(column);
|
||||
|
||||
// Set the width of the column
|
||||
var border = column.outerWidth() - column.width();
|
||||
column.css("width", (col.width - border) + "px");
|
||||
col.drawnWidth = column.outerWidth();
|
||||
|
||||
this.headerColumns.push(column);
|
||||
}
|
||||
|
||||
// Append the option column and set its width of the last column
|
||||
this.outer_head_tr.append(this.optcol);
|
||||
|
||||
this.optcol.css("width", this.scrollbarWidth - this.optcol.outerWidth()
|
||||
+ this.optcol.width() + 1); // The "1" is a "security pixel" which prevents a horizontal scrollbar form occuring on IE7
|
||||
|
||||
this.headerHeight = this.outer_thead.height();
|
||||
}
|
||||
|
||||
egwGridViewOuter.prototype.getScrollbarWidth = function()
|
||||
{
|
||||
if (EGW_GRID_SCROLLBAR_WIDTH === false)
|
||||
{
|
||||
// Create a temporary td and two div, which are inserted into the dom-tree
|
||||
var td = $(document.createElement("td"));
|
||||
var div_outer = $(document.createElement("div"));
|
||||
var div_inner = $(document.createElement("div"));
|
||||
|
||||
// The outer div has a fixed size and "overflow" set to auto. When the second
|
||||
// div is inserted, it will be forced to display a scrollbar.
|
||||
div_outer.css("height", "100px");
|
||||
div_outer.css("width", "100px");
|
||||
div_outer.css("overflow", "auto");
|
||||
|
||||
div_inner.css("height", "1000px");
|
||||
|
||||
this.outer_tr.append(td);
|
||||
td.append(div_outer);
|
||||
div_outer.append(div_inner);
|
||||
|
||||
// Store the scrollbar width statically.
|
||||
EGW_GRID_SCROLLBAR_WIDTH = div_outer.outerWidth() - div_inner.outerWidth();
|
||||
|
||||
// Remove the temporary elements again.
|
||||
this.outer_tr.empty();
|
||||
}
|
||||
|
||||
return EGW_GRID_SCROLLBAR_WIDTH;
|
||||
}
|
||||
|
||||
egwGridViewOuter.prototype.setHeight = function(_h)
|
||||
{
|
||||
this.grid.setScrollHeight(_h - this.headerHeight);
|
||||
}
|
||||
|
||||
|
||||
/** -- egwGridViewContainer Interface -- **/
|
||||
@ -76,11 +284,11 @@ function egwGridViewContainer(_grid, _heightChangeProc)
|
||||
this.parentNode = null;
|
||||
this.columns = [];
|
||||
this.height = false;
|
||||
this.assumedHeight = false;
|
||||
this.index = 0;
|
||||
this.viewArea = false;
|
||||
|
||||
this.doInsertIntoDOM = null;
|
||||
this.doUpdateColumns = null;
|
||||
this.doSetViewArea = null;
|
||||
}
|
||||
|
||||
@ -164,16 +372,6 @@ egwGridViewContainer.prototype.insertIntoDOM = function(_parentNode, _columns)
|
||||
return false;
|
||||
}
|
||||
|
||||
egwGridViewContainer.prototype.updateColumns = function(_columns)
|
||||
{
|
||||
this.columns = _columns;
|
||||
if (_parentNode)
|
||||
{
|
||||
return egwCallAbstract(this, this.doUpdateColumns, arguments);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
egwGridViewContainer.prototype.setViewArea = function(_area, _force)
|
||||
{
|
||||
// Calculate the relative coordinates and pass those to the implementation
|
||||
@ -217,16 +415,20 @@ egwGridViewContainer.prototype.setPosition = function(_top)
|
||||
/**
|
||||
* Returns the height of the container in pixels and zero if the element is not
|
||||
* visible. The height is clamped to positive values.
|
||||
*
|
||||
* TODO: This function consumes 70-80% of the update time! Do something to improve
|
||||
* this!
|
||||
*/
|
||||
egwGridViewContainer.prototype.getHeight = function()
|
||||
{
|
||||
if (this.visible && this.parentNode)
|
||||
{
|
||||
if (this.height === false)
|
||||
if (this.height === false && this.assumedHeight === false)
|
||||
{
|
||||
this.height = this.parentNode.outerHeight();
|
||||
}
|
||||
return this.height;
|
||||
|
||||
return this.height !== false ? this.height : this.assumedHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -236,6 +438,7 @@ egwGridViewContainer.prototype.getHeight = function()
|
||||
|
||||
egwGridViewContainer.prototype.invalidateHeightCache = function()
|
||||
{
|
||||
this.assumedHeight = false;
|
||||
this.height = false;
|
||||
}
|
||||
|
||||
@ -286,11 +489,16 @@ egwGridViewContainer.prototype.getArea = function()
|
||||
|
||||
/** -- egwGridViewGrid Class -- **/
|
||||
|
||||
var EGW_GRID_VIEW_EXT = 25;
|
||||
var EGW_GRID_MAX_CYCLES = 10;
|
||||
var EGW_GRID_SCROLL_TIMEOUT = 100;
|
||||
var EGW_GRID_UPDATE_HEIGHTS_TIMEOUT = 50;
|
||||
|
||||
/**
|
||||
* egwGridViewGrid is the container for egwGridViewContainer objects, but itself
|
||||
* implements the egwGridViewContainer interface.
|
||||
*/
|
||||
function egwGridViewGrid(_grid, _heightChangeProc, _scrollable)
|
||||
function egwGridViewGrid(_grid, _heightChangeProc, _scrollable, _outer)
|
||||
{
|
||||
if (typeof _scrollable == "undefined")
|
||||
{
|
||||
@ -307,6 +515,7 @@ function egwGridViewGrid(_grid, _heightChangeProc, _scrollable)
|
||||
container.scrollHeight = 100;
|
||||
container.scrollEvents = 0;
|
||||
container.didUpdate = false;
|
||||
container.updateIndex = 0;
|
||||
container.setupContainer = egwGridViewGrid_setupContainer;
|
||||
container.insertContainer = egwGridViewGrid_insertContainer;
|
||||
container.removeContainer = egwGridViewGrid_removeContainer;
|
||||
@ -314,18 +523,35 @@ function egwGridViewGrid(_grid, _heightChangeProc, _scrollable)
|
||||
container.heightChangeHandler = egwGridViewGrid_heightChangeHandler;
|
||||
container.setScrollHeight = egwGridViewGrid_setScrollHeight;
|
||||
container.scrollCallback = egwGridViewGrid_scrollCallback;
|
||||
container.empty = egwGridViewGrid_empty;
|
||||
container.getOuter = egwGridViewGrid_getOuter;
|
||||
container.updateAssumedHeights = egwGridViewGrid_updateAssumedHeights;
|
||||
container.children = [];
|
||||
container.outer = _outer;
|
||||
|
||||
// Overwrite the abstract container interface functions
|
||||
container.invalidateHeightCache = egwGridViewGrid_invalidateHeightCache;
|
||||
container.getHeight = egwGridViewGrid_getHeight;
|
||||
container.doUpdateColumns = egwGridViewGrid_doUpdateColumns;
|
||||
container.doInsertIntoDOM = egwGridViewGrid_doInsertIntoDOM;
|
||||
container.doSetViewArea = egwGridViewGrid_doSetviewArea;
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
function egwGridViewGrid_getOuter()
|
||||
{
|
||||
if (this.outer)
|
||||
{
|
||||
return this.outer;
|
||||
}
|
||||
else if (this.grid)
|
||||
{
|
||||
return this.grid.getOuter();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function egwGridViewGrid_setupContainer()
|
||||
{
|
||||
/*
|
||||
@ -352,10 +578,11 @@ function egwGridViewGrid_setupContainer()
|
||||
this.scrollarea.addClass("egwGridView_scrollarea");
|
||||
this.scrollarea.css("height", this.scrollHeight + "px");
|
||||
this.scrollarea.scroll(this, function(e) {
|
||||
window.setTimeout(function() {
|
||||
e.data.scrollEvents++;
|
||||
e.data.scrollCallback(e.data.scrollEvents);
|
||||
}, 50);
|
||||
var cnt = e.data.scrollEvents;
|
||||
window.setTimeout(function() {
|
||||
e.data.scrollCallback(cnt);
|
||||
}, EGW_GRID_SCROLL_TIMEOUT);
|
||||
});
|
||||
}
|
||||
|
||||
@ -388,10 +615,6 @@ function egwGridViewGrid_setScrollHeight(_value)
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
EGW_GRID_VIEW_EXT = 50;
|
||||
EGW_GRID_MAX_CYCLES = 10;
|
||||
|
||||
function egwGridViewGrid_scrollCallback(_event)
|
||||
{
|
||||
if ((typeof _event == "undefined" || _event == this.scrollEvents) && this.scrollarea)
|
||||
@ -399,26 +622,88 @@ function egwGridViewGrid_scrollCallback(_event)
|
||||
var cnt = 0;
|
||||
var area = egwArea(this.scrollarea.scrollTop() - EGW_GRID_VIEW_EXT,
|
||||
this.scrollHeight + EGW_GRID_VIEW_EXT * 2);
|
||||
do {
|
||||
cnt++;
|
||||
this.didUpdate = false;
|
||||
|
||||
// Set view area sets the "didUpdate" variable to false
|
||||
this.setViewArea(area);
|
||||
} while (this.didUpdate && cnt < EGW_GRID_MAX_CYCLES);
|
||||
|
||||
// console.log(cnt);
|
||||
|
||||
if (cnt == EGW_GRID_MAX_CYCLES)
|
||||
{
|
||||
if (this.console && this.console.info)
|
||||
{
|
||||
this.console.info("Too many update cycles. Aborting.")
|
||||
}
|
||||
}
|
||||
|
||||
this.scrollEvents = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function egwGridViewGrid_updateAssumedHeights(_maxCount)
|
||||
{
|
||||
var traversed = 0;
|
||||
var cnt = _maxCount;
|
||||
var outer = this.getOuter();
|
||||
|
||||
try
|
||||
{
|
||||
this.inUpdate = true;
|
||||
|
||||
while (traversed < this.children.length && cnt > 0)
|
||||
{
|
||||
// Clamp the update index
|
||||
if (this.updateIndex >= this.children.length)
|
||||
{
|
||||
this.updateIndex = 0;
|
||||
}
|
||||
|
||||
// Get the child at the given position and check whether it used
|
||||
// an assumed height
|
||||
var child = this.children[this.updateIndex];
|
||||
if (child.assumedHeight !== false)
|
||||
{
|
||||
// Get the difference (delta) between the assumed and the real
|
||||
// height
|
||||
var oldHeight = child.assumedHeight;
|
||||
child.invalidateHeightCache();
|
||||
var newHeight = child.getHeight();
|
||||
outer.addHeightToAvg(newHeight);
|
||||
|
||||
// Offset the position of all following elements by the delta.
|
||||
var delta = newHeight - oldHeight;
|
||||
if (Math.abs(delta) > 0.001)
|
||||
{
|
||||
for (var j = this.updateIndex + 1; j < this.children.length; j++)
|
||||
{
|
||||
this.children[j].offsetPosition(delta);
|
||||
}
|
||||
}
|
||||
|
||||
// We've now worked on one element with assumed height, decrease
|
||||
// the counter
|
||||
cnt--;
|
||||
}
|
||||
|
||||
// Increment the element index and the count of checked elements
|
||||
this.updateIndex++;
|
||||
traversed++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.inUpdate = false;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
if (cnt == 0)
|
||||
{
|
||||
// If the maximum-update-count has been exhausted, retrigger this function
|
||||
window.setTimeout(function() {
|
||||
self.updateAssumedHeights(_maxCount);
|
||||
}, EGW_GRID_UPDATE_HEIGHTS_TIMEOUT);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, all elements have been checked - we'll now call "setViewArea"
|
||||
// which may check whether new objects are now in the currently visible range
|
||||
window.setTimeout(function() {
|
||||
self.setViewArea(self.viewArea);
|
||||
}, EGW_GRID_UPDATE_HEIGHTS_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
function egwGridViewGrid_insertContainer(_after, _class, _params)
|
||||
{
|
||||
this.didUpdate = true;
|
||||
@ -461,7 +746,8 @@ function egwGridViewGrid_insertContainer(_after, _class, _params)
|
||||
|
||||
// Offset the position of all following elements by the height of the container
|
||||
// and move the index of those elements
|
||||
var height = container.getHeight();
|
||||
var height = this.getOuter().avgRowHeight; //container.getHeight(); // This took a lot of time.
|
||||
container.assumedHeight = height;
|
||||
for (var i = idx + 1; i < this.children.length; i++)
|
||||
{
|
||||
this.children[i].offsetPosition(height);
|
||||
@ -495,6 +781,17 @@ function egwGridViewGrid_removeContainer(_container)
|
||||
this.children.splice(idx, 1);
|
||||
}
|
||||
|
||||
function egwGridViewGrid_empty(_newColumns)
|
||||
{
|
||||
if (typeof _newColumns != "undefined")
|
||||
{
|
||||
this.columns = _newColumns;
|
||||
}
|
||||
|
||||
this.innerNode.empty();
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
function egwGridViewGrid_addContainer(_class)
|
||||
{
|
||||
// Insert the container at the beginning of the list.
|
||||
@ -541,7 +838,8 @@ function egwGridViewGrid_heightChangeHandler(_elem)
|
||||
this.didUpdate = true;
|
||||
|
||||
// Get the height-change
|
||||
var oldHeight = _elem.height === false ? 0 : _elem.height;
|
||||
var oldHeight = _elem.assumedHeight !== false ? _elem.assumedHeight :
|
||||
(_elem.height === false ? 0 : _elem.height);
|
||||
_elem.invalidateHeightCache(false);
|
||||
var newHeight = _elem.getHeight();
|
||||
var offs = newHeight - oldHeight;
|
||||
@ -557,29 +855,19 @@ function egwGridViewGrid_heightChangeHandler(_elem)
|
||||
this.callHeightChangeProc();
|
||||
}
|
||||
|
||||
|
||||
function egwGridViewGrid_doInsertIntoDOM()
|
||||
{
|
||||
// Generate the DOM Nodes and append the outer node to the parent node
|
||||
this.setupContainer();
|
||||
this.parentNode.append(this.outerNode);
|
||||
|
||||
this.doUpdateColumns();
|
||||
}
|
||||
|
||||
function egwGridViewGrid_doUpdateColumns(_columns)
|
||||
{
|
||||
this.outerNode.attr("colspan", this.columns.length);
|
||||
|
||||
for (var i = 0; i < this.children.length; i++)
|
||||
{
|
||||
this.children[i].doUpdateColumns(_columns)
|
||||
}
|
||||
this.outerNode.attr("colspan", this.columns.length + (this.scrollable ? 1 : 0));
|
||||
}
|
||||
|
||||
function egwGridViewGrid_doSetviewArea(_area)
|
||||
{
|
||||
// Do a binary search for elements which are inside the given area
|
||||
this.didUpdate = false;
|
||||
var elem = null;
|
||||
var elems = [];
|
||||
|
||||
@ -651,6 +939,18 @@ function egwGridViewGrid_doSetviewArea(_area)
|
||||
}
|
||||
|
||||
this.inUpdate = false;
|
||||
|
||||
// If an update has been done, check whether any height assumptions have been
|
||||
// done. This procedure is executed with some delay, as this gives the browser
|
||||
// the time to insert the newly generated objects into the DOM-Tree and allows
|
||||
// us to read their height at a very fast rate.
|
||||
if (this.didUpdate)
|
||||
{
|
||||
var self = this;
|
||||
window.setTimeout(function() {
|
||||
self.updateAssumedHeights(20);
|
||||
}, EGW_GRID_UPDATE_HEIGHTS_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
/** -- egwGridViewRow Class -- **/
|
||||
@ -663,40 +963,126 @@ function egwGridViewRow(_grid, _heightChangeProc, _item)
|
||||
// source
|
||||
container.item = _item;
|
||||
|
||||
// Set a few new functions/properties
|
||||
container.isOdd = 0;
|
||||
container.aoiSetup = egwGridViewRow_aoiSetup;
|
||||
container.getAOI = egwGridViewRow_getAOI;
|
||||
container.checkOdd = egwGridViewRow_checkOdd;
|
||||
|
||||
// Overwrite the inherited abstract functions
|
||||
container.doInsertIntoDOM = egwGridViewRow_doInsertIntoDOM;
|
||||
container.doSetViewArea = egwGridViewRow_doSetViewArea;
|
||||
container.doUpdateColumns = egwGridViewRow_doUpdateColumns;
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
function egwGridViewRow_doInsertIntoDOM()
|
||||
/**
|
||||
* Creates AOI instance of the item and overwrites all necessary functions.
|
||||
*/
|
||||
function egwGridViewRow_aoiSetup()
|
||||
{
|
||||
this.doUpdateColumns();
|
||||
this.aoi = new egwActionObjectInterface();
|
||||
|
||||
// The default state of an aoi is EGW_AO_STATE_NORMAL || EGW_AO_STATE_VISIBLE -
|
||||
// egwGridItems are not necessarily visible by default
|
||||
this.aoi._state = EGW_AO_STATE_NORMAL;
|
||||
this.aoi.row = this;
|
||||
this.aoi.doSetState = egwGridViewRow_aoiSetState;
|
||||
this.aoi.getDOMNode = egwGridViewRow_aoiGetDOMNode;
|
||||
}
|
||||
|
||||
function egwGridViewRow_doUpdateColumns()
|
||||
function egwGridViewRow_aoiSetState(_state, _shiftState)
|
||||
{
|
||||
if (this.row.parentNode)
|
||||
{
|
||||
this.row.parentNode.toggleClass("selected", egwBitIsSet(_state,
|
||||
EGW_AO_STATE_SELECTED));
|
||||
this.row.parentNode.toggleClass("focused", egwBitIsSet(_state,
|
||||
EGW_AO_STATE_FOCUSED));
|
||||
}
|
||||
}
|
||||
|
||||
function egwGridViewRow_aoiGetDOMNode()
|
||||
{
|
||||
return this.row.parentNode ? this.row.parentNode : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actionObjectInterface object of this grid item.
|
||||
*/
|
||||
function egwGridViewRow_getAOI()
|
||||
{
|
||||
return this.aoi;
|
||||
}
|
||||
|
||||
var
|
||||
EGW_GRID_VIEW_ROW_BORDER = false;
|
||||
|
||||
function egwGridViewRow_doInsertIntoDOM()
|
||||
{
|
||||
this.parentNode.empty();
|
||||
|
||||
// Setup the aoi and inform the item about it
|
||||
if (!this.aoi)
|
||||
{
|
||||
this.aoiSetup();
|
||||
this.item.setGridViewObj(this);
|
||||
}
|
||||
|
||||
// Check whether this element is odd
|
||||
this.checkOdd();
|
||||
|
||||
// Read the column data
|
||||
/*var ids = [];
|
||||
for (var i = 0; i < this.columns.length; i++)
|
||||
{
|
||||
var td = $(document.createElement("td"));
|
||||
td.text(this.item + ", col" + i);
|
||||
ids.push(this.columns[i].id);
|
||||
}
|
||||
|
||||
data = this.item.getData(ids);*/
|
||||
|
||||
for (var i = 0; i < this.columns.length; i++)
|
||||
{
|
||||
var col = this.columns[i];
|
||||
var td = $(document.createElement("td"));
|
||||
|
||||
//if (typeof data[this.columns[i].id] != "undefined")
|
||||
{
|
||||
td.html("col" + i);
|
||||
}
|
||||
this.parentNode.append(td);
|
||||
|
||||
// Set the column width
|
||||
if (EGW_GRID_VIEW_ROW_BORDER === false)
|
||||
{
|
||||
EGW_GRID_VIEW_ROW_BORDER = td.outerWidth() - td.width();
|
||||
}
|
||||
td.css("width", col.drawnWidth - EGW_GRID_VIEW_ROW_BORDER);
|
||||
|
||||
}
|
||||
|
||||
this.checkViewArea();
|
||||
}
|
||||
|
||||
function egwGridViewRow_doSetViewArea()
|
||||
function egwGridViewRow_checkOdd()
|
||||
{
|
||||
//TODO: Load the data for the columns and load it.
|
||||
if (this.item && this.parentNode)
|
||||
{
|
||||
// Update the "odd"-Class of the item
|
||||
var odd = this.item.isOdd();
|
||||
|
||||
if (this.isOdd === 0 || this.isOdd != odd)
|
||||
{
|
||||
$(this.parentNode).toggleClass("odd", odd);
|
||||
this.isOdd = odd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function egwGridViewRow_doSetViewArea()
|
||||
{
|
||||
this.checkOdd();
|
||||
}
|
||||
|
||||
|
||||
/** -- egwGridViewSpacer Class -- **/
|
||||
@ -719,7 +1105,6 @@ function egwGridViewSpacer(_grid, _heightChangeProc, _itemHeight)
|
||||
// Overwrite the inherited functions
|
||||
container.doInsertIntoDOM = egwGridViewSpacer_doInsertIntoDOM;
|
||||
container.doSetViewArea = egwGridViewSpacer_doSetViewArea;
|
||||
container.doUpdateColumns = egwGridViewSpacer_doUpdateColumns;
|
||||
|
||||
return container;
|
||||
}
|
||||
@ -735,19 +1120,27 @@ function egwGridViewSpacer_setItemList(_items)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the spacer DOM-Node and inserts it into the DOM-Tree.
|
||||
*/
|
||||
function egwGridViewSpacer_doInsertIntoDOM()
|
||||
{
|
||||
this.domNode = $(document.createElement("td"));
|
||||
this.domNode.addClass("egwGridView_spacer");
|
||||
this.domNode.css("height", (this.items.length * this.itemHeight) + "px");
|
||||
this.domNode.attr("colspan", this.columns.length);
|
||||
|
||||
this.parentNode.append(this.domNode);
|
||||
|
||||
this.doUpdateColumns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks which elements this spacer contains are inside the given area and
|
||||
* creates those.
|
||||
*/
|
||||
function egwGridViewSpacer_doSetViewArea()
|
||||
{
|
||||
var avgHeight = this.grid.getOuter().avgRowHeight;
|
||||
|
||||
// Get all items which are in the view area
|
||||
var top = Math.max(0, Math.floor(this.viewArea.top / this.itemHeight));
|
||||
var bot = Math.min(this.items.length, Math.ceil(this.viewArea.bottom / this.itemHeight));
|
||||
@ -763,19 +1156,20 @@ function egwGridViewSpacer_doSetViewArea()
|
||||
// Insert the new rows in the parent grid in front of the spacer container
|
||||
for (var i = it_mid.length - 1; i >= 0; i--)
|
||||
{
|
||||
this.grid.insertContainer(idx - 1, egwGridViewRow, it_mid[i]);
|
||||
this.grid.insertContainer(idx - 1, it_mid[i].type, it_mid[i]);
|
||||
}
|
||||
|
||||
// If top was greater than 0, insert a new spacer in front of the
|
||||
if (it_top.length > 0)
|
||||
{
|
||||
var spacer = this.grid.insertContainer(idx - 1, egwGridViewSpacer, this.itemHeight);
|
||||
var spacer = this.grid.insertContainer(idx - 1, egwGridViewSpacer, avgHeight);
|
||||
spacer.setItemList(it_top)
|
||||
}
|
||||
|
||||
// If there are items left at the bottom of the spacer, set theese as items of this spacer
|
||||
if (it_bot.length > 0)
|
||||
{
|
||||
this.itemHeight = avgHeight;
|
||||
this.setItemList(it_bot);
|
||||
}
|
||||
else
|
||||
@ -784,9 +1178,4 @@ function egwGridViewSpacer_doSetViewArea()
|
||||
}
|
||||
}
|
||||
|
||||
function egwGridViewSpacer_doUpdateColumns()
|
||||
{
|
||||
this.domNode.attr("colspan", this.columns.length);
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,7 +8,8 @@ body, td, th {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
table.egwGridView_grid {
|
||||
.egwGridView_grid {
|
||||
table-layout: fixed;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
@ -19,12 +20,12 @@ table.egwGridView_grid {
|
||||
}
|
||||
|
||||
.egwGridView_spacer {
|
||||
display: block;
|
||||
background-image: url(imgs/non_loaded_bg.png);
|
||||
background-position: top left;
|
||||
}
|
||||
|
||||
.egwGridView_outer {
|
||||
table-layout: fixed;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
padding: 0;
|
||||
@ -37,10 +38,15 @@ table.egwGridView_grid {
|
||||
}
|
||||
|
||||
.egwGridView_grid td, .egwGridView_grid tr {
|
||||
padding: 2px;
|
||||
border-right: 1px solid silver;
|
||||
padding: 4px 3px 4px 4px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.egwGridView_grid tr.odd {
|
||||
background-color: #F1F1F1;
|
||||
}
|
||||
|
||||
.egwGridView_outer thead th {
|
||||
background-color: #E0E0E0;
|
||||
font-weight: normal;
|
||||
@ -55,6 +61,11 @@ table.egwGridView_grid {
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
.egwGridView_outer thead th.optcol {
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
@ -165,11 +176,6 @@ table.egwGridView_grid {
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.grid td {
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
|
BIN
phpgwapi/js/egw_action/test/imgs/header_overlay.png
Normal file
BIN
phpgwapi/js/egw_action/test/imgs/header_overlay.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 431 B |
97
phpgwapi/js/egw_action/test/imgs/header_overlay.svg
Normal file
97
phpgwapi/js/egw_action/test/imgs/header_overlay.svg
Normal file
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="128"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.0 r9654"
|
||||
sodipodi:docname="header_overlay.svg"
|
||||
inkscape:export-filename="/home/andreas/source/egroupware/trunk/egroupware/phpgwapi/js/egw_action/test/imgs/header_overlay.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="linearGradient3755">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3757" />
|
||||
<stop
|
||||
id="stop3763"
|
||||
offset="0.21804152"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="0.82098258"
|
||||
id="stop3765" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3759" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3755"
|
||||
id="linearGradient3761"
|
||||
x1="30.557114"
|
||||
y1="924.32532"
|
||||
x2="30.557114"
|
||||
y2="1052.4043"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0031348,0,0,1.0015649,-0.10031348,-1.5466455)" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="43.211207"
|
||||
inkscape:cy="64.846324"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1600"
|
||||
inkscape:window-height="823"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-924.36218)">
|
||||
<rect
|
||||
style="fill:url(#linearGradient3761);fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="rect2985"
|
||||
width="64"
|
||||
height="128"
|
||||
x="0"
|
||||
y="924.36218" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
BIN
phpgwapi/js/egw_action/test/imgs/selectcols.png
Normal file
BIN
phpgwapi/js/egw_action/test/imgs/selectcols.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 215 B |
@ -22,8 +22,7 @@
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
</div>
|
||||
<div id="container" style="height: 300px"></div>
|
||||
<script>
|
||||
var grid = null;
|
||||
var actionManager = null;
|
||||
@ -136,7 +135,32 @@
|
||||
},
|
||||
{
|
||||
"id": "mime",
|
||||
"caption": "File-Type/MIME"
|
||||
"caption": "File-Type/MIME with very long column header caption"
|
||||
},
|
||||
{
|
||||
"id": "atime",
|
||||
"caption": "atime",
|
||||
"width": "40px"
|
||||
},
|
||||
{
|
||||
"id": "ctime",
|
||||
"caption": "ctime",
|
||||
"width": "40px"
|
||||
},
|
||||
{
|
||||
"id": "mtime",
|
||||
"caption": "mtime",
|
||||
"width": "40px"
|
||||
},
|
||||
{
|
||||
"id": "owner",
|
||||
"caption": "owner",
|
||||
"width": "40px"
|
||||
},
|
||||
{
|
||||
"id": "group",
|
||||
"caption": "group",
|
||||
"width": "40px"
|
||||
}
|
||||
]
|
||||
);
|
||||
@ -157,13 +181,13 @@
|
||||
grid.beginUpdate();
|
||||
function recurse_add(item, obj, depth)
|
||||
{
|
||||
for (var i = 1; i <= 6; i++)
|
||||
for (var i = 1; i <= 20; i++)
|
||||
{
|
||||
var id = "file" + i;
|
||||
var it = item.addItem(id, "Test" + i, "imgs/mime16_directory.png", info);
|
||||
var _obj = obj.addObject(id, it.getAOI());
|
||||
_obj.updateActionLinks(listboxFolderLinks);
|
||||
if (depth < 4)
|
||||
if (depth < 0)
|
||||
recurse_add(it, _obj, depth + 1);
|
||||
}
|
||||
}
|
||||
|
106
phpgwapi/js/egw_action/test/test_grid_view.html
Normal file
106
phpgwapi/js/egw_action/test/test_grid_view.html
Normal file
@ -0,0 +1,106 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>Grid Test</title>
|
||||
|
||||
<!-- Basic action stuff -->
|
||||
<script src="../egw_action.js"></script>
|
||||
<script src="../egw_action_common.js"></script>
|
||||
|
||||
<!-- Grid stuff -->
|
||||
<script src="../egw_grid_view.js"></script>
|
||||
<script src="../egw_grid_columns.js"></script>
|
||||
<script src="../egw_grid_data.js"></script>
|
||||
<script src="../egw_grid.js"></script>
|
||||
|
||||
<script src="js/jquery.js"></script>
|
||||
<link rel="stylesheet" href="grid.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test for dynamically displaying and loading grid lines (0,1 Mio Entries)</h1>
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
var grid = null;
|
||||
var actionManager = null;
|
||||
var objectManager = null;
|
||||
|
||||
var columns =
|
||||
[
|
||||
{
|
||||
"caption": "Name",
|
||||
"width": "33%",
|
||||
"type": EGW_COL_TYPE_NAME_ICON_FIXED
|
||||
},
|
||||
{
|
||||
"id": "size",
|
||||
"caption": "Size"
|
||||
},
|
||||
{
|
||||
"id": "rights",
|
||||
"caption": "UNIX Filerights",
|
||||
"default": "---------"
|
||||
},
|
||||
{
|
||||
"id": "mime",
|
||||
"caption": "File-Type/MIME"
|
||||
},
|
||||
{
|
||||
"id": "atime",
|
||||
"caption": "atime"
|
||||
},
|
||||
{
|
||||
"id": "ctime",
|
||||
"caption": "ctime"
|
||||
},
|
||||
{
|
||||
"id": "mtime",
|
||||
"caption": "mtime"
|
||||
},
|
||||
{
|
||||
"id": "owner",
|
||||
"caption": "owner"
|
||||
},
|
||||
{
|
||||
"id": "group",
|
||||
"caption": "group"
|
||||
}
|
||||
];
|
||||
|
||||
function fetchDataProc(_elems, _columns)
|
||||
{
|
||||
console.log("Fetch Data Proc: ", _elems, _columns);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
actionManager = new egwActionManager();
|
||||
objectManager = new egwActionObjectManager("", actionManager);
|
||||
|
||||
grid = new egwGrid($("#container"), columns, objectManager, fetchDataProc,
|
||||
window);
|
||||
grid.dataRoot.loadData(
|
||||
[
|
||||
{
|
||||
"entryType": EGW_DATA_TYPE_RANGE,
|
||||
"prefix": "root_elem_",
|
||||
"count": 100000
|
||||
}
|
||||
]
|
||||
);
|
||||
grid.resize(1500, 650);
|
||||
});
|
||||
|
||||
function check_positions()
|
||||
{
|
||||
var g = grid.gridOuter.grid;
|
||||
var delta = - g.scrollarea.offset().top;
|
||||
for (var i = 0; i < g.children.length; i++)
|
||||
{
|
||||
var rtop = g.children[i].parentNode.offset().top + g.scrollarea.scrollTop() - g.scrollarea.offset().top;
|
||||
var itop = g.children[i].position;
|
||||
|
||||
console.log(Math.round(itop - rtop));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user