mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-01-12 17:08:34 +01:00
New dataview version and integration into the nextmatch widget
This commit is contained in:
parent
23f221456f
commit
a9c1112dc1
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 --- */
|
422
etemplate/js/et2_dataview_controller.js
Normal file
422
etemplate/js/et2_dataview_controller.js
Normal 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);
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -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) {}
|
||||
|
||||
});
|
||||
|
||||
|
@ -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--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
@ -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
@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -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 = [];
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -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(/^( | | )+/,''))+"\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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
@ -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"))
|
||||
|
@ -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++)
|
||||
{
|
||||
|
135
etemplate/js/et2_extension_nextmatch_controller.js
Normal file
135
etemplate/js/et2_extension_nextmatch_controller.js
Normal 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
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
504
etemplate/js/et2_extension_nextmatch_rowProvider.js
Normal file
504
etemplate/js/et2_extension_nextmatch_rowProvider.js
Normal 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;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
@ -39,6 +39,7 @@
|
||||
.egwGridView_spacer {
|
||||
background-image: url(gfx/non_loaded_bg.png);
|
||||
background-position: top left;
|
||||
/* background-attachment: fixed;*/
|
||||
}
|
||||
|
||||
.egwGridView_outer {
|
||||
|
5910
etemplate/js/test/jquery.js
vendored
5910
etemplate/js/test/jquery.js
vendored
File diff suppressed because it is too large
Load Diff
174
etemplate/js/test/test_dataview.html
Normal file
174
etemplate/js/test/test_dataview.html
Normal 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>
|
Loading…
Reference in New Issue
Block a user