Implemented grid column sorting

This commit is contained in:
Andreas Stöckel 2011-04-01 16:38:31 +00:00
parent 5ea2945538
commit a231863ed3
4 changed files with 340 additions and 33 deletions

View File

@ -203,9 +203,60 @@ egwGrid.prototype.sortColsClick = function(_columnIdx)
{ {
if (this.sortColsCallback) if (this.sortColsCallback)
{ {
this.sortColsCallback.call(this.context, this.columns.columns[_columnIdx].id); this.sortColsCallback.call(this.context, col.id);
} }
} }
else
{
var dir = EGW_COL_SORTMODE_ASC;
if (col.sortmode == EGW_COL_SORTMODE_ASC)
{
dir = EGW_COL_SORTMODE_DESC
}
this.sortData(col.id, dir);
}
}
egwGrid.prototype.sortData = function(_columnId, _dir)
{
var col = this.columns.getColumnById(_columnId);
if (col && col.sortable != EGW_COL_SORTABLE_NONE && col.sortable != EGW_COL_SORTABLE_EXTERNAL)
{
this.dataRoot.sortChildren(col.id, _dir, col.sortable, function() {
// Set the new sort direction
col.set_sortmode(_dir);
this.displaySortMode();
// Rebuild the inner grid
this.reload();
}, this);
}
}
egwGrid.prototype.displaySortMode = function()
{
// Update the column data of the grid
this.gridOuter.updateColumns(this.columns.getColumnData());
// Update the column header
for (var i = 0; i < this.columns.columns.length; i++)
{
this.gridOuter.updateColSortmode(i);
}
}
egwGrid.prototype.resetSort = function()
{
for (var i = 0; i < this.columns.columns.length; i++)
{
fileGrid.columns.columns[i].set_sortmode(EGW_COL_SORTMODE_NONE);
}
this.displaySortMode();
} }
/** /**

View File

@ -192,7 +192,7 @@ egwGridColumn.prototype.set_sortmode = function(_value)
{ {
if (_value != this.sortmode) if (_value != this.sortmode)
{ {
if (this.sortmode) if (this.sortmodeChangeCallback)
{ {
this.sortmodeChangeCallback.call(this.context, this); this.sortmodeChangeCallback.call(this.context, this);
} }

View File

@ -752,7 +752,232 @@ egwGridDataElement.prototype.getParentList = function(_lst)
return _lst; return _lst;
} }
/**
* Requires a certain column to have all data localy - if this isn't the case,
* the data is fetched from the server.
*
* @param string _colId specifies the column which should be loaded
* @param function _callback is the function which should be called once all data
* has been fetched.
* @param object _context is the context in which the callback should be executed
* @param object _loadIds is used internally to accumulate the object ids which
* should be loaded.
*/
egwGridDataElement.prototype.requireColumn = function(_colId, _callback, _context,
_loadElems)
{
var outerCall = false;
if (typeof _loadElems == "undefined")
{
_loadElems = {
"elems": []
}
outerCall = true;
}
if (!outerCall && !this.hasColumn(_colId, false))
{
_loadElems.elems.push(this);
}
for (var i = 0; i < this.children.length; i++)
{
this.children[i].requireColumn(_colId, null, null, _loadElems);
}
if (outerCall)
{
if (_loadElems.elems.length > 0)
{
this.readQueue.queueCall(_loadElems.elems, [_colId], function() {
_callback.call(_context);
}, null);
}
else
{
// If all elements had been loaded, postpone calling the callback function
window.setTimeout(function() {
_callback.call(_context);
}, 0);
}
}
}
/**
* Sorts the data element by the given column, the given sort direction and the
* given sort mode - if the tree doesn't have all the column data loaded which is
* needed for sorting, it first queries it from the server.
*
* @param string _colId is the id of the column
* @param int _dir is one of EGW_COL_SORTMODE_*
* @param int _mode is one of EGW_COL_SORTABLE_*
* @param function _callback is a callback function which is called once the
* sorting is done
* @param
* @param boolean _outerCall is used internally, do not specify it
*/
egwGridDataElement.prototype.sortChildren = function(_colId, _dir, _mode, _callback,
_context, _outerCall)
{
if (typeof _outerCall == "undefined")
{
_outerCall = true;
}
// If this is the outer call of the function, we first have to make sure
// that all data for the given column id is available
if (_outerCall)
{
this.requireColumn(_colId, function() {
// Call the actual sort part of this function by explicitly passing "false"
// to the _outerCall parameter
this.sortChildren(_colId, _dir, _mode, _callback, _context, false);
_callback.call(_context);
}, this);
}
else
{
// Select the sort function
var sortFunc = null;
switch (_mode) {
case EGW_COL_SORTABLE_ALPHABETIC:
sortFunc = egwGridData_sortAlphabetic;
break;
case EGW_COL_SORTABLE_NATURAL:
sortFunc = egwGridData_sortNatural;
break;
case EGW_COL_SORTABLE_NUMERICAL:
sortFunc = egwGridData_sortNumerical;
break;
}
var col = this.columns.getColumnById(_colId);
// Determine the mode multiplier which is used to sort the list in the
// given direction.
var dirMul = (_dir == EGW_COL_SORTMODE_ASC) ? 1 : -1;
this.children.sort(function (a, b) {
// Fetch the sortData or the regular data from the a and b element
// and pass it to the sort function
var aData = "";
var bData = "";
switch (col.type)
{
case EGW_COL_TYPE_DEFAULT:
aData = a.data[_colId].sortData === null ? a.data[_colId].data :
a.data[_colId].sortData;
bData = b.data[_colId].sortData === null ? b.data[_colId].data :
b.data[_colId].sortData;
break;
case EGW_COL_TYPE_NAME_ICON_FIXED:
aData = a.caption;
bData = b.caption;
break;
case EGW_COL_TYPE_CHECKBOX:
aData = a.actionObject.getSelected() ? 1 : 0;
bData = b.actionObject.getSelected() ? 1 : 0;
break;
}
return sortFunc(aData, bData) * dirMul;
});
// Sort all children
for (var i = 0; i < this.children.length; i++)
{
this.children[i].sortChildren(_colId, _dir, _mode, null, null, false);
}
// Sorting is done - call the callback function
if (_callback)
{
_callback.call(_context);
}
}
}
function egwGridData_sortAlphabetic(a, b)
{
return (a > b) ? 1 : -1;
}
function egwGridData_sortNumerical(a, b)
{
aa = parseFloat(a);
bb = parseFloat(b);
return (aa > bb) ? 1 : -1;
}
/**
* See http://my.opera.com/GreyWyvern/blog/show.dml/1671288
*/
function egwGridData_sortNatural(a, b)
{
function chunkify(t)
{
var tz = [], x = 0, y = -1, n = 0, i, j;
while (i = (j = t.charAt(x++)).charCodeAt(0))
{
var m = (i == 46 || (i >=48 && i <= 57));
if (m !== n)
{
tz[++y] = "";
n = m;
}
tz[y] += j;
}
return tz;
}
var aa = chunkify(a);
var bb = chunkify(b);
for (x = 0; aa[x] && bb[x]; x++)
{
if (aa[x] !== bb[x])
{
var c = Number(aa[x]), d = Number(bb[x]);
if (c == aa[x] && d == bb[x])
{
return c - d;
}
else
{
return (aa[x] > bb[x]) ? 1 : -1;
}
}
}
return aa.length - bb.length;
}
/**
* Returns the outermost parent of a set of elements - assume the following tree
* where the elements marked with "x" are passed as array to this function:
*
* ROOT
* |- CHILD 1
* |- SUB_CHILD 1
* |- SUB_CHILD 2 (x)
* |- CHILD 2
* |- SUB_CHILD 1
* |- SUB_SUB_CHILD 1 (x)
* The function should now return the "ROOT" elements as this is the outermost
* parent the elements have in common.
*
* TODO: If I think about this, the function actually doesn't work like I'd like
* it to behave...
*/
function egwGridData_getOutermostParent(_elements) function egwGridData_getOutermostParent(_elements)
{ {
var minElem = null; var minElem = null;
@ -772,6 +997,7 @@ function egwGridData_getOutermostParent(_elements)
return minElem ? (minElem.parent ? minElem.parent : minElem) : null; return minElem ? (minElem.parent ? minElem.parent : minElem) : null;
} }
/** - egwGridDataReadQueue -- **/ /** - egwGridDataReadQueue -- **/
// Some internally used constants // Some internally used constants
@ -809,14 +1035,14 @@ egwGridDataQueue.prototype.setDataRoot = function(_dataRoot)
* than the one specified in EGW_DATA_QUEUE_MAX_ELEM_COUNT. If this is the case, * than the one specified in EGW_DATA_QUEUE_MAX_ELEM_COUNT. If this is the case,
* the queue is flushed and false is returned, otherwise true. * the queue is flushed and false is returned, otherwise true.
*/ */
egwGridDataQueue.prototype._queue = function(_obj) egwGridDataQueue.prototype._queue = function(_obj, _last)
{ {
this.timeoutId++; this.timeoutId++;
// Push the queue object onto the queue // Push the queue object onto the queue
this.queue.push(_obj); this.queue.push(_obj);
if (this.queue.length > EGW_DATA_QUEUE_MAX_ELEM_COUNT) if (_last && this.queue.length > EGW_DATA_QUEUE_MAX_ELEM_COUNT)
{ {
this.flushQueue(false); this.flushQueue(false);
return false; return false;
@ -838,6 +1064,8 @@ egwGridDataQueue.prototype._queue = function(_obj)
} }
// Set the flush queue timeout // Set the flush queue timeout
if (_last)
{
var tid = this.timeoutId; var tid = this.timeoutId;
var self = this; var self = this;
window.setTimeout(function() { window.setTimeout(function() {
@ -847,6 +1075,7 @@ egwGridDataQueue.prototype._queue = function(_obj)
} }
}, EGW_DATA_QUEUE_FLUSH_TIMEOUT); }, EGW_DATA_QUEUE_FLUSH_TIMEOUT);
} }
}
return true; return true;
} }
@ -869,7 +1098,8 @@ egwGridDataQueue.prototype._accumulateQueueColumns = function(_columns)
/** /**
* Queues the given element in the fetch-data queue. * Queues the given element in the fetch-data queue.
* *
* @param object _elem is the element whose data will be fetched * @param object/array _elems is a single element or an array of elements -
* their data will be fetched
* @param array _columns is an array of column ids which should be fetched. Those * @param array _columns is an array of column ids which should be fetched. Those
* columns will be accumulated over the queue calls. _columns may also take * columns will be accumulated over the queue calls. _columns may also take
* the value EGW_DATA_QUEUE_CHILDREN in which case a request for the children * the value EGW_DATA_QUEUE_CHILDREN in which case a request for the children
@ -879,7 +1109,7 @@ egwGridDataQueue.prototype._accumulateQueueColumns = function(_columns)
* @param object _context is the context in which the callback function will * @param object _context is the context in which the callback function will
* be executed. * be executed.
*/ */
egwGridDataQueue.prototype.queueCall = function(_elem, _columns, _callback, _context) egwGridDataQueue.prototype.queueCall = function(_elems, _columns, _callback, _context)
{ {
if (typeof _callback == "undefined") if (typeof _callback == "undefined")
{ {
@ -890,14 +1120,22 @@ egwGridDataQueue.prototype.queueCall = function(_elem, _columns, _callback, _con
_context = null; _context = null;
} }
if (!(_elems instanceof Array))
{
_elems = [_elems];
}
for (var i = 0; i < _elems.length; i++)
{
var last = i == _elems.length - 1;
if (_columns === EGW_DATA_QUEUE_CHILDREN) if (_columns === EGW_DATA_QUEUE_CHILDREN)
{ {
this._queue({ this._queue({
"elem": _elem, "elem": _elems[i],
"type": EGW_DATA_QUEUE_CHILDREN, "type": EGW_DATA_QUEUE_CHILDREN,
"callback": _callback, "callback": last ? _callback : null,
"context": _context "context": _context
}); }, last);
} }
else else
{ {
@ -907,11 +1145,12 @@ egwGridDataQueue.prototype.queueCall = function(_elem, _columns, _callback, _con
// Queue the element and search in the elements around the given one for // Queue the element and search in the elements around the given one for
// elements whose data isn't loaded yet. // elements whose data isn't loaded yet.
this._queue({ this._queue({
"elem": _elem, "elem": _elems[i],
"type": EGW_DATA_QUEUE_ELEM, "type": EGW_DATA_QUEUE_ELEM,
"callback": _callback, "callback": last ? _callback : null,
"context": _context "context": _context
}); }, last);
}
} }
} }

View File

@ -808,4 +808,21 @@ td.lettersearch {
border-bottom: 0 none silver !important; border-bottom: 0 none silver !important;
} }
.egwGridView_outer span.sort {
display: inline-block;
width: 7px;
height: 7px;
background-repeat: no-repeat;
background-position: center;
margin: 2px;
vertical-align: middle;
}
.egwGridView_outer span.sort.asc {
background-image: url(../images/up.png);
}
.egwGridView_outer span.sort.desc {
background-image: url(../images/down.png);
}