Dynamic loading of content tested and optimized, resolved bugs, implemented support for data trees in the view classes. The whole progess can be seen in the test/test_grid_view.html file.

This commit is contained in:
Andreas Stöckel 2011-03-10 20:58:35 +00:00
parent d7356a350f
commit 57aaf6d756
9 changed files with 1028 additions and 383 deletions

View File

@ -395,8 +395,6 @@ function egwActionObject(_id, _parent, _iface, _manager, _flags)
this.focusedChild = null;
this.setAOI(_iface);
this.iface.setStateChangeCallback(this._ifaceCallback, this);
this.iface.setReconnectActionsCallback(this._reconnectCallback, this);
}
/**
@ -418,6 +416,8 @@ egwActionObject.prototype.setAOI = function(_aoi)
// Replace the interface object
this.iface = _aoi;
this.iface.setStateChangeCallback(this._ifaceCallback, this);
this.iface.setReconnectActionsCallback(this._reconnectCallback, this);
}
/**
@ -764,7 +764,7 @@ egwActionObject.prototype._ifaceCallback = function(_newState, _changedBit, _shi
// and set their select state.
if (egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_BLOCK))
{
var focused = this.getRootObject().getFocusedObject();
var focused = this.getFocusedObject();
if (focused)
{
objs = this.traversePath(focused);
@ -915,7 +915,7 @@ egwActionObject.prototype.setAllSelected = function(_selected, _informParent)
*/
egwActionObject.prototype.updateSelectedChildren = function(_child, _selected)
{
var id = this.selectedChildren.indexOf(_child);
var id = this.selectedChildren.indexOf(_child); // TODO Replace by binary search, insert children sorted by index!
var wasEmpty = this.selectedChildren.length == 0;
// Add or remove the given child from the selectedChildren list

View File

@ -91,6 +91,7 @@ function egwGetShiftState(e)
var state = EGW_AO_SHIFT_STATE_NONE;
state = egwSetBit(state, EGW_AO_SHIFT_STATE_MULTI, e.ctrkKey || e.metaKey);
state = egwSetBit(state, EGW_AO_SHIFT_STATE_BLOCK, e.shiftKey);
return state;
}

View File

