dojo.provide("dojox.grid.enhanced.plugins.NestedSorting");
dojo.require("dojox.grid.enhanced.plugins._Mixin");
dojo.declare("dojox.grid.enhanced.plugins.NestedSorting", dojox.grid.enhanced.plugins._Mixin, {
// summary:
// Provides nested sorting feature
// example:
//
//sortAttrs: Array
// Sorting attributes, e.g.[{attr: 'col1', asc: 1|-1|0, cell: cell, cellNode: node}, {...}, ...]
sortAttrs: null,
//_unarySortCell: Object
// Cache for the current unary sort cell(the 1st column in sorting sequence)
// will be set as {cell: cell, cellNode: node}
_unarySortCell: null,
//_minColWidth: Integer
// Used for calculating min cell width, will be updated dynamically
_minColWidth: 63,//58
//_widthDelta: Integer
// Min width delta
_widthDelta: 23,//18
//_minColWidthUpdated: Boolean
// Flag to indicate whether the min col width has been updated
_minColWidthUpdated: false,
//_sortTipMap: Object
// Cache the tip on/off status for each cell, e.g. {cellIndex: true|false}
_sortTipMap: null,
//_overResizeWidth: Integer
// Overwrite the default over resize width,
// so that the resize cursor is more obvious when leveraged with sorting hover tips
_overResizeWidth: 3,
//storeItemSelected: String
// Attribute used in data store to mark which row(s) are selected accross sortings
storeItemSelected: 'storeItemSelectedAttr',
//exceptionalSelectedItems: Array
// Cache data store items with exceptional selection state.
// Used to retain selection states accross sortings. User may first select/deselect all rows
// and then deselect/select certain rows, these later changed rows have a different state
// with the global selection state, that is exceptional selection state
exceptionalSelectedItems: null,
//_a11yText: Object
// Characters for sorting arrows, used for a11y high contrast mode
_a11yText: {
'dojoxGridDescending' : '▾',
'dojoxGridAscending' : '▴',
'dojoxGridAscendingTip' : '۸',
'dojoxGridDescendingTip': '۷',
'dojoxGridUnsortedTip' : 'x' //'✖'
},
constructor: function(inGrid){
// summary:
// Mixin in all the properties and methods into DataGrid
this._unarySortCell = {}, this._sortTipMap = {};
this.sortAttrs = [], this.exceptionalSelectedItems = [];
inGrid.mixin(inGrid, this);
//init views
dojo.forEach(inGrid.views.views, this.initView, this);
this.connect(inGrid.views, 'addView', dojo.hitch(this, this.initView));
//init sorting
this.initSort(inGrid);
//keep selection after sorting if required
inGrid.keepSortSelection && this.connect(inGrid, '_onFetchComplete', dojo.hitch(inGrid, 'updateNewRowSelection'));
this.connect(inGrid.layout, 'setStructure', dojo.hitch(inGrid, 'clearSort'));
if(inGrid.indirectSelection && inGrid.rowSelectCell.toggleAllSelection){
this.connect(inGrid.rowSelectCell, 'toggleAllSelection', dojo.hitch(inGrid, 'allSelectionToggled'));
}
this.subscribe(inGrid.rowMovedTopic, dojo.hitch(inGrid, inGrid.clearSort));
this.subscribe(inGrid.rowSelectionChangedTopic, dojo.hitch(inGrid, inGrid._selectionChanged));
//init focus manager for nested sorting
inGrid.focus.destroy();
inGrid.focus = new dojox.grid.enhanced.plugins._NestedSortingFocusManager(inGrid);
//set a11y ARAI information
this.connect(inGrid.views, 'render', dojo.hitch(inGrid, 'initAriaInfo'));
},
initSort: function(inGrid){
// summary:
// initiate sorting
inGrid.getSortProps = inGrid._getDsSortAttrs;
//TODO - set default sorting order
},
initView: function(view){
// summary:
// Initiate views
//some init work for the header cells
this.connect(view, 'renderHeader', dojo.hitch(view, view.grid._initSelectCols));
this.connect(view.header, 'domousemove', dojo.hitch(view.grid, '_sychronizeResize'));
},
setSortIndex: function(inIndex, inAsc, e){
// summary:
// Sorting entry that overwrites parent(_Grid.js) when nested sorting is enabled
// Sort the grid on multiple columns in a nested sorting sequence
// inIndex: Integer
// Column index on which to sort.
// inAsc: Integer
// 1: sort the target column in ascending order
// -1: sort the target column in descending order
// 0: revert the target column back to unsorted state
// e: Event
// Decorated event object which contains reference to grid, target cell etc.
if(!this.nestedSorting){
this.inherited(arguments);
}else{
//cache last selection status
this.keepSortSelection && this.retainLastRowSelection();
//var desc = c["sortDesc"]; //TODO when is c["sortDesc"] used?
this.inSorting = true;
this._toggleProgressTip(true, e); //turn on progress cursor
this._updateSortAttrs(e, inAsc);
this.focus.addSortFocus(e);
if(this.canSort()){
this.sort();
this.edit.info = {};
this.update();
}
this._toggleProgressTip(false, e);//turn off progress cursor
this.inSorting = false;
}
},
_updateSortAttrs: function(e, inAsc){
// summary:
// Update the sorting sequence
// e: Event
// Decorated event object which contains reference to grid, target cell etc.
// inAsc: Integer
// 1: sort the target column in ascending order
// -1: sort the target column in descending order
// 0: revert the target column back to unsorted state
var existing = false;
var unarySort = !!e.unarySortChoice;//true - unary sort | false - nested sort
if(unarySort){
//unary sort
var cellSortInfo = this.getCellSortInfo(e.cell);
var asc = (this.sortAttrs.length > 0 && cellSortInfo["sortPos"] != 1) ? cellSortInfo["unarySortAsc"]
: this._getNewSortState(cellSortInfo["unarySortAsc"]);
if(asc && asc != 0){
//update unary sorting info
this.sortAttrs = [{attr: e.cell.field, asc: asc, cell: e.cell, cellNode: e.cellNode}];
this._unarySortCell = {cell: e.cell, node: e.cellNode};
}else{
//asc = 0, so empty sorting sequence
this.sortAttrs = [];
this._unarySortCell = null;
}
}else{
//nested sort
this.setCellSortInfo(e, inAsc);
}
},
getCellSortInfo: function(cell){
// summay:
// Get sorting info of the cell
// cell: Cell
// Target header cell
// return:
// Sort info e.g. {unarySortAsc: 1|-1|0, nestedSortAsc: 1|-1|0, sortPos:1|2...}
if(!cell){return false;}
var cellSortInfo = null;
var _sortAttrs = this.sortAttrs;
dojo.forEach(_sortAttrs, function(attr, index, attrs){
if(attr && attr["attr"] == cell.field && attr["cell"] == cell){
cellSortInfo = {unarySortAsc: attrs[0] ? attrs[0]["asc"] : undefined,
nestedSortAsc:attr["asc"],
sortPos: index + 1
}
}
});
return cellSortInfo ? cellSortInfo : {unarySortAsc: _sortAttrs && _sortAttrs[0] ? _sortAttrs[0]["asc"] : undefined,
nestedSortAsc: undefined, sortPos:-1};
},
setCellSortInfo: function(e, inAsc){
// summary:
// Update nested sorting sequence with the new state of target cell
// e: Event
// Decorated event object which contains reference to grid, target cell etc.
// inAsc: Integer
// 1: sort the target column in ascending order
// -1: sort the target column in descending order
// 0: revert the target column back to unsorted state
var cell = e.cell;
var existing = false;
var delAttrs = [];//cells to be removed from sorting sequence
var _sortAttrs = this.sortAttrs;
dojo.forEach(_sortAttrs, dojo.hitch(this, function(attr, index){
if(attr && attr["attr"] == cell.field){
var si = inAsc ? inAsc : this._getNewSortState(attr["asc"]);
if(si == 1 || si == -1){
attr["asc"] = si;
}else if(si == 0){
delAttrs.push(index);
}else{
throw new Exception('Illegal nested sorting status - ' + si);
}
existing = true;
}
}));
var minus = 0;
//remove unsorted columns from sorting sequence
dojo.forEach(delAttrs, function(delIndex){
_sortAttrs.splice((delIndex - minus++), 1);
});
//add new column to sorting sequence
if(!existing){
var si = inAsc ? inAsc : 1;
if(si != 0){
_sortAttrs.push({
attr: cell.field,
asc: si,
cell: e.cell,
cellNode: e.cellNode
});
}
}
//cache unary sort cell
if(delAttrs.length > 0){
this._unarySortCell = {cell: _sortAttrs[0]['cell'], node: _sortAttrs[0]['cellNode']};
}
},
_getDsSortAttrs: function(){
// summary:
// Get the sorting attributes for Data Store
// return: Object
// Sorting attributes used by Data Store e.g. {attribute: 'xxx', descending: true|false}
var dsSortAttrs = [];
var si = null;
dojo.forEach(this.sortAttrs, function(attr){
if(attr && (attr["asc"] == 1 || attr["asc"] == -1)){
dsSortAttrs.push({attribute:attr["attr"], descending: (attr["asc"] == -1)});
}
});
return dsSortAttrs.length > 0 ? dsSortAttrs : null;
},
_getNewSortState: function(si/*int 1|-1|0*/){
//summay:
// Get the next sort sate
//si: Integer
// Current sorting state
//return: Integer
// Next new sorting state
return si ? (si == 1 ? -1 : (si == -1 ? 0 : 1)) : 1;
},
sortStateInt2Str: function(si){
//summay:
// Map sort sate from integer to string
//si: Integer
// Sorting state integer
//return: String
// Sort info string
if(!si){
return 'Unsorted';
}
switch (si){
case 1:
return 'Ascending';//'SortUp';
case -1:
return 'Descending';//'SortDown';
default:
return 'Unsorted';
}
},
clearSort: function(){
//summay:
// Clear the sorting sequence
dojo.query("[id*='Sort']", this.viewsHeaderNode).forEach(function(region){
dojo.addClass(region, 'dojoxGridUnsorted');
});
this._unarySortCell = {}, this._sortTipMap = {};
this.sortAttrs = [], this.exceptionalSelectedItems = [];
this.focus.clearHeaderFocus();
},
_getNestedSortHeaderContent: function(inCell){
//summay:
// Callback to render the innHTML for a header cell,
// see _View.renderHeader() and _View.header.generateHtml()
//inCell: Cell
// Header cell for rendering
//return: String
// InnerHTML for the header cell
var n = inCell.name || inCell.grid.getCellName(inCell);
if(inCell.grid.pluginMgr.isFixedCell(inCell)){
return [
'
',
n,
'
'
].join('');
}
//e.g.{unarySortAsc: 1|-1|0, nestedSortAsc: 1|-1|0, sortPos:1|2...}
var cellSortInfo = inCell.grid.getCellSortInfo(inCell);
var _sortAttrs = inCell.grid.sortAttrs;
var inNestedSort = (_sortAttrs && _sortAttrs.length > 1 && cellSortInfo["sortPos"] >= 1);
var inUnarySort = (_sortAttrs && _sortAttrs.length == 1 && cellSortInfo["sortPos"] == 1);
var _grid = inCell.grid;
var ret = ['
',
'
',
//[0] => select-sort Separator
'',
'',
//[1] => nested sort position
'' +
(inNestedSort ? cellSortInfo["sortPos"] : '') + '',
//[2] => nested sort choice
'',
_grid._a11yText['dojoxGrid' + _grid.sortStateInt2Str(cellSortInfo["nestedSortAsc"])] || '.',
'',
'',
//[3] => sortSeparator mark
'',
//[4] => unary sort choice position
//only shown when this cell is the only one in sort sequence
'',
_grid._a11yText['dojoxGrid' + _grid.sortStateInt2Str(cellSortInfo["unarySortAsc"])] || '.',
'',
'
',
//[5] => select region
'',
'
'
];
return ret.join('');