New dataview version and integration into the nextmatch widget

This commit is contained in:
Andreas Stöckel 2012-03-23 12:20:57 +00:00
parent 23f221456f
commit a9c1112dc1
22 changed files with 6279 additions and 5226 deletions

View File

@ -651,3 +651,11 @@ function et2_rangeIntersectDir(_ar1, _ar2)
return 0;
}
/**
* Returns whether two ranges are equal.
*/
function et2_rangeEqual(_ar1, _ar2)
{
return _ar1.top === _ar2.top && _ar1.bottom === _ar2.bottom;
}

View File

@ -1,12 +1,12 @@
/**
* eGroupWare eTemplate2 - Class which generates the outer container for the grid
* 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 2011
* @copyright Stylite 2011-2012
* @version $Id$
*/
@ -16,14 +16,20 @@
jquery.jquery;
et2_core_common;
et2_dataview_model_columns;
et2_dataview_view_rowProvider;
et2_dataview_view_grid;
et2_dataview_view_resizeable;
*/
/**
* Base view class which is responsible for displaying a grid view element.
* The et2_dataview class is the main class for displaying a dataview. The
* dataview class manages the creation of the outer html nodes (like the table,
* header, etc.) and contains the root container: an instance of
* et2_dataview_view_grid, which can be accessed using the "grid" property of
* this object.
*/
var et2_dataview_gridContainer = Class.extend({
var et2_dataview = Class.extend({
/**
* Constant which regulates the column padding.
@ -49,11 +55,10 @@ var et2_dataview_gridContainer = Class.extend({
* Constructor for the grid container
* @param object _parentNode is the DOM-Node into which the grid view will be inserted
*/
init: function(_parentNode, _dataProvider, _egw) {
init: function(_parentNode, _egw) {
// Copy the arguments
this.parentNode = $j(_parentNode);
this.dataProvider = _dataProvider;
this.egw = _egw;
// Initialize some variables
@ -344,7 +349,9 @@ var et2_dataview_gridContainer = Class.extend({
// Add the full row and spacer class
this.egw.css(".egwGridView_grid ." + this.uniqueId + "_div_fullRow",
"width: " + (totalWidth - this.columnBorderWidth - 1) + "px; border-right-width: 0 !important;");
"width: " + (totalWidth - this.columnBorderWidth - 2) + "px; border-right-width: 0 !important;");
this.egw.css(".egwGridView_outer ." + this.uniqueId + "_td_fullRow",
"border-right-width: 0 !important;");
this.egw.css(".egwGridView_outer ." + this.uniqueId + "_spacer_fullRow",
"width: " + (totalWidth - 1) + "px; border-right-width: 0 !important;");
},
@ -430,11 +437,12 @@ var et2_dataview_gridContainer = Class.extend({
this.rowProvider = new et2_dataview_rowProvider(this.uniqueId, colIds);
// Create the grid class and pass "19" as the starting average row height
this.grid = new et2_dataview_grid(null, this.uniqueId, colIds,
this.dataProvider, this.rowProvider, 19);
this.grid = new et2_dataview_grid(null, this.egw, this.rowProvider, 19);
// Insert the grid into the DOM-Tree
this.containerTr.append(this.grid.getJNode());
var tr = $j(this.grid._nodes[0]);
this.containerTr.replaceWith(tr);
this.containerTr = tr;
},
/* --- Code for calculating the browser/css depending widths --- */

View File

@ -0,0 +1,422 @@
/**
* eGroupWare eTemplate2
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011-2012
* @version $Id$
*/
/*egw:uses
et2_core_common;
et2_core_inheritance;
et2_dataview_interfaces;
et2_dataview_view_row;
*/
/**
* The fetch timeout specifies the time during which the controller tries to
* consolidate requests for rows.
*/
var ET2_DATAVIEW_FETCH_TIMEOUT = 50;
/**
* The et2_dataview_controller class is the intermediate layer between a grid
* instance and the corresponding data source. It manages updating the grid,
* as well as inserting and deleting rows.
*/
var et2_dataview_controller = Class.extend({
/**
* Constructor of the et2_dataview_controller, connects to the grid
* callback.
*/
init: function (_grid, _dataProvider, _rowCallback, _context)
{
// Copy the given arguments
this._grid = _grid;
this._dataProvider = _dataProvider;
this._rowCallback = _rowCallback;
this._rowContext = _context;
// Initialize the "index map" which contains all currently displayed
// containers hashed by the "index"
this._indexMap = {};
// "lastModified" contains the last timestap which was returned from the
// server.
this._lastModified = null;
// Register the dataFetch callback
this._grid.setDataCallback(this._gridCallback, this);
},
/**
* The update function queries the server for changes in the currently
* managed index range -- those changes are then merged into the current
* view without a complete rebuild of every row.
*/
update: function () {
// Get the currently visible range from the grid
var range = this._grid.getIndexRange();
// Force range.top and range.bottom to contain an integer
if (range.top === false)
{
range.top = range.bottom = 0;
}
// Require that range from the server
this._queueFetch(range.top, range.bottom - range.top + 1,
this._lastModified !== null);
},
/**
* Rebuilds the complete grid.
*/
reset: function () {
// Throw away all internal mappings and reset the timestamp
this._indexMap = {};
this._lastModified = null;
// Clear the grid
this._grid.clear();
// Update the data
this.update();
},
/* -- PRIVATE FUNCTIONS -- */
_getIndexEntry: function (_idx) {
// Create an entry in the index map if it does not exist yet
if (typeof this._indexMap[_idx] === "undefined")
{
this._indexMap[_idx] = {
"row": null,
"uid": null
};
}
// Always update the index of the entries before returning them. This is
// neccessary, as when we remove the uid from an entry without row, its
// index does not get updated any further
this._indexMap[_idx]["idx"] = _idx;
return this._indexMap[_idx];
},
/**
* Inserts a new data row into the grid. index and uid are derived from the
* given management entry. If the data for the given uid does not exist yet,
* a "loading" placeholder will be shown instead. The function will do
* nothing if there already is a row associated to the entry. This function
* will not re-insert a row if the entry already had a row.
*
* @param _entry is the management entry for the index the row will be
* displayed at.
* @param _update specifies whether the row should be updated if _entry.row
* already exists.
* @return true, if all data for the row has been available, false
* otherwise.
*/
_insertDataRow: function (_entry, _update) {
// Abort if the entry already has a row but the _insert flag is not set
if (_entry.row && !_update)
{
return true;
}
// Context used for the callback functions
var ctx = {"self": this, "entry": _entry};
// Create a new row instance, if it does not exist yet
var createdRow = false;
if (!_entry.row)
{
createdRow = true;
_entry.row = new et2_dataview_row(this._grid);
_entry.row.setDestroyCallback(this._destroyCallback, ctx);
}
// Load the row data if we have a uid for the entry
this.hasData = false; // Gets updated by the _dataCallback
if (_entry.uid)
{
// Register the callback / immediately load the data
this._dataProvider.dataRegisterUID(_entry.uid, this._dataCallback,
ctx);
}
// Display the loading "row prototype" if we don't have data for the row
if (!this.hasData)
{
// Get the average height, the "-5" derives from the td padding
var avg = Math.round(this._grid.getAverageHeight() - 5) + "px";
var prototype = this._grid.getRowProvider().getPrototype("loading");
$j("div", prototype).css("height", avg);
var node = _entry.row.getJNode();
node.empty();
node.append(prototype.children());
}
// Insert the row into the table -- the same row must never be inserted
// twice into the grid, so this function only executes the following
// code only if it is a newly created row.
if (createdRow)
{
this._grid.insertRow(_entry.idx, _entry.row);
}
return this.hasData;
},
/**
* Function which gets called by the grid when data is requested.
*
* @param _idxStart is the index of the first row for which data is
* requested.
* @param _idxEnd is the index of the last requested row.
*/
_gridCallback: function (_idxStart, _idxEnd) {
var needsData = false;
// Iterate over all elements the dataview requested and create a row
// which indicates that we are currently loading data
for (var i = _idxStart; i <= _idxEnd; i++)
{
var entry = this._getIndexEntry(i);
// Insert the row for the entry -- do not update rows which are
// already existing, as we do not have new data for those.
if (!this._insertDataRow(entry, false) && needsData === false)
{
needsData = i;
}
}
// Queue fetching that data range
if (needsData !== false)
{
this._queueFetch(needsData, _idxEnd - needsData + 1, false);
}
},
/**
*
*/
_queueFetch: function (_start, _numRows, _refresh) {
// Sanitize the request
_start = Math.max(0, _start);
_numRows = Math.min(this._grid.getTotalCount(), _start + _numRows)
- _start;
// Context used in the callback function
var ctx = { "self": this, "start": _start, "count": _numRows };
// Build the query
var query = { "start": _start, "num_rows": _numRows, "refresh": _refresh };
// Call the callback
this._dataProvider.dataFetch(query, this._lastModified,
this._fetchCallback, ctx);
},
/**
*
*/
_dataCallback: function (_data) {
// Set the "hasData" flag
this.self.hasData = true;
// Call the row callback with the new data -- the row callback then
// generates the row DOM nodes that will be inserted into the grid
if (this.self._rowCallback)
{
// Remove everything from the current row
this.entry.row.clear();
// Fill the row DOM Node with data
this.self._rowCallback.call(
this.self._rowContext,
_data,
this.entry.row.getDOMNode(),
this.entry.idx,
this.entry
);
// Invalidate the current row entry
this.entry.row.invalidate();
}
},
/**
*
*/
_destroyCallback: function (_row) {
// There is no further row connected to the entry
this.entry.row = null;
// Unregister the data callback
this.self._dataProvider.dataUnregisterUID(this.entry.uid,
this.self._dataCallback, null);
},
/**
* Returns an array containing "_count" index mapping entries starting from
* the index given in "_start".
*/
_getIndexMapping: function (_start, _count) {
var result = [];
for (var i = _start; i < _start + _count; i++)
{
result.push(this._getIndexEntry(i));
}
return result;
},
/**
* Updates the grid according to the new order. The function simply does the
* following: It iterates along the new order (given in _order) and the old
* order given in _idxMap. Iteration variables used are
* a) i -- points to the current entry in _order
* b) idx -- points to the current grid row that will be effected by
* this operation.
* c) mapIdx -- points to the current entry in _indexMap
* The following cases may occur:
* a) The current entry in the old order has no uid or no row -- in that
* case the row at the current position is simply updated,
* the old pointer will be incremented.
* b) The two uids differ -- insert a new row with the new uid, do not
* increment the old pointer.
* c) The two uids are the same -- increment the old pointer.
* In a last step all rows that are left in the old order are deleted. All
* newly created index entries are returned. This function does not update
* the internal mapping in _idxMap.
*/
_updateOrder: function (_start, _count, _idxMap, _order) {
// The result contains the newly created index map entries which have to
// be merged with the result
var result = [];
// Iterate over the new order
var mapIdx = 0;
var idx = _start;
for (var i = 0; i < _order.length; i++, idx++)
{
var current = _idxMap[mapIdx];
if (!current.row || !current.uid)
{
// If there is no row yet at the current position or the uid
// of that entry is unknown, simply update the entry.
current.uid = _order[i];
current.idx = idx;
this._insertDataRow(current, true);
mapIdx++;
}
else if (current.uid !== _order[i])
{
// Insert a new row at the new position
var entry = {
"idx": idx,
"uid": _order[i],
"row": null
};
this._insertDataRow(entry, true);
// Remember the new entry
result.push(entry);
}
else
{
// Do nothing, the uids do not differ, just update the index of
// the element
current.idx = idx;
mapIdx++;
}
}
// Delete as many rows as we have left
for (var i = mapIdx; i < _idxMap.length; i++)
{
this._grid.deleteRow(idx);
}
return result;
},
_mergeResult: function (_newEntries, _invalidStartIdx, _diff) {
if (_newEntries.length > 0 || _diff > 0)
{
// Create a new index map
var newMap = {};
// Insert all new entries into the new index map
for (var i = 0; i < _newEntries.length; i++)
{
newMap[_newEntries[i].idx] = _newEntries[i];
}
// Insert all old entries that have a row into the new index map
// while adjusting their indices
for (var key in this._indexMap)
{
// Get the corresponding index entry
var entry = this._indexMap[key];
// Only keep index entries which are currently displayed
if (entry.row)
{
// Calculate the new index -- if rows were deleted, we'll
// have to adjust the index
var newIdx = entry.idx >= _invalidStartIdx
? entry.idx - _diff : entry.idx;
entry.idx = key;
newMap[newIdx] = entry;
}
}
// Make the new index map the current index map
this._indexMap = newMap;
}
},
_fetchCallback: function (_response) {
// Do nothing if _response.order evaluates to false
if (!_response.order)
{
return;
}
// Get the current index map for the updated region
var idxMap = this.self._getIndexMapping(this.start, this.count);
// Update the grid using the new order. The _updateOrder function does
// not update the internal mapping while inserting and deleting rows, as
// this would move us to another asymptotic runtime level.
var res = this.self._updateOrder(this.start, this.count, idxMap,
_response.order);
// Merge the new indices, update all indices with rows that were not
// affected and invalidate all indices if there were changes
this.self._mergeResult(res, this.start + _response.order.length,
idxMap.length - _response.order.length);
// Update the total element count in the grid
this.self._grid.setTotalCount(_response.total);
}
});

View File

@ -1,5 +1,5 @@
/**
* eGroupWare eTemplate2 - Contains the dataview base object.
* eGroupWare eTemplate2 - Contains interfaces used inside the dataview
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
@ -20,13 +20,7 @@ var et2_dataview_IInvalidatable = new Interface({
invalidate: function() {}
});
var et2_dataview_IDataRow = new Interface({
updateData: function(_data) {}
});
});
var et2_dataview_IViewRange = new Interface({
@ -35,51 +29,70 @@ var et2_dataview_IViewRange = new Interface({
});
/**
* Interface which objects have to implement, that want to act as low level
* datasource.
*/
var et2_IRowFetcher = new Interface({
/**
* @param _fetchList is an array consisting of objects whith the entries
* "startIdx" and "count"
* @param _callback is the callback which is called when the data is ready
* (may be immediately or deferred). The callback has the following
* signature:
* function (_rows)
* where _rows is an associative array which contains the data for that row.
* @param _context is the context in which the callback should run.
*/
getRows: function(_fetchList, _callback, _context) {}
});
/**
* Interface the data provider has to implement
* Interface a data provider has to implement. The data provider functions are
* called by the et2_dataview_controller class. The data provider basically acts
* like the egw api egw_data extension, but some etemplate specific stuff has
* been stripped away -- the implementation (for the nextmatch widget that is
* et2_extension_nextmatch_dataprovider) has to take care of that.
*/
var et2_IDataProvider = new Interface({
/**
* Returns the total count of grid elements
* This function is used by the et2_dataview_controller to fetch data for
* a certain range. The et2_dataview_controller provides data which allows
* to only update elements which really have changed.
*
* @param queriedRange is an object of the following form:
* {
* start: <START INDEX>,
* num_rows: <COUNT OF ENTRIES>
* }
* @param knownRange is an array of the above form and informs the
* implementation which range is already known to the client. This parameter
* may be null in order to indicate that the client currently has no valid
* data.
* @param lastModification is the last timestamp that was returned from the
* data provider and for which the client has data. It may be null in order
* to indicate, that the client currently has no data or needs a complete
* refresh.
* @param callback is the function that should get called, once the data
* is available. The data passed to the callback function has the
* following form:
* {
* order: [uid, ...],
* total: <TOTAL COUNT>,
* lastModification: <LAST MODIFICATION TIMESTAMP>
* }
* @param context is the context in which the callback function will get
* called.
*/
getCount: function() {},
dataFetch: function (_queriedRange, _lastModification, _callback, _context) {},
/**
* Registers the given dataRow for the given index. Calls _dataRow.updateData
* as soon as data is available for that row.
* Registers the intrest in a certain uid for a callback function. If
* the data for that uid changes or gets loaded, the given callback
* function is called. If the data for the given uid is available at the
* time of registering the callback, the callback is called immediately.
*
* @param _uid is the uid for which the callback should be registered.
* @param _callback is the callback which should get called.
* @param _context is an optional parameter which can
*/
registerDataRow: function(_dataRow, _idx) {},
dataRegisterUID: function (_uid, _callback, _context) {},
/**
* Stops calling _dataRow.updateData for the dataRow registered for the given
* index.
* Unregisters the intrest of updates for a certain data uid.
*
* @param _uid is the data uid for which the callbacks should be
* unregistered.
* @param _callback specifies the specific callback that should be
* unregistered. If it evaluates to false, all callbacks (or those
* matching the optionally given context) are removed.
* @param _context specifies the callback context that should be
* unregistered. If it evaluates to false, all callbacks (or those
* matching the optionally given callback function) are removed.
*/
unregisterDataRow: function(_idx) {},
/**
* Returns the action object manager (if one exists, otherwise null)
*/
getActionObjectManager: function() {}
dataUnregisterUID: function (_uid, _callback, _context) {}
});

View File

@ -1,288 +0,0 @@
/**
* eGroupWare eTemplate2 - Class which contains a the data model
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011
* @version $Id$
*/
"use strict"
/*egw:uses
egw_action.egw_action;
et2_core_inheritance;
et2_core_common;
et2_dataview_interfaces;
*/
var et2_dataview_dataProvider = Class.extend(et2_IDataProvider, {
/**
* Creates this instance of the data provider.
*/
init: function(_source, _total, _actionMgr) {
this._source = _source;
this._total = _total;
this._registeredRows = {};
this._data = {};
this._dataCount = 0;
this._queue = {};
this._queueSize = 0;
this._stepSize = 25; // Count of elements which is loaded at once
this._maxCount = 1000; // Maximum count before the elements are cleaned up
// Create an action object manager
this.actionObjectManager = new egwActionObjectManager("", _actionMgr);
var self = this;
this._cleanupInterval = window.setInterval(function() {self._cleanup()},
10 * 1000);
this._queueFlushTimeout = null;
},
destroy: function() {
// Destroy the cleanup timer callback
window.clearInterval(this._cleanupInterval);
// Destroy the _queueFlushTimeout
this._stopFlushTimer();
// Destroy the actionObject manager
this.actionObjectManager.clear();
},
/**
* Resets the stored data and aborts any queue request
*/
clear: function() {
this._data = {};
this._dataCount = 0;
this._queue = {};
this._queueSize = 0;
this._stopFlushTimer();
// Clear all elements in the action object manager
this.actionObjectManager.clear();
},
/**
* Data is an object containing an "rows" and "readonlys" array.
*/
loadData: function(_data) {
this._receiveData(_data);
},
/**
* Returns the total count
*/
getCount: function() {
return this._total;
},
registerDataRow: function(_dataRow, _idx) {
// Make sure _idx is a int
_idx = parseInt(_idx);
if (typeof this._registeredRows[_idx] != "undefined")
{
this.egw().debug("warn", "Overriding data row for index " + _idx);
}
// Associate the given data row with that index
this._registeredRows[_idx] = _dataRow;
// Check whether an entry exists in the data array - if yes, call the
// request immediately
if (typeof this._data[_idx] != "undefined")
{
if (this._data[_idx].data == false)
{
egw().debug("warn", "App provides blank first row, which causes problems");
return;
}
this._callUpdateData(_idx);
}
else
{
this._queueIndex(_idx);
}
},
unregisterDataRow: function(_idx) {
// Make sure _idx is a int
_idx = parseInt(_idx);
delete(this._registeredRows[_idx]);
},
getActionObjectManager: function() {
return this.actionObjectManager;
},
/* ---- PRIVATE FUNCTIONS ---- */
_queueIndex: function(_idx) {
// Mark the index as queued
if (typeof this._queue[_idx] == "undefined")
{
this._queue[_idx] = true;
this._queueSize++;
}
if (this._queueSize > this._stepSize)
{
this._flushQueue();
}
else
{
// (Re)start the queue flush timer
var self = this;
this._stopFlushTimer();
this._queueFlushTimeout = window.setTimeout(function() {
self._queueFlushTimeout = null;
self._flushQueue();
}, 50);
}
},
_flushQueue: function() {
// Stop the flush timer if it is still active
this._stopFlushTimer();
// Mark all elements in a radius of this._stepSize / 2
var marked = {};
var r = Math.floor(this._stepSize / 2);
for (var key in this._queue)
{
key = parseInt(key);
var b = Math.max(0, key - r);
var t = Math.min(key + this._stepSize, this._total - 1);
var c = 0;
for (var i = b; i <= t && c < this._stepSize; i ++)
{
if (typeof this._data[i] == "undefined")
{
marked[i] = true;
c++;
}
}
}
// Reset the queue
this._queue = {};
this._queueSize = 0;
// Create a list with start indices and counts
var fetchList = [];
var entry = null;
var last = 0;
// Get the int keys and sort the array numeric
var arr = et2_arrayIntKeys(marked).sort(function(a,b){return a > b ? 1 : (a == b ? 0 : -1)});
for (var i = 0; i < arr.length; i++)
{
if (i == 0 || arr[i] - last > 1)
{
if (entry)
{
fetchList.push(entry);
}
entry = {
"startIdx": arr[i],
"count": 1
};
}
else
{
entry.count++;
}
last = arr[i];
}
if (entry)
{
fetchList.push(entry);
}
// Call the "getRows" callback
this._source.getRows(fetchList, this._receiveData, this);
},
_receiveData: function(_data) {
var time = (new Date).getTime();
for (var key in _data.rows)
{
if (!isNaN(key))
{
// Make sure the key is a int
key = parseInt(key);
// Copy the data for the given index
this._data[key] = {
"data": _data.rows[key],
"timestamp": time
};
// Update the row associated to the index
this._callUpdateData(key);
}
}
},
_stopFlushTimer: function() {
// Stop the queue flush timer
if (this._queueFlushTimeout !== null)
{
window.clearTimeout(this._queueFlushTimeout);
}
},
_callUpdateData: function(_idx) {
if (typeof this._registeredRows[_idx] != "undefined")
{
this._registeredRows[_idx].updateData({
"content": this._data[_idx].data
});
}
},
_cleanup: function() {
// Delete all data rows which have not been accessed for more than
// "delta" ms (5 minutes) - this method does not ensure that _dataCount
// gets below _maxCount!
var delta = 5 * 60 * 1000;
var now = (new Date).getTime();
if (this._dataCount > this._maxCount)
{
for (var key in this._data)
{
var entry = this._data[key];
if (now - entry.timestamp > delta)
{
delete(this._data[key]);
this._dataCount--;
}
}
}
}
});

View File

@ -1,12 +1,12 @@
/**
* eGroupWare eTemplate2 - Class which contains the "row" base class
* 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 2011
* @copyright Stylite 2012
* @version $Id$
*/
@ -17,60 +17,99 @@
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.
*/
var et2_dataview_container = Class.extend(et2_dataview_IInvalidatable, {
/**
* Initializes the container object.
*
* @param _dataProvider is the data provider for the element
* @param _rowProvider is the "rowProvider" of the element
* @param _invalidationElem is the element of which the "invalidate" method
* will be called if the height of the elements changes. It has to
* implement the et2_dataview_IInvalidatable interface.
* @param _parent is an object which implements the IInvalidatable
* interface. _parent may not be null.
*/
init: function(_dataProvider, _rowProvider, _invalidationElem) {
this.dataProvider = _dataProvider;
this.rowProvider = _rowProvider;
this._invalidationElem = _invalidationElem;
init: function(_parent) {
// Copy the given invalidation element
this._parent = _parent;
this._nodes = [];
this._inTree = false;
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();
},
/**
* Setter function which can be used to update the invalidation element.
*
* @param _invalidationElem is the element of which the "invalidate" method
* will be called if the height of the elements changes. It has to
* implement the et2_dataview_IInvalidatable interface.
*/
setInvalidationElement: function(_invalidationElem) {
this._invalidationElem = _invalidationElem;
},
/**
* Inserts all container nodes into the DOM tree after the given element
*/
insertIntoTree: function(_afterNode, _prepend) {
if (!this._inTree && _afterNode != null)
// 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.
*/
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)
{
_afterNode.prepend(this._nodes[i]);
_node.before(this._nodes[0]);
}
else
{
_afterNode.after(this._nodes[i]);
_node.after(this._nodes[0]);
}
}
else
@ -80,10 +119,8 @@ var et2_dataview_container = Class.extend(et2_dataview_IInvalidatable, {
}
}
// Save the "attachData"
this._inTree = true;
this._attachData = {"node": _afterNode, "prepend": _prepend};
// Invalidate this element in order to update the height of the
// parent
this.invalidate();
}
},
@ -108,7 +145,9 @@ var et2_dataview_container = Class.extend(et2_dataview_IInvalidatable, {
},
/**
* Appends a jQuery node to the container
* 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
@ -118,20 +157,20 @@ var et2_dataview_container = Class.extend(et2_dataview_IInvalidatable, {
// tree.
if (this._inTree)
{
if (this._nodes.length == 1)
if (_nodes.length === 1)
{
if (_prepend)
if (this._attachData.prepend)
{
this._attachData.node.prepend(this._nodes[0]);
this._attachData.node.before(_node);
}
else
{
this._attachData.node.after(this._nodes[0]);
this._attachData.node.after(_node);
}
}
else
{
this._nodes[_nodes.length - 2].after(_node);
_node.after(this._nodes[this._nodes.length - 2]);
}
this.invalidate();
@ -139,7 +178,7 @@ var et2_dataview_container = Class.extend(et2_dataview_IInvalidatable, {
},
/**
* Removes a jQuery node from the container
* Removes a certain node from the container
*/
removeNode: function(_node) {
// Get the index of the node in the nodes array
@ -150,7 +189,7 @@ var et2_dataview_container = Class.extend(et2_dataview_IInvalidatable, {
// Remove the node if the container is currently attached
if (this._inTree)
{
_node.remove();
_node.parentNode.removeChild(_node);
}
// Remove the node from the nodes array
@ -173,41 +212,158 @@ var et2_dataview_container = Class.extend(et2_dataview_IInvalidatable, {
},
/**
* Returns the accumulated height of all container nodes. Only visible nodes
* (without "display: none") are taken into account.
* Returns the first node of the container.
*/
getHeight: function() {
var height = 0;
if (this._inTree)
{
// Increment the height value for each visible container node
var self = this;
$j(this._nodes, ":visible").each(function() {
height += self._nodeHeight(this[0]);
});
}
return height;
getFirstNode: function() {
return this._nodes.length > 0 ? this._nodes[0] : null;
},
/**
* Calls the "invalidate" function of the connected invalidation element.
* 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.
*/
setTop: function(_value) {
this._top = _value;
},
/**
* Sets the index of the element.
*/
setIndex: function(_value) {
this._index = _value;
},
/* -- et2_dataview_IInvalidatable -- */
/**
* Broadcasts an invalidation through the container tree. Marks the own
* height as invalid.
*/
invalidate: function() {
this._invalidationElem.invalidate();
// 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 the container in pixels and zero if the element is not
* Returns the height of a node in pixels and zero if the element is not
* visible. The height is clamped to positive values.
* The browser switch is placed at this position as the getHeight function is one
* of the mostly called functions in the whole grid code and should stay
* quite fast.
* The browser switch is placed at this position as the _nodeHeight function is
* one of the most frequently called functions in the whole grid code and should
* stay quite fast.
*/
if ($j.browser.mozilla)
/*if ($j.browser.mozilla)
{
et2_dataview_container.prototype._nodeHeight = function(_node)
{
@ -232,10 +388,10 @@ if ($j.browser.mozilla)
}
}
else
{
{*/
et2_dataview_container.prototype._nodeHeight = function(_node)
{
return _node.offsetHeight;
}
}
//}

File diff suppressed because it is too large Load Diff

View File

@ -1,256 +0,0 @@
/**
* eGroupWare eTemplate2 - Class which contains an management tree for the grid rows
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011
* @version $Id$
*/
"use strict"
/*egw:uses
et2_core_common; // for et2_range functions
et2_core_inheritance;
et2_dataview_interfaces;
et2_dataview_view_partitionNode;
*/
/**
* The partition container node base class implements most functionality for
* nodes which have a container.
*/
var et2_dataview_partitionContainerNode = et2_dataview_partitionNode.extend({
/**
* Copies the given container object and inserts the container into the tree
* - if it is not already in there.
*/
init: function(_root, _container, _args) {
this._super(_root);
// Copy the container parameter and set its "invalidationElement" to
// this node
this.container = _container;
this.container.setInvalidationElement(this);
},
/**
* Inserts the container into the tree if it is not already inserted.
*/
initializeContainer: function() {
// Obtain the node index and insert the container nodes at the node
// returned by getNodeAt. If idx is zero, the returned node will be
// the outer container, so the container nodes have to be prepended
// to it.
var idx = this.getStartIndex();
this.container.insertIntoTree(this.getRoot().getNodeAt(idx - 1),
idx == 0);
this.invalidate();
},
/**
* Destroys the container if it is still set - e.g. the spacer node
* sets the container to null before "free" ist called in some cases, in
* order to pass the container object to another spacer node.
*/
destroy: function() {
if (this.container)
{
this.container.free();
}
this._super();
},
/**
* Returns the height of the container
*/
calculateHeight: function() {
return this.container.getHeight();
},
/**
* Calls the "insertNodeAfter" function of the container to insert the node.
*/
insertNodeAfter: function(_node) {
this.container.insertNodeAfter(_node);
},
/**
* Returns the "lastNode" of the container
*/
getNodeAt: function(_idx) {
if (_idx >= this.getStartIndex() && _idx < this.getStopIndex())
{
return this.container.getLastNode();
}
return null;
}
});
/**
* Node which represents a spacer. Complete parts of the tree can be
* transformed into spacer nodes.
*/
var et2_dataview_partitionSpacerNode = et2_dataview_partitionContainerNode.extend({
init: function(_root, _count, _avgHeight, _container) {
// Create the container if it has not been passed as a third parameter
var container;
if (typeof _container != "undefined")
{
container = _container;
}
else
{
container = new et2_dataview_spacer(_root.getDataProvider(),
_root.getRowProvider(), this);
}
// Call the inherited constructor
this._super(_root, container);
// Copy the count and average height parameters - this updates the height
// of the outer container
this.setParameters(_count, _avgHeight);
},
getCount: function() {
return this._count;
},
getAvgHeight: function() {
return this._avgHeight;
},
setParameters: function(_count, _avgHeight) {
if (_count != this._count || _avgHeight != this._avgHeight)
{
// Copy the given parameters
this._count = _count;
this._avgHeight = _avgHeight;
// Invalidate this element
this.invalidate();
// Call the container function to set the total height
this.container.setHeight(this._count * this._avgHeight);
}
},
/**
* Creates the nodes which fall in the given range and returns them
*/
getRangeNodes: function(_range, _create) {
// If no new nodes should be created, simply return this node
if (!_create)
{
return this._super(_range);
}
var insertNodes = [];
// Copy parent and pidx as we'll have to access those objects after this
// one gets freed
var parent = this._parent;
var pidx = this._pidx;
// Get the top and bottom of this node
var t = this.getPosTop();
var b = this.getPosBottom();
// Get the start and stop index of the elements which have to be
// created.
var ah = this._avgHeight;
var startIdx = Math.max(0, Math.floor((_range.top - t) / ah));
var stopIdx = Math.min(this._count, Math.ceil((_range.bottom - t) / ah));
if (startIdx > 0 && startIdx < this._count)
{
// Create a spacer which contains the elements until startIdx
insertNodes.push(new et2_dataview_partitionSpacerNode(this.getRoot(),
startIdx, ah, this.container));
this.container = null;
}
// Calculate the current average height
ah = this.getRoot().getAverageHeight();
// Create the elements from start to stop index
for (var i = startIdx; i < stopIdx; i++)
{
var rowNode = new et2_dataview_partitionRowNode(this.getRoot(), ah);
insertNodes.push(rowNode);
}
if (stopIdx < this._count - 1 && stopIdx > 0)
{
// Create a spacer which contains the elements starting from
// stop index
var l = this._count - stopIdx;
insertNodes.push(new et2_dataview_partitionSpacerNode(this.getRoot(),
l, ah));
}
// Check whether insertNodes really has entrys - this is not the case
// if the given range is just outside the range of this element
if (insertNodes.length > 0)
{
// Free this element
this.free();
// Insert the newly created nodes at the original place of this node
parent.insertNodes(pidx, insertNodes);
// Insert the newly created elements into the DOM-Tree
for (var i = 0; i < insertNodes.length; i++)
{
insertNodes[i].initializeContainer();
}
return false;
}
return [];
},
getAvgHeightData: function(_data) {
// Do nothing here, as the spacers should not be inside the average
// height statistic.
}
});
var et2_dataview_partitionRowNode = et2_dataview_partitionContainerNode.extend({
init: function(_root, _avgHeight) {
var container = new et2_dataview_row(_root.getDataProvider(),
_root.getRowProvider(), this, _avgHeight);
this._super(_root, container);
},
initializeContainer: function() {
this._super();
this.container.setIdx(this.getStartIndex());
},
getIdxNode: function(_node, _create) {
return this.node;
}
});

View File

@ -1,323 +0,0 @@
/**
* eGroupWare eTemplate2 - Class which contains an management tree for the grid rows
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011
* @version $Id$
*/
"use strict"
/*egw:uses
et2_core_common; // for et2_range functions
et2_core_inheritance;
et2_dataview_interfaces;
*/
/**
* The partition node tree manages all rows in a dataview. As a dataview may have
* many thousands of lines, the rows are organized in an a tree. The leafs of the
* tree represent the single rows, upper layers represent groups of nodes.
* Each node has a "height" value and is capable of calculate the exact position
* of a row and its top and bottom value.
* Additionaly, a leaf can represent an unlimited number of rows. In this way
* the partition tree is built dynamically and is also capable of "forgetting"
* information about the rows by simply reducing the tree nodes at a certain
* position.
*/
var et2_dataview_IPartitionHeight = new Interface({
calculateHeight: function() {}
});
/**
* Abstract base class for partition nodes - contains the code for calculating
* the top, bottom, height and (start) index of the node
*/
var et2_dataview_partitionNode = Class.extend([et2_dataview_IPartitionHeight,
et2_dataview_IInvalidatable], {
init: function(_root) {
this._root = _root;
this._parent = null;
this._pidx = 0;
// Initialize the temporary storage elements
this.doInvalidate();
this._invalid = true;
},
destroy: function() {
// Remove this element from the parent children list
if (this._parent)
{
this._parent.removePIdxNode(this._pidx);
}
},
setParent: function(_parent) {
if (this._parent != _parent)
{
this._parent = _parent;
this.invalidate();
}
},
setPIdx: function(_pidx) {
if (this._pidx != _pidx)
{
this._pidx = _pidx;
this.invalidate();
}
},
/**
* Invalidates cached values - override the "doInvalidate" function.
*
* @param _sender is the node wich originally triggerd the invalidation, can
* be ommited when calling this function.
*/
invalidate: function(_sender) {
// If the _sender parameter is not given, assume that this element is
// the one which triggered the invalidation
var origin = typeof _sender == "undefined";
if ((origin || _sender != this) && !this._invalid)
{
this.doInvalidate();
this._invalid = true;
// Invalidate the parent node
if (this._parent)
{
// Invalidate the neighbor node
if (this._pidx < this._parent._children.length - 1)
{
this._parent._children[this._pidx + 1].invalidate();
}
this._parent.invalidate(origin ? this : _sender);
}
}
},
/**
* Performs the actual invalidation.
*/
doInvalidate: function() {
this._height = false;
this._posTop = false;
this._posBottom = false;
this._startIdx = false;
this._stopIdx = false;
},
/**
* Returns the root node of the partition tree
*/
getRoot: function() {
return this._root;
},
/**
* Returns the height of this node
*/
getHeight: function() {
// Calculate the height value if it is currently invalid
if (this._height === false)
{
this._height = this.calculateHeight();
// Do a sanity check for the value - if the height wasn't a number
// it could easily destroy the posTop and posBottom values of the
// complete tree!
if (isNaN(this._height))
{
this.egw().debug("error", "calculateHeight returned a NaN value!");
this._height = 0;
}
this._invalid = false;
}
return this._height;
},
/**
* Returns the top position of the node in px
*/
getPosTop: function() {
if (this._posTop === false)
{
this._posTop = this._accumulateValue(this.getPosTop, this.getPosBottom);
this._invalid = false;
}
return this._posTop;
},
/**
* Returns the bottom position of the node in px
*/
getPosBottom: function() {
if (this._posBottom === false)
{
this._posBottom = this.getPosTop() + this.getHeight();
this._invalid = false;
}
return this._posBottom;
},
/**
* Returns an range object
*/
getRange: function() {
return {
"top": this.getPosTop(),
"bottom": this.getPosBottom()
};
},
/**
* Returns true if the node intersects with the given range
*/
inRange: function(_ar) {
return et2_rangeIntersect(this.getRange(), _ar);
},
/**
* Returns the overall start index of the node
*/
getStartIndex: function() {
if (this._startIdx === false)
{
this._startIdx = this._accumulateValue(this.getStartIndex,
this.getStopIndex);
this._invalid = false;
}
return this._startIdx;
},
/**
* Returns the overall stop index of the node
*/
getStopIndex: function() {
if (this._stopIdx === false)
{
this._stopIdx = this.getStartIndex() + this.getCount();
this._invalid = false;
}
return this._stopIdx;
},
/**
* Returns the index range object
*/
getIdxRange: function() {
return {
"top": this.getStartIndex(),
"bottom": this.getStopIndex()
};
},
/**
* Checks whether this element is inside the given index range
*/
inIdxRange: function(_idxRange) {
return et2_rangeIntersect(this.getIdxRange, _idxRange);
},
/**
* Returns the count of leafs which are below this node
*/
getCount: function() {
return 1;
},
/**
* Returns the nodes which reside in the given range
*/
getRangeNodes: function(_range, _create) {
if (this.inRange(_range))
{
return [this];
}
return [];
},
/**
* Returns the nodes which are inside the given index range
*/
getIdxRangeNodes: function(_idxRange, _create) {
if (this.inIdxRange(_idxRange))
{
return [this];
}
return [];
},
/**
* Returns the (maximum) depth of the tree
*/
getDepth: function() {
return 1;
},
getAvgHeightData: function(_data) {
_data.count++;
_data.height += this.getHeight();
},
getNodeIdx: function(_idx, _create) {
return null;
},
getRowProvider: function() {
return this.getRoot().getRowProvider();
},
getDataProvider: function() {
return this.getRoot().getDataProvider();
},
/* ---- PRIVATE FUNCTIONS ---- */
_accumulateValue: function(_f1, _f2) {
if (this._parent)
{
if (this._pidx == 0)
{
return _f1.call(this._parent);
}
else
{
return _f2.call(this._parent._children[this._pidx - 1]);
}
}
return 0;
}
});

View File

@ -1,643 +0,0 @@
/**
* eGroupWare eTemplate2 - Class which implements the organization node
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011
* @version $Id$
*/
"use strict"
/*egw:uses
et2_core_common; // for et2_range functions
et2_core_inheritance;
et2_dataview_interfaces;
et2_dataview_view_partitionNode;
*/
/**
* The ET2_PARTITION_TREE_WIDTH defines the count of children a node will be
* created with.
*/
var ET2_PARTITION_TREE_WIDTH = 10;
/**
* An partition tree organization node can contain child nodes and organizes
* those.
*/
var et2_dataview_partitionOrganizationNode = et2_dataview_partitionNode.extend(
/*et2_dataview_IIndexOperations, */{
init: function(_root, _parent, _pidx) {
if (typeof _parent == "undefined")
{
_parent = null;
}
if (typeof _pidx == "undefined")
{
_pidx = 0;
}
// Call the parent constructor
this._super(_root);
this._children = [];
// Set the given parent and parent-index
this.setParent(_parent);
this.setPIdx(_pidx);
},
destroy: function() {
// Free all child nodes
for (var i = this._children.length - 1; i >= 0; i--)
{
this._children[i].free();
}
this._super();
},
/**
* Delete the buffered element count
*/
doInvalidate: function() {
this._super();
this._count = false;
this._depth = false;
this._avgHeightData = false;
},
/**
* Calculates the count of elements by accumulating the counts of the child
* elements.
*/
getCount: function() {
if (this._count === false)
{
// Calculate the count of nodes
this._count = 0;
for (var i = 0; i < this._children.length; i++)
{
this._count += this._children[i].getCount();
}
}
return this._count;
},
/**
* Calculates the height of this node by accumulating the height of the
* child nodes.
*/
calculateHeight: function() {
var result = 0;
for (var i = 0; i < this._children.length; i++)
{
result += this._children[i].getHeight();
}
return result;
},
/**
* Removes the given node from the tree
*/
removeNode: function(_node) {
// Search the element on this level
for (var i = 0; i < this._children.length; i++)
{
if (this._children[i] == _node)
{
this.removePIdxNode(i);
return true;
}
}
// Search the element on a lower level
for (var i = 0; i < this._children.length; i++)
{
if (this._children[i] instanceof et2_dataview_partitionOrganizationNode &&
this._children[i].removeNode(_node))
{
return true;
}
}
return false;
},
/**
* Removes the child with the given index in the _children list
*/
removePIdxNode: function(_pidx) {
// Invalidate this element
this.invalidate();
// Delete the element at the given pidx and remove the parent reference
this._children.splice(_pidx, 1)[0].setParent(null);
// Recalculate the pidx of the children behind the one removed
for (var i = _pidx; i < this._children.length; i++)
{
this._children[i]._pidx--;
}
return true;
},
/**
* Removes the child with the given overall index
*/
removeIdxNode: function(_idx) {
return this._iterateToIndex(_idx, function(ei, bi, child) {
if (child.implements(et2_dataview_IIndexOperations))
{
return child.removeIdxNode(_idx);
}
return this.removePIdxNode(i);
}, false);
},
/**
* Returns the node with the given overall index and null if it is not found
*/
getIdxNode: function(_idx) {
return this._iterateToIndex(_idx, function(ei, bi, child) {
if (child.implements(et2_dataview_IIndexOperations))
{
return child.getIdxNode()
}
if (idx == bi)
{
return child;
}
}, null);
},
/**
* getNodeAt returns the DOM node at the given index
*/
getNodeAt: function(_idx) {
return this._iterateToIndex(_idx, function(ei, bi, child) {
return child.getNodeAt(_idx);
}, null);
},
/**
* Returns all nodes in the given range
*/
getRangeNodes: function(_range, _create) {
if (typeof _create == "undefined")
{
_create = true;
}
var result = [];
// Create a copy of the children of this element, as the child list may
// change due to new children being inserted.
var children = this._copyChildren();
// We did not have a intersect in the range now
var hadIntersect = false;
for (var i = 0; i < children.length; i++)
{
if (children[i].inRange(_range))
{
hadIntersect = true;
var res = children[i].getRangeNodes(_range, _create);
if (res === false)
{
return this.getRangeNodes(_range, _create);
}
// Append the search results of the given element
result = result.concat(res);
}
else
{
// Abort as we are out of the range where intersects can happen
if (hadIntersect)
{
break;
}
}
}
return result;
},
/**
* Returns the nodes which are inside the given range
*/
getIdxRangeNodes: function(_idxRange, _create) {
if (typeof _create == "undefined")
{
_create = true;
}
var result = [];
// Create a copy of the children of this element, as the child list may
// change due to new children being inserted.
var children = this._copyChildren();
// We did not have a intersect in the range now
var hadIntersect = false;
for (var i = 0; i < children.length; i++)
{
if (children[i].inIdxRange(_idxRange))
{
hadIntersect = true;
// Append the search results of the given element
var res = children[i].getIdxRangeNodes(_idxRange,
_create);
if (res === false)
{
return this.getIdxRangeNodes(_idxRange, _create);
}
result = result.concat(res);
}
else
{
// Abort as we are out of the range where intersects can happen
if (hadIntersect)
{
break;
}
}
}
return result;
},
/**
* Reduces the given range to a spacer
*/
reduceRange: function(_range) {
this._reduce(this.getRangeNodes(_range, false))
},
/**
* Reduces the given index range to a spacer
*/
reduceIdxRange: function(_range) {
this._reduce(this.getIdxRangeNodes(_range, false));
},
getDepth: function() {
if (this._depth === false)
{
this._depth = 0;
// Get the maximum depth and increase it by one
for (var i = 0; i < this._children.length; i++)
{
this._depth = Math.max(this._depth, this._children[i].getDepth());
}
this._depth++;
}
return this._depth;
},
_insertLeft: function(_idx, _nodes) {
// Check whether the node left to the given index can still take some
// nodes - if yes, insert the maximum amount of nodes into this node
if (_idx > 0 && this._children[_idx - 1] instanceof et2_dataview_partitionOrganizationNode
&& this._children[_idx - 1]._children.length < ET2_PARTITION_TREE_WIDTH)
{
// Calculate how many children can be inserted into the left node
var child = this._children[_idx - 1];
var c = Math.min(ET2_PARTITION_TREE_WIDTH - child._children.length, _nodes.length);
// Insert the remaining children into the left node
if (c > 0)
{
var nodes = _nodes.splice(0, c);
child.insertNodes(child._children.length, nodes);
}
}
},
_insertRight: function(_idx, _nodes) {
// Check whether the node right to the given index can still take some
// nodes - if yes, insert the nodes there
if (_idx < this._children.length &&
this._children[_idx] instanceof et2_dataview_partitionOrganizationNode &&
this._children[_idx]._children.length < ET2_PARTITION_TREE_WIDTH)
{
var child = this._children[_idx];
var c = Math.min(ET2_PARTITION_TREE_WIDTH - child._children.length, _nodes.length);
// Insert the remaining children into the left node
if (c > 0)
{
var nodes = _nodes.splice(_nodes.length - c, c);
child.insertNodes(0, nodes);
}
}
},
/**
* Groups the nodes which should be inserted by packages of ten and insert
* those as children. If there are more than ET2_PARTITION_TREE_WIDTH
* children as a result of this action, this node gets destroyed and the
* children are given to the parent node.
*/
insertNodes: function(_idx, _nodes) {
// Break if no nodes are to be inserted
if (_nodes.length == 0)
{
return;
}
// Invalidate this node
this.invalidate();
// Try to insert the given objects into an organization node at the left
// or right side of the given index
this._insertLeft(_idx, _nodes);
this._insertRight(_idx, _nodes);
// Update the pidx of the nodes after _idx
for (var i = _idx; i < this._children.length; i++)
{
this._children[i].setPIdx(i + _nodes.length);
}
// Set the parent and the pidx of the new nodes
for (var i = 0; i < _nodes.length; i++)
{
_nodes[i].setParent(this);
_nodes[i].setPIdx(_idx + i);
}
// Simply insert the nodes at the given position
this._children.splice.apply(this._children, [_idx, 0].concat(_nodes));
// Check whether the width of this element is greater than ET2_PARTITION_TREE_WIDTH
// If yes, split the children into groups of ET2_PARTITION_TREE_WIDTH and
// insert those into this node
/*if (this._children.length > ET2_PARTITION_TREE_WIDTH)
{
var insertNodes = [];
while (_nodes.length > 0)
{
var orgaNode = new et2_dataview_partitionOrganizationNode(this,
insertNodes.length);
// Get groups of ET2_PARTITION_TREE_WIDTH from the nodes while
// reading the first level of nodes from organization nodes
var newNodes = [];
var isPartial = false;
while (newNodes.length < ET2_PARTITION_TREE_WIDTH && _nodes.length > 0)
{
var node = _nodes[0];
if (!(node instanceof et2_dataview_partitionOrganizationNode))
{
newNodes.push(_nodes.shift());
isPartial = true;
}
else
{
if (node._children.length == 0)
{
// Remove the node from the list and free it
_nodes.shift().free();
}
else
{
if (!isPartial && node._children.length == ET2_PARTITION_TREE_WIDTH)
{
newNodes.push(_nodes.shift());
}
else
{
newNodes = newNodes.concat(node._children.splice(0,
ET2_PARTITION_TREE_WIDTH - newNodes.length));
isPartial = true;
}
}
}
}
orgaNode.insertNodes(0, newNodes);
insertNodes.push(orgaNode);
}
this._children = [];
this.insertNodes(0, insertNodes);
}*/
},
rebuild: function() {
// Get all leafs
var children = [];
this._getFlatList(children);
// Free all organization nodes
this._clear();
this.insertNodes(0, children);
},
/**
* Accumulates the "average height" data
*/
getAvgHeightData: function(_data) {
if (this._avgHeightData == false)
{
this._avgHeightData = {"count": 0, "height": 0};
for (var i = 0; i < this._children.length; i++)
{
this._children[i].getAvgHeightData(this._avgHeightData);
}
this._invalid = false;
}
// Increment the data object entries by the buffered avg height data
_data.count += this._avgHeightData.count;
_data.height += this._avgHeightData.height;
},
debug: function() {
var children = [];
var offs = false;
this._getFlatList(children);
for (var i = 0; i < children.length; i++)
{
var idx = children[i].getStartIndex();
var node = children[i].getNodeAt(idx);
if (node)
{
if (offs === false)
{
offs = node.offset().top;
}
var actualTop = node.offset().top - offs;
var calculatedTop = children[i].getPosTop();
if (Math.abs(actualTop - calculatedTop) > 1)
{
egw.debug("warn", i, "Position missmatch at idx ", idx,
actualTop, calculatedTop, node);
}
var actualHeight = node.outerHeight(true);
var calculateHeight = children[i].getHeight();
if (Math.abs(actualHeight - calculateHeight) > 1)
{
egw.debug("warn", i, "Height missmatch at idx ", idx,
actualHeight, calculateHeight, node);
}
}
}
},
/* ---- PRIVATE FUNCTIONS ---- */
_copyChildren: function() {
// Copy the child array as querying the child nodes may change the tree
var children = new Array(this._children.length);
for (var i = 0; i < this._children.length; i++)
{
children[i] = this._children[i];
}
return children;
},
_iterateToIndex: function(_idx, _func, _res) {
for (var i = 0; i < this._children.length; i++)
{
var child = this._children[i];
var bi = child.getStartIndex();
var ei = child.getStopIndex();
if (bi > _idx)
{
return res;
}
if (bi <= _idx && ei > _idx)
{
return _func.call(this, bi, ei, child);
}
}
return res;
},
/**
* Reduces the given nodes to a single spacer
*/
_reduce: function(_nodes) {
if (_nodes.length == 0)
{
return;
}
// Check whether the first or last node is a spacer, if not create
// a new one
var ph;
if (_nodes[0] instanceof et2_dataview_partitionSpacerNode)
{
ph = _nodes[0]
}
else if (_nodes[_nodes.length - 1] instanceof et2_dataview_partitionSpacerNode)
{
ph = _nodes[_nodes.length - 1];
}
else
{
// Create a new spacer node and insert it at the place of the
// first node of the range
ph = new et2_dataview_partitionSpacerNode(this.getRoot(), 0, 0);
_nodes[0]._parent.insertNodes(_nodes[0]._pidx, [ph]);
// Initialize the new placeholder
ph.initializeContainer();
}
// Get the height of the resulting spacer
var height = _nodes[_nodes.length - 1].getPosBottom() - _nodes[0].getPosTop();
// Get the count of actual elements in the nodes
var count = 0;
for (var i = 0; i < _nodes.length; i++)
{
count += _nodes[i].getCount();
}
// Update the spacer parameters
ph.setParameters(count, height / count);
// Free all elements (except for the spacer)
for (var i = _nodes.length - 1; i >= 0; i--)
{
if (_nodes[i] != ph)
{
_nodes[i].free();
}
}
},
/**
* Used when rebuilding the tree
*/
_getFlatList: function(_res) {
for (var i = 0; i < this._children.length; i++)
{
if (this._children[i] instanceof et2_dataview_partitionOrganizationNode)
{
this._children[i]._getFlatList(_res);
}
else
{
_res.push(this._children[i]);
}
}
},
_clear: function() {
for (var i = this._children.length - 1; i >= 0; i--)
{
if (this._children[i] instanceof et2_dataview_partitionOrganizationNode)
{
this._children[i].free();
}
}
this._children = [];
}
});

View File

@ -1,103 +0,0 @@
/**
* eGroupWare eTemplate2 - Class which contains an management tree for the grid rows
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011
* @version $Id$
*/
"use strict"
/*egw:uses
et2_core_common; // for et2_range functions
et2_core_inheritance;
et2_dataview_interfaces;
et2_dataview_view_partitionOrganizationNode;
et2_dataview_view_partitionContainerNodes;
*/
/**
* Main class for the usage of the partition tree
*/
var et2_dataview_partitionTree = et2_dataview_partitionOrganizationNode.extend({
init: function(_dataProvider, _rowProvider, _avgHeight, _node) {
this._super(this);
this._avgHeight = _avgHeight;
this._node = _node;
this._count = _dataProvider.getCount();
this._dataProvider = _dataProvider;
this._rowProvider = _rowProvider;
egw.debug("Creating partition tree with ", this._count,
" elements of avgHeight ", this._avgHeight);
// Append a spacer node to the children
var ph = new et2_dataview_partitionSpacerNode(this, this._count,
this._avgHeight);
ph.setParent(this);
ph.initializeContainer();
this._children = [ph];
},
getAverageHeight: function() {
var data = {"count": 0, "height": 0};
this.getAvgHeightData(data);
if (data.count == 0)
{
return this._avgHeight;
}
return data.height / data.count;
},
/**
* Returns the actual count of managed objects inside of the tree - getCount
* in contrast returns the count of "virtual" objects including the
* spacers.
*/
getManagedCount: function() {
var data = {"count": 0, "height": 0};
this.getAvgHeightData(data);
return data.count;
},
/**
* Returns the node after which new nodes have to be inserted for the given
* index.
*/
getNodeAt: function(_idx) {
// Insert the given node to the top of the parent container
if (_idx < 0)
{
return this._node;
}
// Otherwise search for the tree node with that index
return this._super(_idx);
},
getRowProvider: function() {
return this._rowProvider;
},
getDataProvider: function() {
return this._dataProvider;
}
});

View File

@ -1,12 +1,12 @@
/**
* eGroupWare eTemplate2 - Class which contains a factory method for rows
* eGroupWare eTemplate2 - dataview
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2011
* @copyright Stylite 2011-2012
* @version $Id$
*/
@ -15,109 +15,36 @@
/*egw:uses
egw_action.egw_action;
et2_dataview_interfaces;
et2_dataview_view_container;
et2_dataview_view_rowAOI;
*/
var et2_dataview_row = et2_dataview_container.extend(et2_dataview_IDataRow, {
var et2_dataview_row = et2_dataview_container.extend({
init: function(_dataProvider, _rowProvider, _invalidationElem, _avgHeight) {
/**
* Creates the row container. Use the "setRow" function to load the actual
* row content.
*
* @param _parent is the row parent container.
*/
init: function(_parent) {
// Call the inherited constructor
this._super(_parent);
this._super(_dataProvider, _rowProvider, _invalidationElem);
this._avgHeight = _avgHeight;
this._idx = null;
this.rowWidget = null;
this.actionObject = null;
this.hasAvgHeight = false;
// Get the default row object and scale the row to the average height
this.tr = this.rowProvider.getPrototype("dataRow");
// Append the row
// Create the outer "tr" tag and append it to the container
this.tr = $j(document.createElement("tr"));
this.appendNode(this.tr);
},
destroy: function() {
// Unregister the row from the data provider
if (this._idx !== null)
{
this.dataProvider.unregisterDataRow(this._idx);
}
// Free the row widget first, if it has been set
if (this.rowWidget)
{
this.rowWidget.free();
}
// Free the action object and remove it from the action object manager
if (this.actionObject)
{
// Delete the action object from the surrounding manager
this.actionObject.remove();
}
this._super();
clear: function() {
this.tr.empty();
},
setIdx: function(_idx) {
this._idx = _idx;
// Register the row in the data provider
this.dataProvider.registerDataRow(this, _idx);
// Set the default height of the rowWidget has not been immediately
// created
if (!this.rowWidget)
{
$j("td:first", this.tr).height(this._avgHeight);
this.hasAvgHeight = true;
}
getDOMNode: function() {
return this.tr[0];
},
updateData: function(_data) {
// Free all old data in the action object
if (this.actionObject)
{
this.actionObject.clear();
}
// Create an action object interface for the row
var aom = this.dataProvider.getActionObjectManager();
if (aom)
{
// this is rather hackisch, but I have no idea how to get action_links & row_id here otherwise
this.actionObject = aom.addObject(_data.content[aom.manager.row_id],
new et2_dataview_rowAOI(this.tr[0]));
// TODO: The action links should be inside the data and not inside
// the row classes...
this.actionObject.updateActionLinks(aom.manager.action_links);
}
// Reset the height
if (this.hasAvgHeight)
{
$j("td:first", this.tr).height("auto");
this.hasAvgHeight = false;
}
// Free the row widget if it already existed
if (this.rowWidget != null)
{
this.rowWidget.free();
}
// Create the row widget - it automatically generates the widgets and
// attaches the given data to them
this.rowWidget = this.rowProvider.getDataRow(_data, this.tr, this._idx);
// Invalidate this element
this.invalidate();
getJNode: function() {
return this.tr;
}
});

View File

@ -1,348 +0,0 @@
/**
* EGroupware eTemplate nextmatch row action object interface
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link http://www.egroupware.org
* @author Andreas Stöckel (as AT stylite.de)
* @author Ralf Becker <RalfBecker@outdoor-training.de>
* @version $Id$
*/
"use strict";
/*egw:uses
egw_action.egw_action_common;
egw_action.egw_action;
*/
/**
* Contains the action object interface implementation for the nextmatch widget
* row.
*/
/**
* An action object interface for each nextmatch widget row - "inherits" from
* egwActionObjectInterface
*/
function et2_dataview_rowAOI(_node)
{
var aoi = new egwActionObjectInterface();
aoi.node = _node;
aoi.checkBox = null; //($j(":checkbox", aoi.node))[0];
// Rows without a checkbox OR an id set are unselectable
aoi.doGetDOMNode = function() {
return aoi.node;
}
// Prevent the browser from selecting the content of the element, when
// a special key is pressed.
$j(_node).mousedown(egwPreventSelect);
// Now append some action code to the node
var selectHandler = function(e) {
// Reset the focus so that keyboard navigation will work properly
// after the element has been clicked
egwUnfocus();
// Reset the prevent selection code (in order to allow wanted
// selection of text)
_node.onselectstart = null;
if (e.target != aoi.checkBox)
{
var selected = egwBitIsSet(aoi.getState(), EGW_AO_STATE_SELECTED);
var state = EGW_AO_SHIFT_STATE_NONE; // Multiple row selction does not work right now
aoi.updateState(EGW_AO_STATE_SELECTED,
!egwBitIsSet(state, EGW_AO_SHIFT_STATE_MULTI) || !selected,
state);
}
};
if (egwIsMobile()) {
_node.ontouchend = selectHandler;
} else {
$j(_node).click(selectHandler);
}
$j(aoi.checkBox).change(function() {
aoi.updateState(EGW_AO_STATE_SELECTED, this.checked, EGW_AO_SHIFT_STATE_MULTI);
});
aoi.doSetState = function(_state) {
var selected = egwBitIsSet(_state, EGW_AO_STATE_SELECTED);
if (this.checkBox)
{
this.checkBox.checked = selected;
}
$j(this.node).toggleClass('focused',
egwBitIsSet(_state, EGW_AO_STATE_FOCUSED));
$j(this.node).toggleClass('selected',
selected);
}
return aoi;
}
/**
* Default action for nextmatch rows, runs action specified _action.data.nm_action: see nextmatch_widget::egw_actions()
*
* @param _action action object with attributes caption, id, nm_action, ...
* @param _senders array of rows selected
*/
function nm_action(_action, _senders)
{
// ignore checkboxes, unless they have an explicit defined nm_action
if (_action.checkbox && (!_action.data || typeof _action.data.nm_action == 'undefined')) return;
if (typeof _action.data == 'undefined' || !_action.data) _action.data = {};
if (typeof _action.data.nm_action == 'undefined') _action.data.nm_action = 'submit';
var ids = "";
for (var i = 0; i < _senders.length; i++)
{
ids += (_senders[i].id.indexOf(',') >= 0 ? '"'+_senders[i].id.replace(/"/g,'""')+'"' : _senders[i].id) +
((i < _senders.length - 1) ? "," : "");
}
//console.log(_action); console.log(_senders);
var mgr = _action.getManager();
var select_all = mgr.getActionById("select_all");
var confirm_msg = (_senders.length > 1 || select_all && select_all.checked) &&
typeof _action.data.confirm_multiple != 'undefined' ?
_action.data.confirm_multiple : _action.data.confirm;
// let user confirm the action first (if not select_all set and nm_action == 'submit' --> confirmed later)
if (!(select_all && select_all.checked && _action.data.nm_action == 'submit') &&
typeof _action.data.confirm != 'undefined')
{
if (!confirm(confirm_msg)) return;
}
// in case we only need to confirm multiple selected (only _action.data.confirm_multiple)
else if (typeof _action.data.confirm_multiple != 'undefined' && (_senders.length > 1 || select_all && select_all.checked))
{
if (!confirm(_action.data.confirm_multiple)) return;
}
var url = '#';
if (typeof _action.data.url != 'undefined')
{
url = _action.data.url.replace(/(\$|%24)id/,encodeURIComponent(ids));
}
var target = null;
if (typeof _action.data.target != 'undefined')
{
target = _action.data.target;
}
switch(_action.data.nm_action)
{
case 'alert':
alert(_action.caption + " (\'" + _action.id + "\') executed on rows: " + ids);
break;
case 'location':
if(target)
{
window.open(url, target);
}
else
{
window.location.href = url;
}
break;
case 'popup':
egw_openWindowCentered2(url,target,_action.data.width,_action.data.height);
break;
case 'egw_open':
var params = _action.data.egw_open.split('-'); // type-appname-idNum (idNum is part of id split by :), eg. "edit-infolog"
console.log(params);
var egw_open_id = _senders[0].id;
if (typeof params[2] != 'undefined') egw_open_id = egw_open_id.split(':')[params[2]];
egw_open(egw_open_id,params[1],params[0],params[3]);
break;
case 'open_popup':
// open div styled as popup contained in current form and named action.id+'_popup'
if (nm_popup_action == null)
{
nm_open_popup(_action, _senders);
break;
}
// fall through, if popup is open --> submit form
case 'submit':
// let user confirm select-all
if (select_all && select_all.checked)
{
if (!confirm((confirm_msg ? confirm_msg : _action.caption.replace(/^(&nbsp;| | )+/,''))+"\n\n"+select_all.hint)) return;
}
var checkboxes = mgr.getActionsByAttr("checkbox", true);
var checkboxes_elem = document.getElementById(mgr.etemplate_var_prefix+'[nm][checkboxes]');
if (checkboxes && checkboxes_elem)
for (var i in checkboxes)
checkboxes_elem.value += checkboxes[i].id + ":" + (checkboxes[i].checked ? "1" : "0") + ";";
document.getElementById(mgr.etemplate_var_prefix+'[nm][nm_action]').value = _action.id;
document.getElementById(mgr.etemplate_var_prefix+'[nm][selected]').value = ids;
if (typeof _action.data.button != 'undefined')
{
submitit(mgr.etemplate_form.context, mgr.etemplate_var_prefix+'[nm][rows]['+_action.data.button+']['+ids+']');
}
else
{
mgr.etemplate_form.submit();
}
// Clear action in case there's another one
document.getElementById(mgr.etemplate_var_prefix+'[nm][nm_action]').value = null;
break;
}
}
/**
* Callback to check if none of _senders rows has disableClass set
*
* @param _action egwAction object, we use _action.data.disableClass to check
* @param _senders array of egwActionObject objects
* @param _target egwActionObject object, get's called for every object in _senders
* @returns boolean true if none has disableClass, false otherwise
*/
function nm_not_disableClass(_action, _senders, _target)
{
return !$j(_target.iface.getDOMNode()).hasClass(_action.data.disableClass);
}
/**
* Callback to check if all of _senders rows have enableClass set
*
* @param _action egwAction object, we use _action.data.enableClass to check
* @param _senders array of egwActionObject objects
* @param _target egwActionObject object, get's called for every object in _senders
* @returns boolean true if none has disableClass, false otherwise
*/
function nm_enableClass(_action, _senders, _target)
{
return $j(_target.iface.getDOMNode()).hasClass(_action.data.enableClass);
}
/**
* Enable an _action, if it matches a given regular expresstion in _action.data.enableId
*
* @param _action egwAction object, we use _action.data.enableId to check
* @param _senders array of egwActionObject objects
* @param _target egwActionObject object, get's called for every object in _senders
* @returns boolean true if _target.id matches _action.data.enableId
*/
function nm_enableId(_action, _senders, _target)
{
if (typeof _action.data.enableId == 'string')
_action.data.enableId = new RegExp(_action.data.enableId);
return _target.id.match(_action.data.enableId);
}
/**
* Callback to check if a certain field (_action.data.fieldId) is (not) equal to given value (_action.data.fieldValue)
*
* If field is not found, we return false too!
*
* @param _action egwAction object, we use _action.data.fieldId to check agains _action.data.fieldValue
* @param _senders array of egwActionObject objects
* @param _target egwActionObject object, get's called for every object in _senders
* @returns boolean true if field found and has specified value, false otherwise
*/
function nm_compare_field(_action, _senders, _target)
{
var field = document.getElementById(_action.data.fieldId);
if (!field) return false;
var value = $j(field).val();
if (_action.data.fieldValue.substr(0,1) == '!')
return value != _action.data.fieldValue.substr(1);
return value == _action.data.fieldValue;
}
var nm_popup_action, nm_popup_senders = null;
/**
* Open popup for a certain action requiring further input
*
* Popup needs to have eTemplate name of action id plus "_popup"
*
* @param _action
* @param _senders
*/
function nm_open_popup(_action, _senders)
{
var popup = document.getElementById(_action.getManager().etemplate_var_prefix + '[' + _action.id + '_popup]');
if (popup) {
nm_popup_action = _action;
nm_popup_senders = _senders;
popup.style.display = 'block';
}
}
/**
* Submit a popup action
*/
function nm_submit_popup(button)
{
button.form.submit_button.value = button.name; // set name of button (sub-action)
// call regular nm_action to transmit action and senders correct
nm_action(nm_popup_action, nm_popup_senders);
}
/**
* Hide popup
*/
function nm_hide_popup(element, div_id)
{
var prefix = element.id.substring(0,element.id.indexOf('['));
var popup = document.getElementById(prefix+'['+div_id+']');
// Hide popup
if(popup) {
popup.style.display = 'none';
}
nm_popup_action = null;
nm_popup_senders = null;
return false;
}
/**
* Activate/click first link in row
*/
function nm_activate_link(_action, _senders)
{
// $j(_senders[0].iface.getDOMNode()).find('a:first').trigger('click'); not sure why this is NOT working
var a_href = $j(_senders[0].iface.getDOMNode()).find('a:first');
if (typeof a_href != undefined)
{
var target = a_href.attr('target');
var href = a_href.attr('href');
if (target)
window.open(href,target);
else
window.location = href;
}
}

View File

@ -38,6 +38,11 @@ var et2_dataview_rowProvider = Class.extend({
this._createFullRowPrototype();
this._createDefaultPrototype();
this._createEmptyPrototype();
this._createLoadingPrototype();
},
getColumnCount: function() {
return this._columnIds.length;
},
/**
@ -62,368 +67,15 @@ var et2_dataview_rowProvider = Class.extend({
return this._prototypes[_name].clone();
},
/**
* Returns an array containing objects which have variable attributes
*/
_getVariableAttributeSet: function(_widget) {
var variableAttributes = [];
_widget.iterateOver(function(_widget) {
// Create the attribtues
var hasAttr = false;
var widgetData = {
"widget": _widget,
"data": []
};
// Get all attribute values
for (var key in _widget.attributes)
{
if (!_widget.attributes[key].ignore &&
typeof _widget.options[key] != "undefined")
{
var val = _widget.options[key];
// TODO: Improve detection
if (typeof val == "string" && val.indexOf("$") >= 0)
{
hasAttr = true;
widgetData.data.push({
"attribute": key,
"expression": val
});
}
}
}
// Add the entry if there is any data in it
if (hasAttr)
{
variableAttributes.push(widgetData);
}
}, this);
return variableAttributes;
},
_seperateWidgets: function(_varAttrs) {
// The detachable array contains all widgets which implement the
// et2_IDetachedDOM interface for all needed attributes
var detachable = [];
// The remaining array creates all widgets which have to be completely
// cloned when the widget tree is created
var remaining = [];
// Iterate over the widgets
for (var i = 0; i < _varAttrs.length; i++)
{
var widget = _varAttrs[i].widget;
// Check whether the widget parents are not allready in the "remaining"
// slot - if this is the case do not include the widget at all.
var insertWidget = true;
var checkWidget = function (_widget) {
if (_widget.parent != null)
{
for (var i = 0; i < remaining.length; i++)
{
if (remaining[i].widget == _widget.parent)
{
insertWidget = false;
return;
}
}
checkWidget(_widget.parent);
}
};
checkWidget(widget);
// Handle the next widget if this one should not be included.
if (!insertWidget)
{
continue;
}
// Check whether the widget implements the et2_IDetachedDOM interface
var isDetachable = false;
if (widget.implements(et2_IDetachedDOM))
{
// Get all attributes the widgets supports to be set in the
// "detached" mode
var supportedAttrs = [];
widget.getDetachedAttributes(supportedAttrs);
supportedAttrs.push("id");
isDetachable = true;
for (var j = 0; j < _varAttrs[i].data.length/* && isDetachable*/; j++)
{
var data = _varAttrs[i].data[j];
var supportsAttr = supportedAttrs.indexOf(data.attribute) != -1;
if (!supportsAttr)
{
egw.debug("warn", "et2_IDetachedDOM widget " +
widget._type + " does not support " + data.attribute);
}
isDetachable &= supportsAttr;
}
}
// Insert the widget into the correct slot
if (isDetachable)
{
detachable.push(_varAttrs[i]);
}
else
{
remaining.push(_varAttrs[i]);
}
}
return {
"detachable": detachable,
"remaining": remaining
};
},
/**
* Removes to DOM code for all widgets in the "remaining" slot
*/
_stripTemplateRow: function(_rowTemplate) {
_rowTemplate.placeholders = [];
for (var i = 0; i < _rowTemplate.seperated.remaining.length; i++)
{
var entry = _rowTemplate.seperated.remaining[i];
// Issue a warning - widgets which do not implement et2_IDOMNode
// are very slow
egw.debug("warn", "Non-clonable widget '"+ entry.widget._type + "' in dataview row - this " +
"might be slow", entry);
// Set the placeholder for the entry to null
entry.placeholder = null;
// Get the outer DOM-Node of the widget
if (entry.widget.implements(et2_IDOMNode))
{
var node = entry.widget.getDOMNode(entry.widget);
if (node && node.parentNode)
{
// Get the parent node and replace the node with a placeholder
entry.placeholder = document.createElement("span");
node.parentNode.replaceChild(entry.placeholder, node);
_rowTemplate.placeholders.push({
"widget": entry.widget,
"func": this._compileDOMAccessFunc(_rowTemplate.row,
entry.placeholder)
});
}
}
}
},
_nodeIndex: function(_node) {
if(_node.parentNode == null)
{
return 0;
}
for (var i = 0; i < _node.parentNode.childNodes.length; i++)
{
if (_node.parentNode.childNodes[i] == _node)
{
return i;
}
}
return -1;
},
/**
* Returns a function which does a relative access on the given DOM-Node
*/
_compileDOMAccessFunc: function(_root, _target) {
function recordPath(_root, _target, _path)
{
if (typeof _path == "undefined")
{
_path = [];
}
if (_root != _target && _target)
{
// Get the index of _target in its parent node
var idx = this._nodeIndex(_target);
if (idx >= 0)
{
// Add the access selector
_path.unshift("childNodes[" + idx + "]");
// Record the remaining path
return recordPath.call(this, _root, _target.parentNode, _path);
}
throw("Internal error while compiling DOM access function.");
}
else
{
_path.unshift("_node");
return "return " + _path.join(".") + ";";
}
}
return new Function("_node", recordPath.call(this, _root, _target));
},
/**
* Builds relative paths to the DOM-Nodes and compiles fast-access functions
*/
_buildNodeAccessFuncs: function(_rowTemplate) {
for (var i = 0; i < _rowTemplate.seperated.detachable.length; i++)
{
var entry = _rowTemplate.seperated.detachable[i];
// Get all needed nodes from the widget
var nodes = entry.widget.getDetachedNodes();
var nodeFuncs = entry.nodeFuncs = new Array(nodes.length);
// Record the path to each DOM-Node
for (var j = 0; j < nodes.length; j++)
{
nodeFuncs[j] = this._compileDOMAccessFunc(_rowTemplate.row,
nodes[j]);
}
}
},
/**
* Creates the data row prototype
*/
setDataRowTemplate: function(_widgets, _rootWidget) {
// Copy the root widget
this._rootWidget = _rootWidget;
// Create the base row
var row = this.getPrototype("default");
// Copy the row template
var rowTemplate = {
"row": row[0],
"widgets": _widgets,
"root": _rootWidget,
"seperated": null,
"mgrs": _rootWidget.getArrayMgrs()
};
// Create the row widget and insert the given widgets into the row
var rowWidget = new et2_dataview_rowWidget(rowTemplate.mgrs, row[0]);
rowWidget._parent = _rootWidget;
rowWidget.createWidgets(_widgets);
// Get the set containing all variable attributes
var variableAttributes = this._getVariableAttributeSet(rowWidget);
// Filter out all widgets which do not implement the et2_IDetachedDOM
// interface or do not support all attributes listed in the et2_IDetachedDOM
// interface. A warning is issued for all those widgets as they heavily
// degrade the performance of the dataview
var seperated = rowTemplate.seperated =
this._seperateWidgets(variableAttributes);
// Remove all DOM-Nodes of all widgets inside the "remaining" slot from
// the row-template, then build the access functions for the detachable
// widgets
this._stripTemplateRow(rowTemplate);
this._buildNodeAccessFuncs(rowTemplate);
this._prototypes["dataRow"] = row;
this._template = rowTemplate;
},
getDataRow: function(_data, _row, _idx) {
// Create array managers with the given data merged in
var mgrs = et2_arrayMgrs_expand(rowWidget, this._template.mgrs,
_data, _idx);
// Insert the widgets into the row which do not provide the functions
// to set the _data directly
var rowWidget = null;
if (this._template.seperated.remaining.length > 0)
{
// Transform the variable attributes
for (var i = 0; i < this._template.seperated.remaining.length; i++)
{
var entry = this._template.seperated.remaining[i];
for (var j = 0; j < entry.data.length; j++)
{
var set = entry.data[j];
entry.widget.options[set.attribute] = mgrs["content"].expandName(set.expression);
}
}
// Create the row widget
var rowWidget = new et2_dataview_rowTemplateWidget(this._rootWidget,
_row[0]);
// Let the row widget create the widgets
rowWidget.createWidgets(mgrs, this._template.placeholders);
}
// Update the content of all other widgets
for (var i = 0; i < this._template.seperated.detachable.length; i++)
{
var entry = this._template.seperated.detachable[i];
// Parse the attribute expressions
var data = {};
for (var j = 0; j < entry.data.length; j++)
{
var set = entry.data[j];
data[set.attribute] = mgrs["content"].expandName(set.expression);
}
// Retrieve all DOM-Nodes
var nodes = new Array(entry.nodeFuncs.length);
for (var j = 0; j < nodes.length; j++)
{
// Use the previously compiled node function to get the node
// from the entry
nodes[j] = entry.nodeFuncs[j](_row[0]);
}
if(typeof nodes[0] == "undefined")
egw.debug("warn", "Missing node", entry.widget.id,nodes, entry );
// Set the array managers first
entry.widget._mgrs = mgrs;
if (typeof data.id != "undefined")
{
entry.widget.id = data.id;
}
// Adjust data for that row
entry.widget.transformAttributes.call(entry.widget,data);
// Call the setDetachedAttributes function
entry.widget.setDetachedAttributes(nodes, data);
}
return rowWidget;
},
/* ---- PRIVATE FUNCTIONS ---- */
_createFullRowPrototype: function() {
var tr = $j(document.createElement("tr"));
var td = $j(document.createElement("td"))
.attr("span", this._columnIds.length)
.addClass(this._outerId + "_td_fullRow")
.attr("colspan", this._columnIds.length)
.appendTo(tr);
var div = $j(document.createElement("div"))
.addClass(this._outerId + "_div_fullRow")
@ -452,106 +104,14 @@ var et2_dataview_rowProvider = Class.extend({
_createEmptyPrototype: function() {
this._prototypes["empty"] = $j(document.createElement("tr"));
},
_createLoadingPrototype: function() {
var fullRow = this.getPrototype("fullRow");
$j("div", fullRow).addClass("loading");
this._prototypes["loading"] = fullRow;
}
});
var et2_dataview_rowWidget = et2_widget.extend(et2_IDOMNode, {
init: function(_mgrs, _row) {
// Call the parent constructor with some dummy attributes
this._super(null, {"id": "", "type": "rowWidget"});
// Initialize some variables
this._widgets = [];
// Copy the given DOM node and the content arrays
this._mgrs = _mgrs;
this._row = _row;
},
/**
* Copies the given array manager and clones the given widgets and inserts
* them into the row which has been passed in the constructor.
*/
createWidgets: function(_widgets) {
// Clone the given the widgets with this element as parent
this._widgets = new Array(_widgets.length);
for (var i = 0; i < _widgets.length; i++)
{
this._widgets[i] = _widgets[i].clone(this);
this._widgets[i].loadingFinished();
}
},
/**
* Returns the column node for the given sender
*/
getDOMNode: function(_sender) {
for (var i = 0; i < this._widgets.length; i++)
{
if (this._widgets[i] == _sender)
{
return this._row.childNodes[i].childNodes[0]; // Return the i-th td tag
}
}
return null;
}
});
var et2_dataview_rowTemplateWidget = et2_widget.extend(et2_IDOMNode, {
init: function(_root, _row) {
// Call the parent constructor with some dummy attributes
this._super(null, {"id": "", "type": "rowTemplateWidget"});
this._root = _root;
this._mgrs = {};
this._row = _row;
// Set parent to root widget, so sub-widget calls still work
this._parent = _root;
// Clone the widgets inside the placeholders array
this._widgets = [];
},
createWidgets: function(_mgrs, _widgets) {
// Set the array managers - don't use setArrayMgrs here as this creates
// an unnecessary copy of the object
this._mgrs = _mgrs;
this._widgets = new Array(_widgets.length);
for (var i = 0; i < _widgets.length; i++)
{
this._row.childNodes[0].childNodes[0];
this._widgets[i] = {
"widget": _widgets[i].widget.clone(this),
"node": _widgets[i].func(this._row)
};
this._widgets[i].widget.loadingFinished();
}
},
/**
* Returns the column node for the given sender
*/
getDOMNode: function(_sender) {
for (var i = 0; i < this._widgets.length; i++)
{
if (this._widgets[i].widget == _sender)
{
return this._widgets[i].node;
}
}
return null;
}
});

View File

@ -19,25 +19,69 @@
var et2_dataview_spacer = et2_dataview_container.extend({
init: function(_dataProvider, _rowProvider, _invalidationElem) {
init: function (_parent, _rowProvider) {
// Call the inherited container constructor
this._super(_dataProvider, _rowProvider, _invalidationElem);
this._super(_parent);
// Initialize the row count and the row height
this._count = 0;
this._rowHeight = 19;
this._avgSum = 0;
this._avgCount = 0;
// Get the spacer row and append it to the container
this.spacerNode = this.rowProvider.getPrototype("spacer",
this.spacerNode = _rowProvider.getPrototype("spacer",
this._createSpacerPrototype, this);
this._phDiv = $j("td", this.spacerNode);
this.appendNode(this.spacerNode);
},
setHeight: function(_height) {
this._phDiv.height(_height);
setCount: function (_count, _rowHeight) {
// Set the new count and _rowHeight if given
this._count = _count;
if (typeof _rowHeight !== "undefined")
{
this._rowHeight = _rowHeight;
}
// Update the element height
this._phDiv.height(this._count * this._rowHeight);
// Call the invalidate function
this.invalidate();
},
getCount: function () {
return this._count;
},
getHeight: function () {
// Set the calculated height, so that "invalidate" will work correctly
this._height = this._count * this._rowHeight;
return this._height;
},
getAvgHeightData: function () {
if (this._avgCount > 0)
{
return {
"avgHeight": this._avgSum / this._avgCount,
"avgCount": this._avgCount
};
}
return null;
},
addAvgHeight: function (_height) {
this._avgSum += _height;
this._avgCount++;
},
/* ---- PRIVATE FUNCTIONS ---- */
_createSpacerPrototype: function(_outerId, _columnIds) {
_createSpacerPrototype: function (_outerId, _columnIds) {
var tr = $j(document.createElement("tr"));
var td = $j(document.createElement("td"))

View File

@ -33,13 +33,13 @@
et2_widget_selectbox;
et2_extension_customfields;
// Include the dynheight manager
// Include all nextmatch subclasses
et2_extension_nextmatch_controller;
et2_extension_nextmatch_rowProvider;
et2_extension_nextmatch_dynheight;
// Include the grid classes
et2_dataview_view_gridContainer;
et2_dataview_model_dataProvider;
et2_dataview_model_columns;
et2_dataview;
*/
@ -115,25 +115,19 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
this.dynheight = new et2_dynheight(this.egw().window,
this.innerDiv, 150);
// Create the action manager
this.actionManager = new egwActionManager();
// Create the data provider which cares about streaming the row data
// efficiently to the rows.
var total = typeof this.options.settings.total != "undefined" ?
this.options.settings.total : 0;
this.dataProvider = new et2_dataview_dataProvider(this, total,
this.actionManager);
// Load the first data into the dataProvider
if (this.options.settings.rows)
/* if (this.options.settings.rows)
{
this.dataProvider.loadData({"rows": this.options.settings.rows});
}
}*/
// Create the outer grid container
this.dataviewContainer = new et2_dataview_gridContainer(this.innerDiv,
this.dataProvider, this.egw());
this.dataview = new et2_dataview(this.innerDiv, this.egw());
// We cannot create the grid controller now, as this depends on the grid
// instance, which can first be created once we have the columns
this.controller = null;
this.rowProvider = null;
this.activeFilters = {};
},
@ -173,31 +167,10 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
*/
resize: function() {
this.dynheight.update(function(_w, _h) {
this.dataviewContainer.resize(_w, _h);
this.dataview.resize(_w, _h);
}, this);
},
/**
* Get Rows callback
*/
getRows: function(_fetchList, _callback, _context) {
// Create an ajax-request
var request = new egw_json_request(
"etemplate_widget_nextmatch::ajax_get_rows::etemplate", [
this.getInstanceManager().etemplate_exec_id,
_fetchList,
this.activeFilters
], this);
var nextmatch = this;
// Send the request
request.sendRequest(true, function(_data) {
_callback.call(_context, _data);
// Let anything else know
nextmatch.div.trigger({type:'nm_data', nm_data: _data, 'nextmatch': nextmatch});
}, null);
},
/**
* Sorts the nextmatch widget by the given ID.
*
@ -270,10 +243,8 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
applyFilters: function() {
this.egw().debug("info", "Changing nextmatch filters to ", this.activeFilters);
// Clear the dataprovider and the dataview container - this will cause
// the grid to reload.
this.dataProvider.clear();
this.dataviewContainer.clear();
// Update the filters in the grid controller
this.controller.setFilters(this.activeFilters);
},
/**
@ -431,7 +402,7 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
* for next time
*/
_updateUserPreferences: function() {
var colMgr = this.dataviewContainer.getColumnMgr()
var colMgr = this.dataview.getColumnMgr()
if(!this.options.settings.columnselection_pref) {
this.options.settings.columnselection_pref = this.options.template;
}
@ -510,7 +481,6 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
"widget": _row[x].widget
};
columnData[x] = {
"id": "col_" + x,
"caption": this._genColumnCaption(_row[x].widget),
@ -524,14 +494,22 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
}
// Create the column manager and update the grid container
this.dataviewContainer.setColumns(columnData);
this.dataview.setColumns(columnData);
// Create the nextmatch row provider
this.rowProvider = new et2_nextmatch_rowProvider(
this.dataview.rowProvider);
var self = this;
// Register handler to update preferences when column properties are changed
this.dataviewContainer.onUpdateColumns = function() { self._updateUserPreferences();};
var self = this;
this.dataview.onUpdateColumns = function() {
self._updateUserPreferences();
};
// Register handler for column selection popup
this.dataviewContainer.selectColumnsClick = function(event) { self._selectColumnsClick(event);};
this.dataview.selectColumnsClick = function(event) {
self._selectColumnsClick(event);
};
},
_parseDataRow: function(_row, _colData) {
@ -552,7 +530,22 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
}
}
this.dataviewContainer.rowProvider.setDataRowTemplate(columnWidgets, this);
this.rowProvider.setDataRowTemplate(columnWidgets, this);
// Set the initial row count
var total = typeof this.options.settings.total != "undefined" ?
this.options.settings.total : 0;
this.dataview.grid.setTotalCount(total);
// Create the grid controller
this.controller = new et2_nextmatch_controller(
this.egw(),
this.getInstanceManager().etemplate_exec_id,
"nm",
this.dataview.grid,
this.rowProvider);
this.controller.setFilters(this.activeFilters);
},
_parseGrid: function(_grid) {
@ -573,7 +566,7 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
_selectColumnsClick: function(e) {
var self = this;
var columnMgr = this.dataviewContainer.columnMgr;
var columnMgr = this.dataview.getColumnMgr();
var columns = {};
var columns_selected = [];
for (var i = 0; i < columnMgr.columns.length; i++)
@ -662,7 +655,7 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
columnMgr.setColumnVisibilitySet(visibility);
self.selectPopup.toggle();
self.dataviewContainer.updateColumns();
self.dataview.updateColumns();
// Set default?
if(defaultCheck.get_value() == "true")
@ -753,7 +746,7 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
* Activates the actions
*/
set_settings: function(_settings) {
if (_settings.actions)
/* if (_settings.actions)
{
// Read the actions from the settings array
this.actionManager.updateActions(_settings.actions);
@ -761,7 +754,7 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
// this is rather hackisch, but I have no idea how to get the action_link & row_id to the actionObject of the row otherwise
this.actionManager.action_links = _settings.action_links;
this.actionManager.row_id = _settings.row_id;
}
}*/
},
getDOMNode: function(_sender) {
@ -774,7 +767,7 @@ var et2_nextmatch = et2_DOMWidget.extend(et2_IResizeable, {
{
if (_sender == this.columns[i].widget)
{
return this.dataviewContainer.getHeaderContainerNode(i);
return this.dataview.getHeaderContainerNode(i);
}
}
@ -1171,7 +1164,7 @@ var et2_nextmatch_customfields = et2_customfields_list.extend(et2_INextmatchHead
// not ready yet
return;
}
var columnMgr = this.nextmatch.dataviewContainer.columnMgr;
var columnMgr = this.nextmatch.dataview.getColumnMgr();
var nm_column = null;
for(var i = 0; i < this.nextmatch.columns.length; i++)
{

View File

@ -0,0 +1,135 @@
/**
* eGroupWare eTemplate2 - Class which contains a the data model for nextmatch widgets
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2012
* @version $Id$
*/
"use strict";
/*egw:uses
et2_core_common;
et2_core_inheritance;
et2_dataview_view_row;
et2_dataview_controller;
et2_dataview_interfaces;
egw_data;
*/
var et2_nextmatch_controller = et2_dataview_controller.extend(
et2_IDataProvider, {
/**
* Initializes the nextmatch controller.
*
* @param _egw is the api instance
* @param _execId is the execId of the etemplate
* @param _widgetId is the id of the nextmatch-widget we are fetching data
* for.
*/
init: function (_egw, _execId, _widgetId, _grid, _rowProvider) {
// Call the parent et2_dataview_controller constructor
this._super(_grid, this, this._rowCallback, this);
// Copy all parameters
this.egw = _egw;
this._execId = _execId;
this._widgetId = _widgetId;
this._rowProvider = _rowProvider;
// We start with no filters
this._filters = {};
// Directly use the API-Implementation of dataRegisterUID and
// dataUnregisterUID
this.dataRegisterUID = _egw.dataRegisterUID;
this.dataUnregisterUID = _egw.dataUnregisterUID;
},
/**
* Updates the filter instance.
*/
setFilters: function (_filters) {
// Update the filters, reset the "lastModification"
this._filters = _filters;
this._lastModified = null;
// Trigger an update
this.update();
},
/** -- PRIVATE FUNCTIONS -- **/
/**
* Overwrites the inherited _destroyCallback function in order to be able
* to free the "rowWidget".
*/
_destroyCallback: function (_row) {
// Destroy any widget associated to the row
if (this.entry.widget)
{
this.entry.widget.free();
this.entry.widget = null;
}
// Call the inherited function
this._super.call(this, arguments);
},
/**
* Creates the actual data row.
*
* @param _data is an array containing the row data
* @param _tr is the tr into which the data will be inserted
* @param _idx is the index of the row
* @param _entry is the internal row datastructure of the controller, in
* this special case used to store the rowWidget reference, so that it can
* be properly freed.
*/
_rowCallback: function (_data, _tr, _idx, _entry) {
// Let the row provider fill in the data row -- store the returned
// rowWidget inside the _entry
_entry.widget = this._rowProvider.getDataRow(
{ "content": _data }, _tr, _idx);
},
/** -- Implementation of et2_IDataProvider -- **/
dataFetch: function (_queriedRange, _lastModification, _callback,
_context) {
// Pass the fetch call to the API, multiplex the data about the
// nextmatch instance into the call.
this.egw.dataFetch(
this._execId,
_queriedRange,
this._filters,
this._widgetId,
_lastModification,
_callback,
_context);
},
dataRegisterUID: function () {
// Overwritten in the constructor
},
dataUnregisterUID: function () {
// Overwritten in the constructor
}
});

View File

@ -57,9 +57,6 @@ var et2_dynheight = Class.extend({
// Initialize the height calculation
this._initialize();
console.log(this.outerNode);
console.log(this.innerNode);
// Get the outer container height
var oh = this.outerNode.height();

View File

@ -0,0 +1,504 @@
/**
* eGroupWare eTemplate2 - Class which contains a factory method for rows
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link http://www.egroupware.org
* @author Andreas Stöckel
* @copyright Stylite 2012
* @version $Id$
*/
/*egw:uses
jquery.jquery;
et2_core_inheritance;
et2_core_interfaces;
et2_core_arrayMgr;
et2_core_widget;
*/
/**
* The row provider contains prototypes (full clonable dom-trees)
* for all registered row types.
*/
var et2_nextmatch_rowProvider = Class.extend({
/**
* Creates the nextmatch row provider.
*/
init: function (_rowProvider) {
// Copy the arguments
this._rowProvider = _rowProvider;
},
/**
* Creates the data row prototype
*/
setDataRowTemplate: function(_widgets, _rootWidget) {
// Copy the root widget
this._rootWidget = _rootWidget;
// Create the base row
var row = this._rowProvider.getPrototype("default");
// Copy the row template
var rowTemplate = {
"row": row[0],
"widgets": _widgets,
"root": _rootWidget,
"seperated": null,
"mgrs": _rootWidget.getArrayMgrs()
};
// Create the row widget and insert the given widgets into the row
var rowWidget = new et2_nextmatch_rowWidget(rowTemplate.mgrs, row[0]);
rowWidget._parent = _rootWidget;
rowWidget.createWidgets(_widgets);
// Get the set containing all variable attributes
var variableAttributes = this._getVariableAttributeSet(rowWidget);
// Filter out all widgets which do not implement the et2_IDetachedDOM
// interface or do not support all attributes listed in the et2_IDetachedDOM
// interface. A warning is issued for all those widgets as they heavily
// degrade the performance of the dataview
var seperated = rowTemplate.seperated =
this._seperateWidgets(variableAttributes);
// Remove all DOM-Nodes of all widgets inside the "remaining" slot from
// the row-template, then build the access functions for the detachable
// widgets
this._stripTemplateRow(rowTemplate);
this._buildNodeAccessFuncs(rowTemplate);
// Create the DOM row template
var tmpl = document.createDocumentFragment();
row.children().each(function() { tmpl.appendChild(this); });
this._dataRow = tmpl;
this._template = rowTemplate;
},
getDataRow: function(_data, _tr, _idx) {
// Clone the row template
var row = this._dataRow.cloneNode(true);
// Create array managers with the given data merged in
var mgrs = et2_arrayMgrs_expand(rowWidget, this._template.mgrs,
_data, _idx);
// Insert the widgets into the row which do not provide the functions
// to set the _data directly
var rowWidget = null;
if (this._template.seperated.remaining.length > 0)
{
// Transform the variable attributes
for (var i = 0; i < this._template.seperated.remaining.length; i++)
{
var entry = this._template.seperated.remaining[i];
for (var j = 0; j < entry.data.length; j++)
{
var set = entry.data[j];
entry.widget.options[set.attribute] = mgrs["content"].expandName(set.expression);
}
}
// Create the row widget
var rowWidget = new et2_nextmatch_rowTemplateWidget(this._rootWidget,
row);
// Let the row widget create the widgets
rowWidget.createWidgets(mgrs, this._template.placeholders);
}
// Update the content of all other widgets
for (var i = 0; i < this._template.seperated.detachable.length; i++)
{
var entry = this._template.seperated.detachable[i];
// Parse the attribute expressions
var data = {};
for (var j = 0; j < entry.data.length; j++)
{
var set = entry.data[j];
data[set.attribute] = mgrs["content"].expandName(set.expression);
}
// Retrieve all DOM-Nodes
var nodes = new Array(entry.nodeFuncs.length);
for (var j = 0; j < nodes.length; j++)
{
// Use the previously compiled node function to get the node
// from the entry
nodes[j] = entry.nodeFuncs[j](row);
}
if(typeof nodes[0] == "undefined")
egw.debug("warn", "Missing node", entry.widget.id,nodes, entry );
// Set the array managers first
entry.widget._mgrs = mgrs;
if (typeof data.id != "undefined")
{
entry.widget.id = data.id;
}
// Adjust data for that row
entry.widget.transformAttributes.call(entry.widget,data);
// Call the setDetachedAttributes function
entry.widget.setDetachedAttributes(nodes, data);
}
// Insert the row into the tr
_tr.appendChild(row);
return rowWidget;
},
/** -- PRIVATE FUNCTIONS -- **/
/**
* Returns an array containing objects which have variable attributes
*/
_getVariableAttributeSet: function(_widget) {
var variableAttributes = [];
_widget.iterateOver(function(_widget) {
// Create the attribtues
var hasAttr = false;
var widgetData = {
"widget": _widget,
"data": []
};
// Get all attribute values
for (var key in _widget.attributes)
{
if (!_widget.attributes[key].ignore &&
typeof _widget.options[key] != "undefined")
{
var val = _widget.options[key];
// TODO: Improve detection
if (typeof val == "string" && val.indexOf("$") >= 0)
{
hasAttr = true;
widgetData.data.push({
"attribute": key,
"expression": val
});
}
}
}
// Add the entry if there is any data in it
if (hasAttr)
{
variableAttributes.push(widgetData);
}
}, this);
return variableAttributes;
},
_seperateWidgets: function(_varAttrs) {
// The detachable array contains all widgets which implement the
// et2_IDetachedDOM interface for all needed attributes
var detachable = [];
// The remaining array creates all widgets which have to be completely
// cloned when the widget tree is created
var remaining = [];
// Iterate over the widgets
for (var i = 0; i < _varAttrs.length; i++)
{
var widget = _varAttrs[i].widget;
// Check whether the widget parents are not allready in the "remaining"
// slot - if this is the case do not include the widget at all.
var insertWidget = true;
var checkWidget = function (_widget) {
if (_widget.parent != null)
{
for (var i = 0; i < remaining.length; i++)
{
if (remaining[i].widget == _widget.parent)
{
insertWidget = false;
return;
}
}
checkWidget(_widget.parent);
}
};
checkWidget(widget);
// Handle the next widget if this one should not be included.
if (!insertWidget)
{
continue;
}
// Check whether the widget implements the et2_IDetachedDOM interface
var isDetachable = false;
if (widget.implements(et2_IDetachedDOM))
{
// Get all attributes the widgets supports to be set in the
// "detached" mode
var supportedAttrs = [];
widget.getDetachedAttributes(supportedAttrs);
supportedAttrs.push("id");
isDetachable = true;
for (var j = 0; j < _varAttrs[i].data.length/* && isDetachable*/; j++)
{
var data = _varAttrs[i].data[j];
var supportsAttr = supportedAttrs.indexOf(data.attribute) != -1;
if (!supportsAttr)
{
egw.debug("warn", "et2_IDetachedDOM widget " +
widget._type + " does not support " + data.attribute);
}
isDetachable &= supportsAttr;
}
}
// Insert the widget into the correct slot
if (isDetachable)
{
detachable.push(_varAttrs[i]);
}
else
{
remaining.push(_varAttrs[i]);
}
}
return {
"detachable": detachable,
"remaining": remaining
};
},
/**
* Removes to DOM code for all widgets in the "remaining" slot
*/
_stripTemplateRow: function(_rowTemplate) {
_rowTemplate.placeholders = [];
for (var i = 0; i < _rowTemplate.seperated.remaining.length; i++)
{
var entry = _rowTemplate.seperated.remaining[i];
// Issue a warning - widgets which do not implement et2_IDOMNode
// are very slow
egw.debug("warn", "Non-clonable widget '"+ entry.widget._type + "' in dataview row - this " +
"might be slow", entry);
// Set the placeholder for the entry to null
entry.placeholder = null;
// Get the outer DOM-Node of the widget
if (entry.widget.implements(et2_IDOMNode))
{
var node = entry.widget.getDOMNode(entry.widget);
if (node && node.parentNode)
{
// Get the parent node and replace the node with a placeholder
entry.placeholder = document.createElement("span");
node.parentNode.replaceChild(entry.placeholder, node);
_rowTemplate.placeholders.push({
"widget": entry.widget,
"func": this._compileDOMAccessFunc(_rowTemplate.row,
entry.placeholder)
});
}
}
}
},
_nodeIndex: function(_node) {
if(_node.parentNode == null)
{
return 0;
}
for (var i = 0; i < _node.parentNode.childNodes.length; i++)
{
if (_node.parentNode.childNodes[i] == _node)
{
return i;
}
}
return -1;
},
/**
* Returns a function which does a relative access on the given DOM-Node
*/
_compileDOMAccessFunc: function(_root, _target) {
function recordPath(_root, _target, _path)
{
if (typeof _path == "undefined")
{
_path = [];
}
if (_root != _target && _target)
{
// Get the index of _target in its parent node
var idx = this._nodeIndex(_target);
if (idx >= 0)
{
// Add the access selector
_path.unshift("childNodes[" + idx + "]");
// Record the remaining path
return recordPath.call(this, _root, _target.parentNode, _path);
}
throw("Internal error while compiling DOM access function.");
}
else
{
_path.unshift("_node");
return "return " + _path.join(".") + ";";
}
}
return new Function("_node", recordPath.call(this, _root, _target));
},
/**
* Builds relative paths to the DOM-Nodes and compiles fast-access functions
*/
_buildNodeAccessFuncs: function(_rowTemplate) {
for (var i = 0; i < _rowTemplate.seperated.detachable.length; i++)
{
var entry = _rowTemplate.seperated.detachable[i];
// Get all needed nodes from the widget
var nodes = entry.widget.getDetachedNodes();
var nodeFuncs = entry.nodeFuncs = new Array(nodes.length);
// Record the path to each DOM-Node
for (var j = 0; j < nodes.length; j++)
{
nodeFuncs[j] = this._compileDOMAccessFunc(_rowTemplate.row,
nodes[j]);
}
}
}
});
var et2_nextmatch_rowWidget = et2_widget.extend(et2_IDOMNode, {
init: function(_mgrs, _row) {
// Call the parent constructor with some dummy attributes
this._super(null, {"id": "", "type": "rowWidget"});
// Initialize some variables
this._widgets = [];
// Copy the given DOM node and the content arrays
this._mgrs = _mgrs;
this._row = _row;
},
/**
* Copies the given array manager and clones the given widgets and inserts
* them into the row which has been passed in the constructor.
*/
createWidgets: function(_widgets) {
// Clone the given the widgets with this element as parent
this._widgets = new Array(_widgets.length);
for (var i = 0; i < _widgets.length; i++)
{
this._widgets[i] = _widgets[i].clone(this);
this._widgets[i].loadingFinished();
}
},
/**
* Returns the column node for the given sender
*/
getDOMNode: function(_sender) {
for (var i = 0; i < this._widgets.length; i++)
{
if (this._widgets[i] == _sender)
{
return this._row.childNodes[i].childNodes[0]; // Return the i-th td tag
}
}
return null;
}
});
var et2_nextmatch_rowTemplateWidget = et2_widget.extend(et2_IDOMNode, {
init: function(_root, _row) {
// Call the parent constructor with some dummy attributes
this._super(null, {"id": "", "type": "rowTemplateWidget"});
this._root = _root;
this._mgrs = {};
this._row = _row;
// Set parent to root widget, so sub-widget calls still work
this._parent = _root;
// Clone the widgets inside the placeholders array
this._widgets = [];
},
createWidgets: function(_mgrs, _widgets) {
// Set the array managers - don't use setArrayMgrs here as this creates
// an unnecessary copy of the object
this._mgrs = _mgrs;
this._widgets = new Array(_widgets.length);
for (var i = 0; i < _widgets.length; i++)
{
this._row.childNodes[0].childNodes[0];
this._widgets[i] = {
"widget": _widgets[i].widget.clone(this),
"node": _widgets[i].func(this._row)
};
this._widgets[i].widget.loadingFinished();
}
},
/**
* Returns the column node for the given sender
*/
getDOMNode: function(_sender) {
for (var i = 0; i < this._widgets.length; i++)
{
if (this._widgets[i].widget == _sender)
{
return this._widgets[i].node;
}
}
return null;
}
});

View File

@ -39,6 +39,7 @@
.egwGridView_spacer {
background-image: url(gfx/non_loaded_bg.png);
background-position: top left;
/* background-attachment: fixed;*/
}
.egwGridView_outer {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,174 @@
<html>
<head>
<title>ET2 - Test</title>
<script src="jquery.js"></script>
<script src="../../../phpgwapi/js/jsapi/jsapi.js"></script>
<script src="../../../phpgwapi/js/egw_json.js"></script>
<script src="../../../phpgwapi/js/jquery/jquery.tools.min.js"></script>
<script src="../../../phpgwapi/js/jquery/jquery-ui.js"></script>
<script>
// Create the egw object template
window["egw"] = {
'prefsOnly': true
};
</script>
<script src="../../../phpgwapi/js/jsapi/egw_core.js"></script>
<script src="../../../phpgwapi/js/jsapi/egw_utils.js"></script>
<script src="../../../phpgwapi/js/jsapi/egw_css.js"></script>
<script src="../../../phpgwapi/js/jsapi/egw_debug.js"></script>
<script src="../et2_core_inheritance.js"></script>
<script src="../et2_core_interfaces.js"></script>
<script src="../et2_core_common.js"></script>
<script src="../et2_extension_nextmatch_dynheight.js"></script>
<script src="../et2_dataview_interfaces.js"></script>
<script src="../et2_dataview_controller.js"></script>
<script src="../et2_dataview_model_columns.js"></script>
<script src="../et2_dataview_view_rowProvider.js"></script>
<script src="../et2_dataview_view_container.js"></script>
<script src="../et2_dataview_view_grid.js"></script>
<script src="../et2_dataview_view_spacer.js"></script>
<script src="../et2_dataview_view_row.js"></script>
<script src="../et2_dataview_view_resizeable.js"></script>
<script src="../et2_dataview.js"></script>
<link rel="StyleSheet" type="text/css" href="./test.css" />
<link rel="StyleSheet" type="text/css" href="./grid.css" />
</head>
<body>
<h1>EGroupware ETemplate2 Dataview Test</h1>
<div>
<button id="update">Update</button>
<button id="refresh">Refresh</button>
<span id="range" style="float: right; font-size: 12pt; padding-right: 10px">0-0</span>
</div>
<div id="container" style="margin:0; padding:0;">
</div>
<script type="text/javascript">
document.getElementById("update").onclick = function () {
controller.update();
};
document.getElementById("refresh").onclick = function () {
controller.reset();
};
// The column data object contains the data which is used to setup
// the column properties (visibility, width) etc. "caption" is only
// used to build the column selection menu (which is not used in
// this example)
var columnData = [
{
"id": "col_0",
"caption": "Name",
"visibility": ET2_COL_VISIBILITY_VISIBLE,
"width": "50%"
},
{
"id": "col_1",
"caption": "Size",
"visibility": ET2_COL_VISIBILITY_VISIBLE,
"width": "auto"
},
{
"id": "col_2",
"caption": "Creation Date",
"visibility": ET2_COL_VISIBILITY_VISIBLE,
"width": "auto"
}
];
var data = new Array(1000);
for (var i = 0; i < data.length; i++)
{
data[i] = "uid_" + i;
}
var dataprovider = Class.extend(et2_IDataProvider, {
dataFetch: function (_queriedRange, _lastModification, _callback, _context) {
var response = {
"order": data.slice(_queriedRange.start, _queriedRange.start + _queriedRange.num_rows),
"total": data.length,
"lastModification": 0
};
window.setTimeout(function () {
_callback.call(_context, response)
}, Math.round(100 + Math.random() * 750));
},
dataRegisterUID: function (_uid, _callback, _context) {
_callback.call(_context, {
"caption": "Row " + _uid
});
},
dataUnregisterUID: function (_uid, _callback, _context) {
//
}
});
/**
* The row callback gets called by the gridview controller whenever
* the actual DOM-Nodes for a node with the given data have to be
* created.
*/
function rowCallback(_data, _tr, _idx)
{
var row = dataview.rowProvider.getPrototype("default");
$j("div", row).each(function () { $j(this).text("#" + _idx + " " + _data.caption) });
$j(_tr).append(row.children());
}
// The dynheight object is responsible for automatically resizing
// the gridContainer to its maximum extends
var dynheight = new et2_dynheight(window, $j("#container"), 150);
// The et2_dataview_gridContainer object is the outer grid container
// which is responsible for displaying the columns etc.
var dataview = new et2_dataview($j("#container"), egw(window));
// Load the column data
dataview.setColumns(columnData);
// Register the callback for displaying the range
dataview.grid.setInvalidateCallback(function (range) {
$j("#range").text("Showing elements " + (range.top + 1) + " - " + (range.bottom + 1) + " of " + dataview.grid.getTotalCount());
});
// Create the gridview controller
var controller = new et2_dataview_controller(dataview.grid,
new dataprovider(), rowCallback);
// Trigger the initial update
controller.update();
// Write something inside the column headers
for (var i = 0; i < columnData.length; i++)
{
$j(dataview.getHeaderContainerNode(i)).text(columnData[i].caption);
}
// Register a resize callback
$j(window).resize(function() {
dynheight.update(function(_w, _h) {
dataview.resize(_w, _h);
});
});
$j(window).resize();
</script>
</body>
</html>