@ -64,6 +64,7 @@ function egwGridDataElement(_id, _parent, _columns, _readQueue, _objectManager)
this.canHaveChildren = false;
this.type = egwGridViewRow;
this.userData = null;
this.updatedGrid = null;
this.gridViewObj = null;
}
@ -132,7 +133,8 @@ egwGridDataElement.prototype.set_data = function(_value)
this.data[col_id] = {
"data": data,
"sortData": sortData
"sortData": sortData,
"queued": false
}
}
}
@ -168,8 +170,13 @@ egwGridDataElement.prototype.set_data = function(_value)
* "canHaveChildren": [true|false] // Specifies whether the row "open/close" button is displayed
* }
*/
egwGridDataElement.prototype.loadData = function(_data)
egwGridDataElement.prototype.loadData = function(_data, _doCallUpdate)
{
if (typeof _doCallUpdate == "undefined")
{
_doCallUpdate = false;
}
if (_data.constructor == Array)
{
var virgin = this.children.length == 0;
@ -197,6 +204,7 @@ egwGridDataElement.prototype.loadData = function(_data)
{
var count = typeof entry.count == "number" && entry.count >= 0 ? entry.count : 1;
var prefix = typeof entry.prefix == "string" ? entry.prefix : "elem_";
var canHaveChildren = typeof entry.canHaveChildren == "boolean" ? entry.canHaveChildren : false;
var index = last_element ? last_element.index + 1 : 0;
for (var j = 0; j < count; j++)
@ -204,6 +212,7 @@ egwGridDataElement.prototype.loadData = function(_data)
var id = prefix + (index + j);
element = this.insertElement(index + j, id);
element.type = type; // Type can only be set directly after creation
element.canHaveChildren = canHaveChildren;
}
}
else if (entryType == EGW_DATA_TYPE_ELEMENT)
@ -240,7 +249,12 @@ egwGridDataElement.prototype.loadData = function(_data)
this.loadData(_data.children);
}
this.gridViewObj.callGridViewObjectUpdate();
if (_doCallUpdate)
{
this.callBeginUpdate();
}
this.callGridViewObjectUpdate();
}
}
@ -328,7 +342,7 @@ egwGridDataElement.prototype.getElementById = function(_id, _depth)
{
for (var i = 0; i < this.children.length; i++)
{
var elem = this.children.getElementById(_id, _depth - 1);
var elem = this.children[i].getElementById(_id, _depth - 1);
if (elem)
{
@ -348,13 +362,13 @@ egwGridDataElement.prototype.getChildren = function(_callback, _context)
{
if (this.children.length > 0)
{
_callback.call(_context, this.children);
_callback.call(_context, this.children, true);
}
else if (this.canHaveChildren)
{
// If the children havn't been loaded yet, request them via queue call.
this.readQueue.queue(this, EGW_DATA_QUEUE_CHILDREN, function() {
_callback.call(_context, this.children);
this.readQueue.queueCall(this, EGW_DATA_QUEUE_CHILDREN, function() {
_callback.call(_context, this.children, false);
}, this);
}
}
@ -386,16 +400,21 @@ egwGridDataElement.prototype.hasColumn = function(_columnId, _returnData)
res = true;
}
}
if (!_returnData && typeof (this.data[_columnId]) != "undefined" && this.data[_columnId].queued)
{
res = true;
}
}
else
{
// Check whether the column data of this column has been read,
// if yes, return it.
if (typeof this.data[_columnIds] != "undefined")
if (typeof this.data[_columnId] != "undefined")
{
if (_returnData)
if (_returnData && typeof this.data[_columnId].data != "undefined")
{
res = this.data[_columnIds].data;
res = this.data[_columnId].data;
}
else
{
@ -456,7 +475,7 @@ egwGridDataElement.prototype.getData = function(_columnIds)
// in the readQueue
if (queryList.length > 0)
{
this.readQueue.queue(this, queryList);
this.readQueue.queueCall(this, queryList);
}
return result;
@ -467,11 +486,16 @@ egwGridDataElement.prototype.getData = function(_columnIds)
* Calls the row object update function - checks whether the row object implements
* this interface and whether it is set.
*/
egwGridDataElement.prototype.callGridViewObjectUpdate = function()
egwGridDataElement.prototype.callGridViewObjectUpdate = function(_immediate)
{
if (typeof _immediate == "undefined")
{
_immediate = false;
}
if (this.gridViewObj && typeof this.gridViewObj.doUpdateData == "function")
{
this.gridViewObj.doUpdateData();
this.gridViewObj.doUpdateData(_immediate);
}
}
@ -516,7 +540,63 @@ egwGridDataElement.prototype.setGridViewObj = function(_obj)
}
}
/**
* Returns the root element
*/
egwGridDataElement.prototype.getRootElement = function()
{
if (!this.parent)
{
return this;
}
else
{
return this.parent.getRootElement();
}
}
/**
* Returns the depth of this element in the document tree
*/
egwGridDataElement.prototype.getDepth = function()
{
return (this.parent) ? (this.parent.getDepth() + 1) : 0;
}
/**
* Calls the beginUpdate function of the grid associated to the grid view object
*/
egwGridDataElement.prototype.callBeginUpdate = function()
{
if (this.gridViewObj)
{
var root = this.getRootElement();
if (root.updatedGrid != this.gridViewObj.grid)
{
if (root.updatedGrid)
{
root.updatedGrid.endUpdate();
}
root.updatedGrid = this.gridViewObj.grid;
root.updatedGrid.beginUpdate();
}
}
}
/**
* Calls the end update function of the currently active updated grid
*/
egwGridDataElement.prototype.callEndUpdate = function()
{
var root = this.getRootElement();
if (root.updatedGrid)
{
root.updatedGrid.endUpdate();
root.updatedGrid = null;
}
}
/** - egwGridDataReadQueue -- **/
@ -558,27 +638,56 @@ egwGridDataQueue.prototype.setDataRoot = function(_dataRoot)
*/
egwGridDataQueue.prototype._queue = function(_obj)
{
this.timeoutId++;
// Push the queue object onto the queue
this.queue.push(_obj);
if (this.queue.length > EGW_DATA_QUEUE_MAX_ELEM_COUNT)
{
this.flushQueue();
this.flushQueue(false);
return false;
}
else
{
// Specify that the element data is queued
for (var i = 0; i < this.queueColumns.length; i++)
{
if (typeof _obj.elem.data[this.queueColumns[i]] == "undefined")
{
_obj.elem.data[this.queueColumns[i]] = {
"queued": true
}
}
}
// Set the flush queue timeout
var tid = this.timeoutId;
var self = this;
window.setTimeout(function() {
if (self.timeoutId == tid)
{
self.flushQueue(true);
}
}, EGW_DATA_QUEUE_FLUSH_TIMEOUT);
}
return true;
}
egwGridDataQueue.prototype.inQueue = function(_elem)
egwGridDataQueue.prototype._accumulateQueueColumns = function(_columns)
{
for (var i = 0; i < this.queue.length; i++)
if (this.dataRoot.columns.columns.length > this.queueColumns.length)
{
if (this.queue[i].elem == _elem)
// Merge the specified columns into the queueColumns variable
for (var i = 0; i < _columns.length; i++)
{
return true;
if (this.queueColumns.indexOf(_columns[i]) == -1)
{
this.queueColumns.push(_columns[i]);
}
}
}
return false;
}
/**
@ -594,7 +703,7 @@ egwGridDataQueue.prototype.inQueue = function(_elem)
* @param object _context is the context in which the callback function will
* be executed.
*/
egwGridDataQueue.prototype.queue = function(_elem, _columns, _callback, _context)
egwGridDataQueue.prototype.queueCall = function(_elem, _columns, _callback, _context)
{
if (typeof _callback == "undefined")
{
@ -610,7 +719,7 @@ egwGridDataQueue.prototype.queue = function(_elem, _columns, _callback, _context
if (!this._queue({
"elem": _elem,
"type": EGW_DATA_QUEUE_CHILDREN,
"proc": _callback,
"callback": _callback,
"context": _context
}))
{
@ -619,73 +728,125 @@ egwGridDataQueue.prototype.queue = function(_elem, _columns, _callback, _context
}
else
{
// Merge the specified columns into the queueColumns variable
for (var i = 0; i < _columns.length; i++)
{
if (this.queueColumns.indexOf(_columns[i]) == -1)
{
this.queueColumns.push(_columns[i]);
}
}
// Accumulate the queue columns ids
this._accumulateQueueColumns(_columns);
// Queue the element and search in the elements around the given one for
// elements whose data isn't loaded yet.
var done = !this._queue({
this._queue({
"elem": _elem,
"type": EGW_DATA_QUEUE_ELEM,
"proc": _callback,
"callback": _callback,
"context": _context
});
}
}
// Prefetch other elements around the given element
var parent = _elem.parent;
if (parent)
egwGridDataQueue.prototype._getQueuePlanes = function()
{
// Initialize the start prefetch index and the max prefetch count
var prefetch = EGW_DATA_QUEUE_PREFETCH_COUNT;
var idx = Math.floor(Math.max(0, _elem.index - prefetch / 2));
var planes = [];
var curPlane = null;
while (!done && prefetch > 0 && idx < parent.children.length)
for (var i = 0; i < this.queue.length; i++)
{
var elem = this.queue[i].elem;
// Don't prefetch the element itself
if (idx != _elem.idx)
if (!curPlane || elem.parent != curPlane.parent)
{
// Fetch the element with the current index from the children
// of the parent of the element.
var elem = parent.children[idx];
// Check whether this element has all data columns loaded and is
// not already in the queue
if (!this.inQueue(elem))
curPlane = null;
for (var j = 0; j < planes.length; j++)
{
var hasColumns = true;
for (var j = 0; j < this.queueColumns.length; j++)
if (planes[j].parent == elem.parent)
{
var res = elem.hasColumn(this.queueColumns[i], false);
if (!res)
{
hasColumns = false;
curPlane = planes[j];
break;
}
}
if (!hasColumns)
if (!curPlane)
{
done = !this._queue({
"elem": elem,
"type": EGW_DATA_QUEUE_ELEM,
"proc": null,
"context": null
});
prefetch--;
}
curPlane = {
"parent": elem.parent,
"cnt": 0,
"min": 0,
"max": 0,
"idx": 0,
"done": false
};
planes.push(curPlane);
}
}
idx++;
if (curPlane.cnt == 0 || elem.index < curPlane.min)
{
curPlane.min = elem.index;
}
if (curPlane.cnt == 0 || elem.index > curPlane.max)
{
curPlane.max = elem.index;
}
curPlane.cnt++;
}
return planes;
}
egwGridDataQueue.prototype.prefetch = function(_cnt)
{
var cnt = _cnt;
var planes = this._getQueuePlanes();
// Set the start indices
for (var i = 0; i < planes.length; i++)
{
planes[i].idx = Math.max(0, Math.ceil(planes[i].min - _cnt / (2 * planes.length)));
}
// Add as many elements as specified to the prefetched elements
var done = 0;
var plane = 0;
while (cnt > 0 && done < planes.length)
{
if (!planes[plane].done)
{
var idx = planes[plane].idx;
if (idx == planes[plane].parent.children.length)
{
planes[plane].done = true;
done++;
}
else
{
var hasData = true;
var elem = planes[plane].parent.children[idx];
for (var j = 0; j < this.queueColumns.length; j++)
{
if (!elem.hasColumn(this.queueColumns[i], false))
{
hasData = false;
break;
}
}
if (!hasData)
{
this._queue({
"elem": elem,
"type": EGW_DATA_QUEUE_ELEM,
"callback": null,
"context": null
});
cnt--;
}
planes[plane].idx++;
}
}
// Go to the next plane
plane = (plane + 1) % planes.length;
}
}
@ -693,14 +854,32 @@ egwGridDataQueue.prototype.queue = function(_elem, _columns, _callback, _context
* Empties the queue and calls the fetch callback which cares about retrieving
* the data from the server.
*/
egwGridDataQueue.prototype.flushQueue = function()
egwGridDataQueue.prototype.flushQueue = function(_doPrefetch)
{
var ids = [];
if (_doPrefetch)
{
// Get the count of elements which will be dynamically added to the list, "prefetched"
var prefetch_cnt = Math.min(EGW_DATA_QUEUE_PREFETCH_COUNT,
Math.max(0, EGW_DATA_QUEUE_MAX_ELEM_COUNT - this.queue.length));
this.prefetch(prefetch_cnt);
}
// Generate a list of element ids
for (var i = 0; i < this.queue.length; i++)
{
ids.push(this.queue[i].elem.id);
var id = this.queue[i].elem.id;
if (id == this.queue[i].elem.id)
{
if (this.queue[i].type == EGW_DATA_QUEUE_CHILDREN)
{
id = "[CHILDREN]" + id;
}
}
ids.push(id);
}
// Call the fetch callback and save a snapshot of the current queue
@ -711,16 +890,19 @@ egwGridDataQueue.prototype.flushQueue = function()
this.queue = [];
this.queueColumns = [];
this.timeoutId = 0;
}
egwGridDataQueue.prototype.dataCallback = function(_data, _queue)
{
var rootData = [];
try
{
// Iterate over the given data and check whether the data coresponds to one
// of the queue elements - if yes, call their (probably) specified callback.
// All elements for which no queue element can be found are added to the
// "rootData" list, which is then loaded by the "dataRoot" data object.
var i = 0;
for (var i = 0; i < _data.length; i++)
{
var hasTarget = false;
@ -734,8 +916,7 @@ egwGridDataQueue.prototype.dataCallback = function(_data, _queue)
{
if (_queue[j].elem.id == id)
{
// The element has been found, update its data
_queue[j].elem.loadData(_data[i]);
_queue[j].elem.loadData(_data[i], true);
// Call the queue object callback (if specified)
if (_queue[j].callback)
@ -744,7 +925,7 @@ egwGridDataQueue.prototype.dataCallback = function(_data, _queue)
}
// Delete this queue element
_queue.splice(i, 1);
_queue.splice(j, 1);
hasTarget = true;
break;
@ -754,10 +935,15 @@ egwGridDataQueue.prototype.dataCallback = function(_data, _queue)
if (!hasTarget)
{
rootData.push(_queue[i]);
rootData.push(_data[i]);
}
}
this.dataRoot.loadData(rootData);
this.dataRoot.loadData(rootData, true);
}
finally
{
this.dataRoot.callEndUpdate();
}
}

View File

@ -102,7 +102,7 @@ function egwGridViewOuter(_parentNode, _dataRoot)
this.scrollbarWidth = Math.max(10, this.getScrollbarWidth());
// Start value for the average row height
this.avgRowHeight = 23.0;
this.avgRowHeight = 19.0;
this.avgRowCnt = 1;
// Insert the base grid container into the DOM-Tree
@ -121,6 +121,19 @@ egwGridViewOuter.prototype.addHeightToAvg = function(_value)
this.avgRowHeight = this.avgRowHeight * (1 - frac) + _value * frac;
}
/**
* Removes the height from the average container height
*/
egwGridViewOuter.prototype.remHeightFromAvg = function(_value)
{
if (this.avgRowCnt > 1)
{
var sum = this.avgRowHeight * this.avgRowCnt - _value;
this.avgRowCnt--;
this.avgRowCount = sum / this.avgRowCnt;
}
}
/**
* Removes all containers from the base grid and replaces it with spacers again.
* As only partial data is displayed, this method is faster than updating every
@ -287,6 +300,8 @@ function egwGridViewContainer(_grid, _heightChangeProc)
this.assumedHeight = false;
this.index = 0;
this.viewArea = false;
this.containerClass = "";
this.heightInAvg = false;
this.doInsertIntoDOM = null;
this.doSetViewArea = null;
@ -323,7 +338,10 @@ egwGridViewContainer.prototype.setVisible = function(_visible, _force)
// While the element has been invisible, the viewarea might have changed,
// so check it now
if (this.visible)
{
this.checkViewArea();
}
// As the element is now (in)visible, its height has changed. Inform the
// parent about it.
@ -514,8 +532,10 @@ function egwGridViewGrid(_grid, _heightChangeProc, _scrollable, _outer)
container.scrollable = _scrollable;
container.scrollHeight = 100;
container.scrollEvents = 0;
container.inUpdate = 0;
container.didUpdate = false;
container.updateIndex = 0;
container.triggerID = 0;
container.setupContainer = egwGridViewGrid_setupContainer;
container.insertContainer = egwGridViewGrid_insertContainer;
container.removeContainer = egwGridViewGrid_removeContainer;
@ -526,8 +546,12 @@ function egwGridViewGrid(_grid, _heightChangeProc, _scrollable, _outer)
container.empty = egwGridViewGrid_empty;
container.getOuter = egwGridViewGrid_getOuter;
container.updateAssumedHeights = egwGridViewGrid_updateAssumedHeights;
container.beginUpdate = egwGridViewGrid_beginUpdate;
container.endUpdate = egwGridViewGrid_endUpdate;
container.triggerUpdateAssumedHeights = egwGridViewGrid_triggerUpdateAssumedHeights;
container.children = [];
container.outer = _outer;
container.containerClass = "grid";
// Overwrite the abstract container interface functions
container.invalidateHeightCache = egwGridViewGrid_invalidateHeightCache;
@ -538,6 +562,67 @@ function egwGridViewGrid(_grid, _heightChangeProc, _scrollable, _outer)
return container;
}
function egwGridViewGrid_beginUpdate()
{
if (this.inUpdate == 0)
{
this.didUpdate = false;
if (this.grid)
{
this.grid.beginUpdate();
}
}
this.inUpdate++;
}
function egwGridViewGrid_triggerUpdateAssumedHeights()
{
this.triggerID++;
var self = this;
var id = this.triggerID;
window.setTimeout(function() {
if (id = self.triggerID)
{
self.triggerID = 0;
self.updateAssumedHeights(20);
}
},
EGW_GRID_UPDATE_HEIGHTS_TIMEOUT);
}
function egwGridViewGrid_endUpdate(_recPrev)
{
if (typeof _recPrev == "undefined")
{
_recPrev = false;
}
if (this.inUpdate > 0)
{
this.inUpdate--;
if (this.inUpdate == 0 && this.grid)
{
this.grid.endUpdate();
}
if (this.inUpdate == 0 && this.didUpdate)
{
// If an update has been done, check whether any height assumptions have been
// done. This procedure is executed with some delay, as this gives the browser
// the time to insert the newly generated objects into the DOM-Tree and allows
// us to read their height at a very fast rate.
if (this.didUpdate && !_recPrev)
{
this.triggerUpdateAssumedHeights();
}
}
}
this.didUpdate = false;
}
function egwGridViewGrid_getOuter()
{
if (this.outer)
@ -571,6 +656,7 @@ function egwGridViewGrid_setupContainer()
*/
this.outerNode = $(document.createElement("td"));
this.outerNode.addClass("frame");
if (this.scrollable)
{
@ -623,7 +709,6 @@ function egwGridViewGrid_scrollCallback(_event)
var area = egwArea(this.scrollarea.scrollTop() - EGW_GRID_VIEW_EXT,
this.scrollHeight + EGW_GRID_VIEW_EXT * 2);
// Set view area sets the "didUpdate" variable to false
this.setViewArea(area);
this.scrollEvents = 0;
@ -638,7 +723,7 @@ function egwGridViewGrid_updateAssumedHeights(_maxCount)
try
{
this.inUpdate = true;
this.beginUpdate();
while (traversed < this.children.length && cnt > 0)
{
@ -658,7 +743,16 @@ function egwGridViewGrid_updateAssumedHeights(_maxCount)
var oldHeight = child.assumedHeight;
child.invalidateHeightCache();
var newHeight = child.getHeight();
if (child.containerClass == "row")
{
if (child.heightInAvg)
{
outer.remHeightFromAvg(oldHeight);
}
outer.addHeightToAvg(newHeight);
child.heightInAvg = true;
}
// Offset the position of all following elements by the delta.
var delta = newHeight - oldHeight;
@ -682,7 +776,7 @@ function egwGridViewGrid_updateAssumedHeights(_maxCount)
}
finally
{
this.inUpdate = false;
this.endUpdate(true);
}
var self = this;
@ -690,9 +784,7 @@ function egwGridViewGrid_updateAssumedHeights(_maxCount)
if (cnt == 0)
{
// If the maximum-update-count has been exhausted, retrigger this function
window.setTimeout(function() {
self.updateAssumedHeights(_maxCount);
}, EGW_GRID_UPDATE_HEIGHTS_TIMEOUT);
this.triggerUpdateAssumedHeights();
}
else
{
@ -705,6 +797,9 @@ function egwGridViewGrid_updateAssumedHeights(_maxCount)
}
function egwGridViewGrid_insertContainer(_after, _class, _params)
{
this.beginUpdate();
try
{
this.didUpdate = true;
@ -713,7 +808,7 @@ function egwGridViewGrid_insertContainer(_after, _class, _params)
var idx = this.children.length;
if (typeof _after == "number")
{
idx = Math.max(-1, Math.min(this.children.length, _after)) + 1;
idx = Math.min(this.children.length, Math.max(-1, _after)) + 1;
}
else if (typeof _after == "object" && _after)
{
@ -746,7 +841,7 @@ function egwGridViewGrid_insertContainer(_after, _class, _params)
// Offset the position of all following elements by the height of the container
// and move the index of those elements
var height = this.getOuter().avgRowHeight; //container.getHeight(); // This took a lot of time.
var height = this.getOuter().avgRowHeight;
container.assumedHeight = height;
for (var i = idx + 1; i < this.children.length; i++)
{
@ -756,11 +851,22 @@ function egwGridViewGrid_insertContainer(_after, _class, _params)
return container;
}
finally
{
this.endUpdate();
}
this.callHeightChangeProc();
}
function egwGridViewGrid_removeContainer(_container)
{
this.didUpdate = true;
try
{
this.beginUpdate();
var idx = _container.index;
// Offset the position of the folowing children back
@ -780,6 +886,13 @@ function egwGridViewGrid_removeContainer(_container)
this.children.splice(idx, 1);
}
finally
{
this.endUpdate();
}
this.callHeightChangeProc();
}
function egwGridViewGrid_empty(_newColumns)
{
@ -807,6 +920,7 @@ function egwGridViewGrid_invalidateHeightCache(_children)
}
this.height = false;
this.assumedHeight = false;
if (_children)
{
@ -837,17 +951,17 @@ function egwGridViewGrid_heightChangeHandler(_elem)
{
this.didUpdate = true;
// Get the height-change
// The old height of the element is now only an assumed height - the next
// time the "updateAssumedHeights" functions is triggered, this will be
// updated.
var oldHeight = _elem.assumedHeight !== false ? _elem.assumedHeight :
(_elem.height === false ? 0 : _elem.height);
(_elem.height === false ? this.getOuter().avgRowHeight : _elem.height);
_elem.invalidateHeightCache(false);
var newHeight = _elem.getHeight();
var offs = newHeight - oldHeight;
_elem.assumedHeight = oldHeight;
// Set the offset of all elements succeding the given element correctly
for (var i = _elem.index + 1; i < this.children.length; i++)
if (_elem.containerClass == "grid" && !this.inUpdate)
{
this.children[i].offsetPosition(offs);
this.triggerUpdateAssumedHeights();
}
// As a result of the height of one of the children, the height of this element
@ -864,8 +978,13 @@ function egwGridViewGrid_doInsertIntoDOM()
this.outerNode.attr("colspan", this.columns.length + (this.scrollable ? 1 : 0));
}
function egwGridViewGrid_doSetviewArea(_area)
function egwGridViewGrid_doSetviewArea(_area, _recPrev)
{
if (typeof _recPrev == "undefined")
{
_recPrev == false;
}
// Do a binary search for elements which are inside the given area
this.didUpdate = false;
var elem = null;
@ -927,7 +1046,9 @@ function egwGridViewGrid_doSetviewArea(_area)
}
}
this.inUpdate = true;
try
{
this.beginUpdate();
// Call the setViewArea function of visible child elements
// Imporant: The setViewArea function has to work on a copy of children,
@ -937,19 +1058,10 @@ function egwGridViewGrid_doSetviewArea(_area)
{
elems[i].setViewArea(_area, true);
}
this.inUpdate = false;
// If an update has been done, check whether any height assumptions have been
// done. This procedure is executed with some delay, as this gives the browser
// the time to insert the newly generated objects into the DOM-Tree and allows
// us to read their height at a very fast rate.
if (this.didUpdate)
}
finally
{
var self = this;
window.setTimeout(function() {
self.updateAssumedHeights(20);
}, EGW_GRID_UPDATE_HEIGHTS_TIMEOUT);
this.endUpdate(_recPrev);
}
}
@ -968,10 +1080,17 @@ function egwGridViewRow(_grid, _heightChangeProc, _item)
container.aoiSetup = egwGridViewRow_aoiSetup;
container.getAOI = egwGridViewRow_getAOI;
container.checkOdd = egwGridViewRow_checkOdd;
container._columnClick = egwGridViewRow__columnClick;
container.setOpen = egwGridViewRow_setOpen;
container.tdObjects = [];
container.containerClass = "row";
container.childGrid = null;
container.opened = false;
// Overwrite the inherited abstract functions
container.doInsertIntoDOM = egwGridViewRow_doInsertIntoDOM;
container.doSetViewArea = egwGridViewRow_doSetViewArea;
container.doUpdateData = egwGridViewRow_doUpdateData;
return container;
}
@ -1015,6 +1134,16 @@ function egwGridViewRow_getAOI()
return this.aoi;
}
function egwGridViewRow__columnClick(_shiftState, _column)
{
var state = this.aoi.getState();
var isSelected = egwBitIsSet(state, EGW_AO_STATE_SELECTED);
this.aoi.updateState(EGW_AO_STATE_SELECTED,
!egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI) || !isSelected,
_shiftState);
}
var
EGW_GRID_VIEW_ROW_BORDER = false;
@ -1032,26 +1161,25 @@ function egwGridViewRow_doInsertIntoDOM()
// Check whether this element is odd
this.checkOdd();
// Read the column data
/*var ids = [];
for (var i = 0; i < this.columns.length; i++)
{
ids.push(this.columns[i].id);
}
data = this.item.getData(ids);*/
for (var i = 0; i < this.columns.length; i++)
{
var col = this.columns[i];
var td = $(document.createElement("td"));
//if (typeof data[this.columns[i].id] != "undefined")
{
td.html("col" + i);
}
this.parentNode.append(td);
// Assign the click event to the column
td.mousedown(egwPreventSelect);
td.click({"item": this, "col": col.id}, function(e) {
this.onselectstart = null;
e.data.item._columnClick(egwGetShiftState(e), e.data.col);
});
if (i == 0)
{
td.addClass("first");
}
// Set the column width
if (EGW_GRID_VIEW_ROW_BORDER === false)
{
@ -1059,11 +1187,117 @@ function egwGridViewRow_doInsertIntoDOM()
}
td.css("width", col.drawnWidth - EGW_GRID_VIEW_ROW_BORDER);
// Store the column in the td object array
this.tdObjects.push({
"col": col,
"td": td
});
}
this.doUpdateData(true);
this.checkViewArea();
}
function egwGridViewRow_doUpdateData(_immediate)
{
var ids = [];
for (var i = 0; i < this.columns.length; i++)
{
ids.push(this.columns[i].id);
}
data = this.item.getData(ids);
for (var i = 0; i < this.tdObjects.length; i++)
{
var td = this.tdObjects[i].td;
var col = this.tdObjects[i].col;
if (typeof data[col.id] != "undefined")
{
td.empty();
if (col.type == EGW_COL_TYPE_NAME_ICON_FIXED)
{
// Insert the indentation spacer
var depth = this.item.getDepth() - 1;
if (depth > 0)
{
// Build the indentation object
var indentation = $(document.createElement("span"));
indentation.addClass("indentation");
indentation.css("width", (depth * 12) + "px");
td.append(indentation);
}
// Insert the open/close arrow
if (this.item.canHaveChildren)
{
var arrow = $(document.createElement("span"));
arrow.addClass("arrow");
arrow.addClass(this.item.opened ? "opened" : "closed");
arrow.click(this, function(e) {
$this = $(this);
if (!e.data.opened)
{
$this.addClass("opened");
$this.removeClass("closed");
}
else
{
$this.addClass("closed");
$this.removeClass("opened");
}
e.data.setOpen(!e.data.opened);
});
td.append(arrow);
}
// Insert the icon
if (data[col.id].iconUrl)
{
// Build the icon element
var icon = $(document.createElement("img"));
icon.attr("src", data[col.id].iconUrl);
icon.load(this, function(e) {
e.data.callHeightChangeProc();
});
icon.addClass("icon");
td.append(icon);
}
// Build the caption
if (data[col.id].caption)
{
var caption = $(document.createElement("span"));
caption.html(data[col.id].caption);
td.append(caption);
}
}
else
{
td.html(data[col.id]);
}
td.toggleClass("queued", false);
}
else
{
td.toggleClass("queued", true);
}
}
// Set the open state
this.setOpen(this.item.opened);
// If the call is not from inside the doInsertIntoDOM function, we have to
// inform the parent about a possible height change
if (!_immediate)
{
this.callHeightChangeProc();
}
}
function egwGridViewRow_checkOdd()
{
if (this.item && this.parentNode)
@ -1084,6 +1318,44 @@ function egwGridViewRow_doSetViewArea()
this.checkOdd();
}
function egwGridViewRow_setOpen(_open)
{
if (_open != this.opened)
{
if (_open)
{
if (!this.childGrid)
{
// Get the arrow and put it to "loading" state
var arrow = $(".arrow", this.parentNode);
arrow.removeClass("closed");
arrow.addClass("loading");
// Create the "child grid"
this.childGrid = this.grid.insertContainer(this.index, egwGridViewGrid,
false);
this.childGrid.setVisible(false);
var spacer = this.childGrid.insertContainer(-1, egwGridViewSpacer,
this.grid.getOuter().avgRowHeight);
this.item.getChildren(function(_children) {
arrow.removeClass("loading");
arrow.removeClass("closed");
arrow.addClass("opened");
spacer.setItemList(_children);
});
}
}
if (this.childGrid)
{
this.childGrid.setVisible(_open);
}
this.opened = _open;
this.item.opend = _open;
}
}
/** -- egwGridViewSpacer Class -- **/
@ -1138,6 +1410,8 @@ function egwGridViewSpacer_doInsertIntoDOM()
* creates those.
*/
function egwGridViewSpacer_doSetViewArea()
{
if (this.items.length > 0)
{
var avgHeight = this.grid.getOuter().avgRowHeight;
@ -1159,7 +1433,8 @@ function egwGridViewSpacer_doSetViewArea()
this.grid.insertContainer(idx - 1, it_mid[i].type, it_mid[i]);
}
// If top was greater than 0, insert a new spacer in front of the
// If top was greater than 0, insert a new spacer in front of the newly
// created elements.
if (it_top.length > 0)
{
var spacer = this.grid.insertContainer(idx - 1, egwGridViewSpacer, avgHeight);
@ -1177,5 +1452,6 @@ function egwGridViewSpacer_doSetViewArea()
this.grid.removeContainer(this);
}
}
}

View File

@ -14,6 +14,26 @@ body, td, th {
border-collapse: collapse;
}
.egwGridView_outer td.queued {
background-image: url(imgs/ajax-loader.gif);
background-position: center;
background-repeat: no-repeat;
height: 19px;
}
.egwGridView_grid tr.focused td {
background-image: url(imgs/focused_hatching.png);
background-repeat: repeat;
}
.egwGridView_grid tr.selected td {
background-color: #b7c3ff;
}
.egwGridView_grid tr.selected.odd td {
background-color: #9dadff;
}
.egwGridView_scrollarea {
width: 100%;
overflow: auto;
@ -37,16 +57,71 @@ body, td, th {
margin: 0;
}
.egwGridView_grid td, .egwGridView_grid tr {
.egwGridView_grid td {
border-right: 1px solid silver;
padding: 4px 3px 4px 4px;
padding: 2px 3px 2px 4px;
margin: 0;
}
.egwGridView_grid tr {
padding: 2px 3px 2px 4px;
margin: 0;
}
.egwGridView_grid tr.hidden {
display: none;
}
.egwGridView_grid tr.odd {
background-color: #F1F1F1;
}
.egwGridView_grid span.indentation {
display: inline-block;
}
.egwGridView_grid span {
vertical-align: middle;
}
.egwGridView_grid img.icon {
vertical-align: middle;
margin: 2px 5px 2px 2px;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
}
.egwGridView_grid span.arrow {
display: inline-block;
vertical-align: middle;
width: 8px;
height: 8px;
background-repeat: no-repeat;
margin-right: 2px;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
}
.egwGridView_grid span.arrow.opened {
cursor: pointer;
background-image: url(imgs/arrows.png);
background-position: -8px 0;
}
.egwGridView_grid span.arrow.closed {
cursor: pointer;
background-image: url(imgs/arrows.png);
background-position: 0 0;
}
.egwGridView_grid span.arrow.loading {
cursor: pointer;
background-image: url(imgs/ajax-loader.gif);
background-position: 0 0;
}
.egwGridView_outer thead th {
background-color: #E0E0E0;
font-weight: normal;
@ -66,32 +141,6 @@ body, td, th {
text-align: center;
}
/*----------------------------------------------------------------------------*/
.grid_outer {
border-spacing: 0;
border-collapse: collapse;
padding: 0;
margin: 0;
}
.grid_outer div.scrollarea {
overflow: auto;
width:100%;
}
.grid_outer td, .grid_outer tr {
padding: 0;
margin: 0;
}
.horizontal_spacer {
display: block;
background-image: url(imgs/non_loaded_bg.png);
background-position: top left;
}
.selectcols {
display: inline-block;
width: 10px;
@ -104,99 +153,9 @@ body, td, th {
background-repeat: no-repeat;
}
.grid {
border-spacing: 0;
border-collapse: collapse;
.frame {
padding: 0 !important;
border-right: 0 none silver !important;
}
.grid th.optcol {
width: 6px;
padding: 0;
text-align: center;
}
.grid tr.hidden {
display: none;
}
.grid td, .grid th {
border: 1px solid white;
}
.grid tr.focused td {
border: 1px dotted black;
}
.grid tr.selected td {
background-image: url(imgs/select_overlay.png);
background-position: center;
background-repeat: repeat-x;
}
.grid span.arrow {
display: inline-block;
vertical-align: middle;
width: 8px;
height: 8px;
background-repeat: no-repeat;
margin-right: 2px;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
}
.grid span.arrow.opened {
cursor: pointer;
background-image: url(imgs/arrows.png);
background-position: -8px 0;
}
.grid span.arrow.closed {
cursor: pointer;
background-image: url(imgs/arrows.png);
background-position: 0 0;
}
.grid tr.odd {
background-color: #F1F1F1;
}
.grid th {
background-color: #E0E0E0;
font-weight: normal;
padding: 5px;
text-align: left;
border-left: 1px solid silver;
border-top: 1px solid silver;
border-right: 1px solid gray;
border-bottom: 1px solid gray;
background-image: url(imgs/header_overlay.png);
background-position: center;
background-repeat: repeat-x;
}
.grid td {
padding: 0;
vertical-align: middle;
}
.grid th.front {
font-weight: bold;
}
.grid span.caption {
vertical-align: middle;
}
.grid span.indentation {
display: inline-block;
}
.grid img.icon {
vertical-align: middle;
margin: 2px 5px 2px 2px;
-moz-user-select: none;
-khtml-user-select: none;
user-select: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

View File

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32"
height="32"
id="svg2"
version="1.1"
inkscape:version="0.48.0 r9654"
sodipodi:docname="focused_hatching.svg"
inkscape:export-filename="/home/andreas/source/egroupware/trunk/egroupware/phpgwapi/js/egw_action/test/imgs/focused_hatching.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="7.9195959"
inkscape:cx="8.8135677"
inkscape:cy="16.76308"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:window-width="1600"
inkscape:window-height="823"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid2985"
empspacing="2"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1020.3622)">
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.18232045"
d="M -2,34 34,-2"
id="path2987"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.05801105"
d="M -2,30 30,-2"
id="path2989"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.18232045"
d="M 26,-2 -2,26"
id="path2991"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.05801105"
d="M 22,-2 -2,22"
id="path2993"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.18232045"
d="M 18,-2 -2,18"
id="path2995"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.05801105"
d="M -2,14 14,-2"
id="path2997"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.05801105"
d="M 10,-2 -2,10"
id="path2999"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.18232045"
d="M -2,6 6,-2"
id="path3001"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.05801105"
d="M 2,-2 -2,2"
id="path3003"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.05801105"
d="M 34,2 2,34"
id="path3005"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.18232045"
d="M 6,34 34,6"
id="path3007"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.05801105"
d="M 34,10 10,34"
id="path3009"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.18232045"
d="M 14,34 34,14"
id="path3011"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.05801105"
d="M 34,18 18,34"
id="path3013"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.18232045"
d="M 22,34 34,22"
id="path3015"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.05801105"
d="m 34,26 -8,8"
id="path3017"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
<path
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.18232045"
d="m 30,34 4,-4"
id="path3019"
inkscape:connector-curvature="0"
transform="translate(0,1020.3622)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -17,7 +17,8 @@
<link rel="stylesheet" href="grid.css"/>
</head>
<body>
<h1>Test for dynamically displaying and loading grid lines (0,1 Mio Entries)</h1>
<h1>Test for dynamically displaying and loading grid lines</h1>
<b>Simulates network trafic by using window.setTimeout(), 100ms network latency</b>
<div id="container"></div>
<script>
var grid = null;
@ -27,8 +28,9 @@
var columns =
[
{
"id": "name",
"caption": "Name",
"width": "33%",
"width": "20%",
"type": EGW_COL_TYPE_NAME_ICON_FIXED
},
{
@ -37,8 +39,7 @@
},
{
"id": "rights",
"caption": "UNIX Filerights",
"default": "---------"
"caption": "UNIX Filerights"
},
{
"id": "mime",
@ -46,29 +47,81 @@
},
{
"id": "atime",
"caption": "atime"
"caption": "atime",
"width": "15%"
},
{
"id": "ctime",
"caption": "ctime"
"caption": "ctime",
"width": "15%"
},
{
"id": "mtime",
"caption": "mtime"
"caption": "mtime",
"width": "15%"
},
{
"id": "owner",
"caption": "owner"
"caption": "owner",
"width": "10%"
},
{
"id": "group",
"caption": "group"
"caption": "group",
"width": "10%"
}
];
function fetchDataProc(_elems, _columns)
function fetchDataProc(_elems, _columns, _callback, _context)
{
console.log("Fetch Data Proc: ", _elems, _columns);
// Delay the result a bit to simulate real network traffic
window.setTimeout(function() {
var result = [];
for (var i = 0; i < _elems.length; i++)
{
// console.log(_elems[i]);
if (_elems[i].substr(0, "[CHILDREN]".length) == "[CHILDREN]")
{
var id = _elems[i].substr("[CHILDREN]".length);
var children = [
{
"entryType": EGW_DATA_TYPE_RANGE,
"prefix": id + "_child_",
"canHaveChildren": true,
"count": 20
}
];
result.push({
"id": id,
"children": children,
"opened": true
});
}
else
{
var data = {};
data["size"] = Math.floor(Math.random() * 1024) + "KiB";
data["rights"] = "rwxr-xr--";
data["mime"] = "image/png";
data["atime"] = (new Date).toUTCString();
data["mtime"] = (new Date).toUTCString();
data["ctime"] = (new Date).toUTCString();
data["owner"] = "as";
data["group"] = "stylitedevs";
result.push({
"id": _elems[i],
"data": data,
"caption": _elems[i],
"iconUrl": "imgs/folder.png"
});
}
}
_callback.call(_context, result);
}, 100);
}
$(document).ready(function() {
@ -82,11 +135,12 @@
{
"entryType": EGW_DATA_TYPE_RANGE,
"prefix": "root_elem_",
"count": 100000
"canHaveChildren": true,
"count": 10000
}
]
);
grid.resize(1500, 650);
grid.resize(1500, 500);
});
function check_positions()