webui-aria2/js/libs/dojox/grid/enhanced/plugins/Selector.js.uncompressed.js
2012-05-01 19:52:07 +08:00

1477 lines
46 KiB
JavaScript

//>>built
define("dojox/grid/enhanced/plugins/Selector", [
"dojo/_base/kernel",
"dojo/_base/lang",
"dojo/_base/declare",
"dojo/_base/array",
"dojo/_base/event",
"dojo/keys",
"dojo/query",
"dojo/_base/html",
"dojo/_base/window",
"dijit/focus",
"../../_RowSelector",
"../_Plugin",
"../../EnhancedGrid",
"../../cells/_base",
"./AutoScroll"
], function(dojo, lang, declare, array, event, keys, query, html, win, dijitFocus, _RowSelector, _Plugin, EnhancedGrid){
/*=====
dojo.declare("__SelectItem", null,{
// summary:
// An abstract representation of an item.
});
dojo.declare("__SelectCellItem", __SelectItem,{
// summary:
// An abstract representation of a cell.
// row: Integer
// Row index of this cell
row: 0,
// col: Integer
// Column index of this cell
col: 0
});
dojo.declare("__SelectRowItem", __SelectItem,{
// summary:
// An abstract representation of a row.
// row: Integer
// Row index of this row
row: 0,
// except: Integer[]
// An array of column indexes of all the unselected cells in this row.
except: []
});
dojo.declare("__SelectColItem", __SelectItem,{
// summary:
// An abstract representation of a column.
// col: Integer
// Column index of this column
col: 0,
// except: Integer[]
// An array of row indexes of all the unselected cells in this column.
except: []
});
=====*/
var DISABLED = 0, SINGLE = 1, MULTI = 2,
_theOther = { col: "row", row: "col" },
_inRange = function(type, value, start, end, halfClose){
if(type !== "cell"){
value = value[type];
start = start[type];
end = end[type];
if(typeof value !== "number" || typeof start !== "number" || typeof end !== "number"){
return false;
}
return halfClose ? ((value >= start && value < end) || (value > end && value <= start))
: ((value >= start && value <= end) || (value >= end && value <= start));
}else{
return _inRange("col", value, start, end, halfClose) && _inRange("row", value, start, end, halfClose);
}
},
_isEqual = function(type, v1, v2){
try{
if(v1 && v2){
switch(type){
case "col": case "row":
return v1[type] == v2[type] && typeof v1[type] == "number" &&
!(_theOther[type] in v1) && !(_theOther[type] in v2);
case "cell":
return v1.col == v2.col && v1.row == v2.row && typeof v1.col == "number" && typeof v1.row == "number";
}
}
}catch(e){}
return false;
},
_stopEvent = function(evt){
try{
if(evt && evt.preventDefault){
event.stop(evt);
}
}catch(e){}
},
_createItem = function(type, rowIndex, colIndex){
switch(type){
case "col":
return {
"col": typeof colIndex == "undefined" ? rowIndex : colIndex,
"except": []
};
case "row":
return {
"row": rowIndex,
"except": []
};
case "cell":
return {
"row": rowIndex,
"col": colIndex
};
}
return null;
};
var Selector = declare("dojox.grid.enhanced.plugins.Selector", _Plugin, {
// summary:
// Provides standard extended selection for grid.
// Supports mouse/keyboard selection, multi-selection, and de-selection.
// Acceptable plugin parameters:
// The whole plugin parameter object is a config object passed to the setupConfig function.
//
// Acceptable cell parameters defined in layout:
// 1. notselectable: boolean
// Whether this column is (and all the cells in it are) selectable.
// name: String
// plugin name
name: "selector",
// noClear: Boolean
// Not to clear rows selected by IndirectSelection.
/*
// _config: null,
// _enabled: true,
// _selecting: {
// row: false,
// col: false,
// cell: false
// },
// _selected: {
// row: [],
// col: [],
// cell: []
// },
// _startPoint: {},
// _currentPoint: {},
// _lastAnchorPoint: {},
// _lastEndPoint: {},
// _lastSelectedAnchorPoint: {},
// _lastSelectedEndPoint: {},
// _keyboardSelect: {
// row: 0,
// col: 0,
// cell: 0
// },
// _curType: null,
// _lastType: null,
// _usingKeyboard: false,
// _toSelect: true,
*/
constructor: function(grid, args){
this.grid = grid;
this._config = {
row: MULTI,
col: MULTI,
cell: MULTI
};
this.noClear = args && args.noClear;
this.setupConfig(args);
if(grid.selectionMode === "single"){
this._config.row = SINGLE;
}
this._enabled = true;
this._selecting = {};
this._selected = {
"col": [],
"row": [],
"cell": []
};
this._startPoint = {};
this._currentPoint = {};
this._lastAnchorPoint = {};
this._lastEndPoint = {};
this._lastSelectedAnchorPoint = {};
this._lastSelectedEndPoint = {};
this._keyboardSelect = {};
this._lastType = null;
this._selectedRowModified = {};
this._hacks();
this._initEvents();
this._initAreas();
this._mixinGrid();
},
destroy: function(){
this.inherited(arguments);
},
//------------public--------------------
setupConfig: function(config){
// summary:
// Set selection mode for row/col/cell.
// config: Object
// An object with the following structure (all properties are optional):
// {
// //Default is "multi", all other values are same as "multi".
// row: false|"disabled"|"single",
// col: false|"disabled"|"single",
// cell: false|"disabled"|"single"
// }
if(!config || !lang.isObject(config)){
return;
}
var types = ["row", "col", "cell"];
for(var type in config){
if(array.indexOf(types, type) >= 0){
if(!config[type] || config[type] == "disabled"){
this._config[type] = DISABLED;
}else if(config[type] == "single"){
this._config[type] = SINGLE;
}else{
this._config[type] = MULTI;
}
}
}
//Have to set mode to default grid selection.
var mode = ["none","single","extended"][this._config.row];
this.grid.selection.setMode(mode);
},
isSelected: function(type, rowIndex, colIndex){
// summary:
// Check whether a location (a cell, a column or a row) is selected.
// tag:
// public
// type: String
// "row" or "col" or "cell"
// rowIndex: Integer
// If type is "row" or "cell", this is the row index.
// If type if "col", this is the column index.
// colIndex: Integer?
// Only valid when type is "cell"
// return: Boolean
// true if selected, false if not. If cell is covered by a selected column, it's selected.
return this._isSelected(type, _createItem(type, rowIndex, colIndex));
},
toggleSelect: function(type, rowIndex, colIndex){
this._startSelect(type, _createItem(type, rowIndex, colIndex), this._config[type] === MULTI, false, false, !this.isSelected(type, rowIndex, colIndex));
this._endSelect(type);
},
select: function(type, rowIndex, colIndex){
// summary:
// Select a location (a cell, a column or a row).
// tag:
// public
// type: String
// "row" or "col" or "cell"
// rowIndex: Integer
// If type is "row" or "cell", this is the row index.
// If type if "col", this is the column index.
// colIndex: Integer?
// Only valid when type is "cell"
if(!this.isSelected(type, rowIndex, colIndex)){
this.toggleSelect(type, rowIndex, colIndex);
}
},
deselect: function(type, rowIndex, colIndex){
if(this.isSelected(type, rowIndex, colIndex)){
this.toggleSelect(type, rowIndex, colIndex);
}
},
selectRange: function(type, start, end, toSelect){
// summary:
// Select a continuous range (a block of cells, a set of continuous columns or rows)
// tag:
// public
// type: String
// "row" or "col" or "cell"
// start: Integer | Object
// If type is "row" or "col", this is the index of the starting row or column.
// If type if "cell", this is the left-top cell of the range.
// end: Integer | Object
// If type is "row" or "col", this is the index of the ending row or column.
// If type if "cell", this is the right-bottom cell of the range.
this.grid._selectingRange = true;
var startPoint = type == "cell" ? _createItem(type, start.row, start.col) : _createItem(type, start),
endPoint = type == "cell" ? _createItem(type, end.row, end.col) : _createItem(type, end);
this._startSelect(type, startPoint, false, false, false, toSelect);
this._highlight(type, endPoint, toSelect === undefined ? true : toSelect);
this._endSelect(type);
this.grid._selectingRange = false;
},
clear: function(type){
// summary:
// Clear all selections.
// tag:
// public
// type: String?
// "row" or "col" or "cell". If omitted, clear all.
this._clearSelection(type || "all");
},
isSelecting: function(type){
// summary:
// Check whether the user is currently selecting something.
// tag:
// public
// type: String
// "row" or "col" or "cell"
// return: Boolean
// true if is selection, false otherwise.
if(typeof type == "undefined"){
return this._selecting.col || this._selecting.row || this._selecting.cell;
}
return this._selecting[type];
},
selectEnabled: function(toEnable){
// summary:
// Turn on/off this selection functionality if *toEnable* is provided.
// Check whether this selection functionality is enabled if nothing is passed in.
// tag:
// public
// toEnable: Boolean?
// To enable or not. Optional.
// return: Boolean | undefined
// Enabled or not.
if(typeof toEnable != "undefined" && !this.isSelecting()){
this._enabled = !!toEnable;
}
return this._enabled;
},
getSelected: function(type, includeExceptions){
// summary:
// Get an array of selected locations.
// tag:
// public
// type: String
// "row" or "col" or "cell"
// includeExceptions: Boolean
// Only meaningful for rows/columns. If true, all selected rows/cols, even they are partly selected, are all returned.
// return: __SelectItem[]
switch(type){
case "cell":
return array.map(this._selected[type], function(item){ return item; });
case "col": case "row":
return array.map(includeExceptions ? this._selected[type]
: array.filter(this._selected[type], function(item){
return item.except.length === 0;
}), function(item){
return includeExceptions ? item : item[type];
});
}
return [];
},
getSelectedCount: function(type, includeExceptions){
// summary:
// Get the number of selected items.
// tag:
// public
// type: String
// "row" or "col" or "cell"
// includeExceptions: Boolean
// Only meaningful for rows/columns. If true, all selected rows/cols, even they are partly selected, are all returned.
// return: Integer
// The number of selected items.
switch(type){
case "cell":
return this._selected[type].length;
case "col": case "row":
return (includeExceptions ? this._selected[type]
: array.filter(this._selected[type], function(item){
return item.except.length === 0;
})).length;
}
return 0;
},
getSelectedType: function(){
// summary:
// Get the type of selected items.
// tag:
// public
// return: String
// "row" or "col" or "cell", or any mix of these (separator is | ).
var s = this._selected;
return ["", "cell", "row", "row|cell",
"col", "col|cell", "col|row", "col|row|cell"
][(!!s.cell.length) | (!!s.row.length << 1) | (!!s.col.length << 2)];
},
getLastSelectedRange: function(type){
// summary:
// Get last selected range of the given type.
// tag:
// public
// return: Object
// {start: __SelectItem, end: __SelectItem}
// return null if nothing is selected.
return this._lastAnchorPoint[type] ? {
"start": this._lastAnchorPoint[type],
"end": this._lastEndPoint[type]
} : null;
},
//--------------------------private----------------------------
_hacks: function(){
// summary:
// Complete the event system of grid, hack some grid functions to prevent default behavior.
var g = this.grid;
var doContentMouseUp = function(e){
if(e.cellNode){
g.onMouseUp(e);
}
g.onMouseUpRow(e);
};
var mouseUp = lang.hitch(g, "onMouseUp");
var mouseDown = lang.hitch(g, "onMouseDown");
var doRowSelectorFocus = function(e){
e.cellNode.style.border = "solid 1px";
};
array.forEach(g.views.views, function(view){
view.content.domouseup = doContentMouseUp;
view.header.domouseup = mouseUp;
if(view.declaredClass == "dojox.grid._RowSelector"){
view.domousedown = mouseDown;
view.domouseup = mouseUp;
view.dofocus = doRowSelectorFocus;
}
});
//Disable default selection.
g.selection.clickSelect = function(){};
this._oldDeselectAll = g.selection.deselectAll;
var _this = this;
g.selection.selectRange = function(from, to){
_this.selectRange("row", from, to, true);
if(g.selection.preserver){
g.selection.preserver._updateMapping(true, true, false, from, to);
}
g.selection.onChanged();
};
g.selection.deselectRange = function(from, to){
_this.selectRange("row", from, to, false);
if(g.selection.preserver){
g.selection.preserver._updateMapping(true, false, false, from, to);
}
g.selection.onChanged();
};
g.selection.deselectAll = function(){
g._selectingRange = true;
_this._oldDeselectAll.apply(g.selection, arguments);
_this._clearSelection("all");
g._selectingRange = false;
if(g.selection.preserver){
g.selection.preserver._updateMapping(true, false, true);
}
g.selection.onChanged();
};
var rowSelector = g.views.views[0];
//The default function re-write the whole className, so can not insert any other classes.
if(rowSelector instanceof _RowSelector){
rowSelector.doStyleRowNode = function(inRowIndex, inRowNode){
html.removeClass(inRowNode, "dojoxGridRow");
html.addClass(inRowNode, "dojoxGridRowbar");
html.addClass(inRowNode, "dojoxGridNonNormalizedCell");
html.toggleClass(inRowNode, "dojoxGridRowbarOver", g.rows.isOver(inRowIndex));
html.toggleClass(inRowNode, "dojoxGridRowbarSelected", !!g.selection.isSelected(inRowIndex));
};
}
this.connect(g, "updateRow", function(rowIndex){
array.forEach(g.layout.cells, function(cell){
if(this.isSelected("cell", rowIndex, cell.index)){
this._highlightNode(cell.getNode(rowIndex), true);
}
}, this);
});
},
_mixinGrid: function(){
// summary:
// Expose events to grid.
var g = this.grid;
g.setupSelectorConfig = lang.hitch(this, this.setupConfig);
g.onStartSelect = function(){};
g.onEndSelect = function(){};
g.onStartDeselect = function(){};
g.onEndDeselect = function(){};
g.onSelectCleared = function(){};
},
_initEvents: function(){
// summary:
// Connect events, create event handlers.
var g = this.grid,
_this = this,
dp = lang.partial,
starter = function(type, e){
if(type === "row"){
_this._isUsingRowSelector = true;
}
//only left mouse button can select.
if(_this.selectEnabled() && _this._config[type] && e.button != 2){
if(_this._keyboardSelect.col || _this._keyboardSelect.row || _this._keyboardSelect.cell){
_this._endSelect("all");
_this._keyboardSelect.col = _this._keyboardSelect.row = _this._keyboardSelect.cell = 0;
}
if(_this._usingKeyboard){
_this._usingKeyboard = false;
}
var target = _createItem(type, e.rowIndex, e.cell && e.cell.index);
_this._startSelect(type, target, e.ctrlKey, e.shiftKey);
}
},
ender = lang.hitch(this, "_endSelect");
this.connect(g, "onHeaderCellMouseDown", dp(starter, "col"));
this.connect(g, "onHeaderCellMouseUp", dp(ender, "col"));
this.connect(g, "onRowSelectorMouseDown", dp(starter, "row"));
this.connect(g, "onRowSelectorMouseUp", dp(ender, "row"));
this.connect(g, "onCellMouseDown", function(e){
if(e.cell && e.cell.isRowSelector){ return; }
if(g.singleClickEdit){
_this._singleClickEdit = true;
g.singleClickEdit = false;
}
starter(_this._config["cell"] == DISABLED ? "row" : "cell", e);
});
this.connect(g, "onCellMouseUp", function(e){
if(_this._singleClickEdit){
delete _this._singleClickEdit;
g.singleClickEdit = true;
}
ender("all", e);
});
this.connect(g, "onCellMouseOver", function(e){
if(_this._curType != "row" && _this._selecting[_this._curType] && _this._config[_this._curType] == MULTI){
_this._highlight("col", _createItem("col", e.cell.index), _this._toSelect);
if(!_this._keyboardSelect.cell){
_this._highlight("cell", _createItem("cell", e.rowIndex, e.cell.index), _this._toSelect);
}
}
});
this.connect(g, "onHeaderCellMouseOver", function(e){
if(_this._selecting.col && _this._config.col == MULTI){
_this._highlight("col", _createItem("col", e.cell.index), _this._toSelect);
}
});
this.connect(g, "onRowMouseOver", function(e){
if(_this._selecting.row && _this._config.row == MULTI){
_this._highlight("row", _createItem("row", e.rowIndex), _this._toSelect);
}
});
//When row order has changed in a unpredictable way (sorted or filtered), map the new rowindex.
this.connect(g, "onSelectedById", "_onSelectedById");
//When the grid refreshes, all those selected should still appear selected.
this.connect(g, "_onFetchComplete", function(){
//console.debug("refresh after buildPage:", g._notRefreshSelection);
if(!g._notRefreshSelection){
this._refreshSelected(true);
}
});
//Small scroll might not refresh the grid.
this.connect(g.scroller, "buildPage", function(){
//console.debug("refresh after buildPage:", g._notRefreshSelection);
if(!g._notRefreshSelection){
this._refreshSelected(true);
}
});
//Whenever the mouse is up, end selecting.
this.connect(win.doc, "onmouseup", dp(ender, "all"));
//If autoscroll is enabled, connect to it.
this.connect(g, "onEndAutoScroll", function(isVertical, isForward, view, target){
var selectCell = _this._selecting.cell,
type, current, dir = isForward ? 1 : -1;
if(isVertical && (selectCell || _this._selecting.row)){
type = selectCell ? "cell" : "row";
current = _this._currentPoint[type];
_this._highlight(type, _createItem(type, current.row + dir, current.col), _this._toSelect);
}else if(!isVertical && (selectCell || _this._selecting.col)){
type = selectCell ? "cell" : "col";
current = _this._currentPoint[type];
_this._highlight(type, _createItem(type, current.row, target), _this._toSelect);
}
});
//If the grid is changed, selection should be consistent.
this.subscribe("dojox/grid/rearrange/move/" + g.id, "_onInternalRearrange");
this.subscribe("dojox/grid/rearrange/copy/" + g.id, "_onInternalRearrange");
this.subscribe("dojox/grid/rearrange/change/" + g.id, "_onExternalChange");
this.subscribe("dojox/grid/rearrange/insert/" + g.id, "_onExternalChange");
this.subscribe("dojox/grid/rearrange/remove/" + g.id, "clear");
//have to also select when the grid's default select is used.
this.connect(g, "onSelected", function(rowIndex){
if(this._selectedRowModified && this._isUsingRowSelector){
delete this._selectedRowModified;
}else if(!this.grid._selectingRange){
this.select("row", rowIndex);
}
});
this.connect(g, "onDeselected", function(rowIndex){
if(this._selectedRowModified && this._isUsingRowSelector){
delete this._selectedRowModified;
}else if(!this.grid._selectingRange){
this.deselect("row", rowIndex);
}
});
},
_onSelectedById: function(id, newIndex, isSelected){
if(this.grid._noInternalMapping){
return;
}
var pointSet = [this._lastAnchorPoint.row, this._lastEndPoint.row,
this._lastSelectedAnchorPoint.row, this._lastSelectedEndPoint.row];
pointSet = pointSet.concat(this._selected.row);
var found = false;
array.forEach(pointSet, function(item){
if(item){
if(item.id === id){
found = true;
item.row = newIndex;
}else if(item.row === newIndex && item.id){
item.row = -1;
}
}
});
if(!found && isSelected){
array.some(this._selected.row, function(item){
if(item && !item.id && !item.except.length){
item.id = id;
item.row = newIndex;
return true;
}
return false;
});
}
found = false;
pointSet = [this._lastAnchorPoint.cell, this._lastEndPoint.cell,
this._lastSelectedAnchorPoint.cell, this._lastSelectedEndPoint.cell];
pointSet = pointSet.concat(this._selected.cell);
array.forEach(pointSet, function(item){
if(item){
if(item.id === id){
found = true;
item.row = newIndex;
}else if(item.row === newIndex && item.id){
item.row = -1;
}
}
});
},
onSetStore: function(){
this._clearSelection("all");
},
_onInternalRearrange: function(type, mapping){
try{
//The column can not refresh it self!
this._refresh("col", false);
array.forEach(this._selected.row, function(item){
array.forEach(this.grid.layout.cells, function(cell){
this._highlightNode(cell.getNode(item.row), false);
}, this);
}, this);
//The rowbar must be cleaned manually
query(".dojoxGridRowSelectorSelected").forEach(function(node){
html.removeClass(node, "dojoxGridRowSelectorSelected");
html.removeClass(node, "dojoxGridRowSelectorSelectedUp");
html.removeClass(node, "dojoxGridRowSelectorSelectedDown");
});
var cleanUp = function(item){
if(item){
delete item.converted;
}
},
pointSet = [this._lastAnchorPoint[type], this._lastEndPoint[type],
this._lastSelectedAnchorPoint[type], this._lastSelectedEndPoint[type]];
if(type === "cell"){
this.selectRange("cell", mapping.to.min, mapping.to.max);
var cells = this.grid.layout.cells;
array.forEach(pointSet, function(item){
if(item.converted){ return; }
for(var r = mapping.from.min.row, tr = mapping.to.min.row; r <= mapping.from.max.row; ++r, ++tr){
for(var c = mapping.from.min.col, tc = mapping.to.min.col; c <= mapping.from.max.col; ++c, ++tc){
while(cells[c].hidden){ ++c; }
while(cells[tc].hidden){ ++tc; }
if(item.row == r && item.col == c){
//console.log('mapping found: (', item.row, ",",item.col,") to (", tr, ",", tc,")");
item.row = tr;
item.col = tc;
item.converted = true;
return;
}
}
}
});
}else{
pointSet = this._selected.cell.concat(this._selected[type]).concat(pointSet).concat(
[this._lastAnchorPoint.cell, this._lastEndPoint.cell,
this._lastSelectedAnchorPoint.cell, this._lastSelectedEndPoint.cell]);
array.forEach(pointSet, function(item){
if(item && !item.converted){
var from = item[type];
if(from in mapping){
item[type] = mapping[from];
}
item.converted = true;
}
});
array.forEach(this._selected[_theOther[type]], function(item){
for(var i = 0, len = item.except.length; i < len; ++i){
var from = item.except[i];
if(from in mapping){
item.except[i] = mapping[from];
}
}
});
}
array.forEach(pointSet, cleanUp);
this._refreshSelected(true);
this._focusPoint(type, this._lastEndPoint);
}catch(e){
console.warn("Selector._onInternalRearrange() error",e);
}
},
_onExternalChange: function(type, target){
var start = type == "cell" ? target.min : target[0],
end = type == "cell" ? target.max : target[target.length - 1];
this.selectRange(type, start, end);
},
_refresh: function(type, toHighlight){
if(!this._keyboardSelect[type]){
array.forEach(this._selected[type], function(item){
this._highlightSingle(type, toHighlight, item, undefined, true);
}, this);
}
},
_refreshSelected: function(){
this._refresh("col", true);
this._refresh("row", true);
this._refresh("cell", true);
},
_initAreas: function(){
var g = this.grid, f = g.focus, _this = this,
keyboardSelectReady = 1, duringKeyboardSelect = 2,
onmove = function(type, createNewEnd, rowStep, colStep, evt){
//Keyboard swipe selection is SHIFT + Direction Keys.
var ks = _this._keyboardSelect;
//Tricky, rely on valid status not being 0.
if(evt.shiftKey && ks[type]){
if(ks[type] === keyboardSelectReady){
if(type === "cell"){
var item = _this._lastEndPoint[type];
if(f.cell != g.layout.cells[item.col + colStep] || f.rowIndex != item.row + rowStep){
ks[type] = 0;
return;
}
}
//If selecting is not started, start it
_this._startSelect(type, _this._lastAnchorPoint[type], true, false, true);
_this._highlight(type, _this._lastEndPoint[type], _this._toSelect);
ks[type] = duringKeyboardSelect;
}
//Highlight to the new end point.
var newEnd = createNewEnd(type, rowStep, colStep, evt);
if(_this._isValid(type, newEnd, g)){
_this._highlight(type, newEnd, _this._toSelect);
}
_stopEvent(evt);
}
},
onkeydown = function(type, getTarget, evt, isBubble){
if(isBubble && _this.selectEnabled() && _this._config[type] != DISABLED){
switch(evt.keyCode){
case keys.SPACE:
//Keyboard single point selection is SPACE.
_this._startSelect(type, getTarget(), evt.ctrlKey, evt.shiftKey);
_this._endSelect(type);
break;
case keys.SHIFT:
//Keyboard swipe selection starts with SHIFT.
if(_this._config[type] == MULTI && _this._isValid(type, _this._lastAnchorPoint[type], g)){
//End last selection if any.
_this._endSelect(type);
_this._keyboardSelect[type] = keyboardSelectReady;
_this._usingKeyboard = true;
}
}
}
},
onkeyup = function(type, evt, isBubble){
if(isBubble && evt.keyCode == keys.SHIFT && _this._keyboardSelect[type]){
_this._endSelect(type);
_this._keyboardSelect[type] = 0;
}
};
//TODO: this area "rowHeader" should be put outside, same level as header/content.
if(g.views.views[0] instanceof _RowSelector){
this._lastFocusedRowBarIdx = 0;
f.addArea({
name:"rowHeader",
onFocus: function(evt, step){
var view = g.views.views[0];
if(view instanceof _RowSelector){
var rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0);
if(rowBarNode){
html.toggleClass(rowBarNode, f.focusClass, false);
}
//evt might not be real event, it may be a mock object instead.
if(evt && "rowIndex" in evt){
if(evt.rowIndex >= 0){
_this._lastFocusedRowBarIdx = evt.rowIndex;
}else if(!_this._lastFocusedRowBarIdx){
_this._lastFocusedRowBarIdx = 0;
}
}
rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0);
if(rowBarNode){
dijitFocus.focus(rowBarNode);
html.toggleClass(rowBarNode, f.focusClass, true);
}
f.rowIndex = _this._lastFocusedRowBarIdx;
_stopEvent(evt);
return true;
}
return false;
},
onBlur: function(evt, step){
var view = g.views.views[0];
if(view instanceof _RowSelector){
var rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0);
if(rowBarNode){
html.toggleClass(rowBarNode, f.focusClass, false);
}
_stopEvent(evt);
}
return true;
},
onMove: function(rowStep, colStep, evt){
var view = g.views.views[0];
if(rowStep && view instanceof _RowSelector){
var next = _this._lastFocusedRowBarIdx + rowStep;
if(next >= 0 && next < g.rowCount){
//TODO: these logic require a better Scroller.
_stopEvent(evt);
var rowBarNode = view.getCellNode(_this._lastFocusedRowBarIdx, 0);
html.toggleClass(rowBarNode, f.focusClass, false);
//If the row is not fetched, fetch it.
var sc = g.scroller;
var lastPageRow = sc.getLastPageRow(sc.page);
var rc = g.rowCount - 1, row = Math.min(rc, next);
if(next > lastPageRow){
g.setScrollTop(g.scrollTop + sc.findScrollTop(row) - sc.findScrollTop(_this._lastFocusedRowBarIdx));
}
//Now we have fetched the row.
rowBarNode = view.getCellNode(next, 0);
dijitFocus.focus(rowBarNode);
html.toggleClass(rowBarNode, f.focusClass, true);
_this._lastFocusedRowBarIdx = next;
//If the row is out of view, scroll to it.
f.cell = rowBarNode;
f.cell.view = view;
f.cell.getNode = function(index){
return f.cell;
};
f.rowIndex = _this._lastFocusedRowBarIdx;
f.scrollIntoView();
f.cell = null;
}
}
}
});
f.placeArea("rowHeader","before","content");
}
//Support keyboard selection.
f.addArea({
name:"cellselect",
onMove: lang.partial(onmove, "cell", function(type, rowStep, colStep, evt){
var current = _this._currentPoint[type];
return _createItem("cell", current.row + rowStep, current.col + colStep);
}),
onKeyDown: lang.partial(onkeydown, "cell", function(){
return _createItem("cell", f.rowIndex, f.cell.index);
}),
onKeyUp: lang.partial(onkeyup, "cell")
});
f.placeArea("cellselect","below","content");
f.addArea({
name:"colselect",
onMove: lang.partial(onmove, "col", function(type, rowStep, colStep, evt){
var current = _this._currentPoint[type];
return _createItem("col", current.col + colStep);
}),
onKeyDown: lang.partial(onkeydown, "col", function(){
return _createItem("col", f.getHeaderIndex());
}),
onKeyUp: lang.partial(onkeyup, "col")
});
f.placeArea("colselect","below","header");
f.addArea({
name:"rowselect",
onMove: lang.partial(onmove, "row", function(type, rowStep, colStep, evt){
return _createItem("row", f.rowIndex);
}),
onKeyDown: lang.partial(onkeydown, "row", function(){
return _createItem("row", f.rowIndex);
}),
onKeyUp: lang.partial(onkeyup, "row")
});
f.placeArea("rowselect","below","rowHeader");
},
_clearSelection: function(type, reservedItem){
// summary:
// Clear selection for given type and fire events, but retain the highlight for *reservedItem*,
// thus avoid "flashing".
// tag:
// private
// type: String
// "row", "col", or "cell
// reservedItem: __SelectItem
// The item to retain highlight.
if(type == "all"){
this._clearSelection("cell", reservedItem);
this._clearSelection("col", reservedItem);
this._clearSelection("row", reservedItem);
return;
}
this._isUsingRowSelector = true;
array.forEach(this._selected[type], function(item){
if(!_isEqual(type, reservedItem, item)){
this._highlightSingle(type, false, item);
}
}, this);
this._blurPoint(type, this._currentPoint);
this._selecting[type] = false;
this._startPoint[type] = this._currentPoint[type] = null;
this._selected[type] = [];
//Have to also deselect default grid selection.
if(type == "row" && !this.grid._selectingRange){
this._oldDeselectAll.call(this.grid.selection);
this.grid.selection._selectedById = {};
}
//Fire events.
this.grid.onEndDeselect(type, null, null, this._selected);
this.grid.onSelectCleared(type);
},
_startSelect: function(type, start, extending, isRange, mandatarySelect, toSelect){
// summary:
// Start selection, setup start point and current point, fire events.
// tag:
// private
// type: String
// "row", "col", or "cell"
// extending: Boolean
// Whether this is a multi selection
// isRange: Boolean
// Whether this is a range selection (i.e. select from the last end point to this point)
// start: __SelectItem
// The start point
// mandatarySelect: Boolean
// If true, toSelect will be same as the original selection status.
if(!this._isValid(type, start)){
return;
}
var lastIsSelected = this._isSelected(type, this._lastEndPoint[type]),
isSelected = this._isSelected(type, start);
if(this.noClear && !extending){
this._toSelect = toSelect === undefined ? true : toSelect;
}else{
//If we are modifying the selection using keyboard, retain the old status.
this._toSelect = mandatarySelect ? isSelected : !isSelected;
}
//If CTRL is not pressed or it's SINGLE mode, this is a brand new selection.
if(!extending || (!isSelected && this._config[type] == SINGLE)){
this._clearSelection("col", start);
this._clearSelection("cell", start);
if(!this.noClear || (type === 'row' && this._config[type] == SINGLE)){
this._clearSelection('row', start);
}
this._toSelect = toSelect === undefined ? true : toSelect;
}
this._selecting[type] = true;
this._currentPoint[type] = null;
//We're holding SHIFT while clicking, it's a Click-Range selection.
if(isRange && this._lastType == type && lastIsSelected == this._toSelect && this._config[type] == MULTI){
if(type === "row"){
this._isUsingRowSelector = true;
}
this._startPoint[type] = this._lastEndPoint[type];
this._highlight(type, this._startPoint[type]);
this._isUsingRowSelector = false;
}else{
this._startPoint[type] = start;
}
//Now start selection
this._curType = type;
this._fireEvent("start", type);
this._isStartFocus = true;
this._isUsingRowSelector = true;
this._highlight(type, start, this._toSelect);
this._isStartFocus = false;
},
_endSelect: function(type){
// summary:
// End selection. Keep records, fire events and cleanup status.
// tag:
// private
// type: String
// "row", "col", or "cell"
if(type === "row"){
delete this._isUsingRowSelector;
}
if(type == "all"){
this._endSelect("col");
this._endSelect("row");
this._endSelect("cell");
}else if(this._selecting[type]){
this._addToSelected(type);
this._lastAnchorPoint[type] = this._startPoint[type];
this._lastEndPoint[type] = this._currentPoint[type];
if(this._toSelect){
this._lastSelectedAnchorPoint[type] = this._lastAnchorPoint[type];
this._lastSelectedEndPoint[type] = this._lastEndPoint[type];
}
this._startPoint[type] = this._currentPoint[type] = null;
this._selecting[type] = false;
this._lastType = type;
this._fireEvent("end", type);
}
},
_fireEvent: function(evtName, type){
switch(evtName){
case "start":
this.grid[this._toSelect ? "onStartSelect" : "onStartDeselect"](type, this._startPoint[type], this._selected);
break;
case "end":
this.grid[this._toSelect ? "onEndSelect" : "onEndDeselect"](type, this._lastAnchorPoint[type], this._lastEndPoint[type], this._selected);
break;
}
},
_calcToHighlight: function(type, target, toHighlight, toSelect){
// summary:
// Calculate what status should *target* have.
// If *toSelect* is not provided, this is a no op.
// This function is time-critical!!
if(toSelect !== undefined){
var sltd;
if(this._usingKeyboard && !toHighlight){
var last = this._isInLastRange(this._lastType, target);
if(last){
sltd = this._isSelected(type, target);
//This 2 cases makes the keyboard swipe selection valid!
if(toSelect && sltd){
return false;
}
if(!toSelect && !sltd && this._isInLastRange(this._lastType, target, true)){
return true;
}
}
}
return toHighlight ? toSelect : (sltd || this._isSelected(type, target));
}
return toHighlight;
},
_highlightNode: function(node, toHighlight){
// summary:
// Do the actual highlight work.
if(node){
var selectCSSClass = "dojoxGridRowSelected";
var selectCellClass = "dojoxGridCellSelected";
html.toggleClass(node, selectCSSClass, toHighlight);
html.toggleClass(node, selectCellClass, toHighlight);
}
},
_highlightHeader: function(colIdx, toHighlight){
var cells = this.grid.layout.cells;
var node = cells[colIdx].getHeaderNode();
var selectedClass = "dojoxGridHeaderSelected";
html.toggleClass(node, selectedClass, toHighlight);
},
_highlightRowSelector: function(rowIdx, toHighlight){
//var t1 = (new Date()).getTime();
var rowSelector = this.grid.views.views[0];
if(rowSelector instanceof _RowSelector){
var node = rowSelector.getRowNode(rowIdx);
if(node){
var selectedClass = "dojoxGridRowSelectorSelected";
html.toggleClass(node, selectedClass, toHighlight);
}
}
//console.log((new Date()).getTime() - t1);
},
_highlightSingle: function(type, toHighlight, target, toSelect, isRefresh){
// summary:
// Highlight a single item.
// This function is time critical!!
var _this = this, toHL, g = _this.grid, cells = g.layout.cells;
switch(type){
case "cell":
toHL = this._calcToHighlight(type, target, toHighlight, toSelect);
var c = cells[target.col];
if(!c.hidden && !c.notselectable){
this._highlightNode(target.node || c.getNode(target.row), toHL);
}
break;
case "col":
toHL = this._calcToHighlight(type, target, toHighlight, toSelect);
this._highlightHeader(target.col, toHL);
query("td[idx='" + target.col + "']", g.domNode).forEach(function(cellNode){
var rowNode = cells[target.col].view.content.findRowTarget(cellNode);
if(rowNode){
var rowIndex = rowNode[dojox.grid.util.rowIndexTag];
_this._highlightSingle("cell", toHL, {
"row": rowIndex,
"col": target.col,
"node": cellNode
});
}
});
break;
case "row":
toHL = this._calcToHighlight(type, target, toHighlight, toSelect);
this._highlightRowSelector(target.row, toHL);
if(this._config.cell){
array.forEach(cells, function(cell){
_this._highlightSingle("cell", toHL, {
"row": target.row,
"col": cell.index,
"node": cell.getNode(target.row)
});
});
}
//To avoid dead lock
this._selectedRowModified = true;
if(!isRefresh){
g.selection.setSelected(target.row, toHL);
}
}
},
_highlight: function(type, target, toSelect){
// summary:
// Highlight from start point to target.
// toSelect: Boolean
// Whether we are selecting or deselecting.
// This function is time critical!!
if(this._selecting[type] && target !== null){
var start = this._startPoint[type],
current = this._currentPoint[type],
_this = this,
highlight = function(from, to, toHL){
_this._forEach(type, from, to, function(item){
_this._highlightSingle(type, toHL, item, toSelect);
}, true);
};
switch(type){
case "col": case "row":
if(current !== null){
if(_inRange(type, target, start, current, true)){
//target is between start and current, some selected should be deselected.
highlight(current, target, false);
}else{
if(_inRange(type, start, target, current, true)){
//selection has jumped to different direction, all should be deselected.
highlight(current, start, false);
current = start;
}
highlight(target, current, true);
}
}else{
//First time select.
this._highlightSingle(type, true, target, toSelect);
}
break;
case "cell":
if(current !== null){
if(_inRange("row", target, start, current, true) ||
_inRange("col", target, start, current, true) ||
_inRange("row", start, target, current, true) ||
_inRange("col", start, target, current, true)){
highlight(start, current, false);
}
}
highlight(start, target, true);
}
this._currentPoint[type] = target;
this._focusPoint(type, this._currentPoint);
}
},
_focusPoint: function(type, point){
// summary:
// Focus the current point, so when you move mouse, the focus indicator follows you.
if(!this._isStartFocus){
var current = point[type],
f = this.grid.focus;
if(type == "col"){
f._colHeadFocusIdx = current.col;
f.focusArea("header");
}else if(type == "row"){
f.focusArea("rowHeader", {
"rowIndex": current.row
});
}else if(type == "cell"){
f.setFocusIndex(current.row, current.col);
}
}
},
_blurPoint: function(type, point){
// summary:
// Blur the current point.
var f = this.grid.focus;
if(type == "col"){
f._blurHeader();
}else if(type == "cell"){
f._blurContent();
}
},
_addToSelected: function(type){
// summary:
// Record the selected items.
var toSelect = this._toSelect, _this = this,
toAdd = [], toRemove = [],
start = this._startPoint[type],
end = this._currentPoint[type];
if(this._usingKeyboard){
//If using keyboard, selection will be ended after every move. But we have to remember the original selection status,
//so as to return to correct status when we shrink the selection region.
this._forEach(type, this._lastAnchorPoint[type], this._lastEndPoint[type], function(item){
//If the original selected item is not in current range, change its status.
if(!_inRange(type, item, start, end)){
(toSelect ? toRemove : toAdd).push(item);
}
});
}
this._forEach(type, start, end, function(item){
var isSelected = _this._isSelected(type, item);
if(toSelect && !isSelected){
//Add new selected items
toAdd.push(item);
}else if(!toSelect){
//Remove deselected items.
toRemove.push(item);
}
});
this._add(type, toAdd);
this._remove(type, toRemove);
// have to keep record in original grid selection
array.forEach(this._selected.row, function(item){
if(item.except.length > 0){
//to avoid dead lock
this._selectedRowModified = true;
this.grid.selection.setSelected(item.row, false);
}
}, this);
},
_forEach: function(type, start, end, func, halfClose){
// summary:
// Go through items from *start* point to *end* point.
// This function is time critical!!
if(!this._isValid(type, start, true) || !this._isValid(type, end, true)){
return;
}
switch(type){
case "col": case "row":
start = start[type];
end = end[type];
var dir = end > start ? 1 : -1;
if(!halfClose){
end += dir;
}
for(; start != end; start += dir){
func(_createItem(type, start));
}
break;
case "cell":
var colDir = end.col > start.col ? 1 : -1,
rowDir = end.row > start.row ? 1 : -1;
for(var i = start.row, p = end.row + rowDir; i != p; i += rowDir){
for(var j = start.col, q = end.col + colDir; j != q; j += colDir){
func(_createItem(type, i, j));
}
}
}
},
_makeupForExceptions: function(type, newCellItems){
// summary:
// When new cells is selected, maybe they will fill in the "holes" in selected rows and columns.
var makedUps = [];
array.forEach(this._selected[type], function(v1){
array.forEach(newCellItems, function(v2){
if(v1[type] == v2[type]){
var pos = array.indexOf(v1.except, v2[_theOther[type]]);
if(pos >= 0){
v1.except.splice(pos, 1);
}
makedUps.push(v2);
}
});
});
return makedUps;
},
_makeupForCells: function(type, newItems){
// summary:
// When some rows/cols are selected, maybe they can cover some of the selected cells,
// and fill some of the "holes" in the selected cols/rows.
var toRemove = [];
array.forEach(this._selected.cell, function(v1){
array.some(newItems, function(v2){
if(v1[type] == v2[type]){
toRemove.push(v1);
return true;
}
return false;
});
});
this._remove("cell", toRemove);
array.forEach(this._selected[_theOther[type]], function(v1){
array.forEach(newItems, function(v2){
var pos = array.indexOf(v1.except, v2[type]);
if(pos >= 0){
v1.except.splice(pos, 1);
}
});
});
},
_addException: function(type, items){
// summary:
// If some rows/cols are deselected, maybe they have created "holes" in selected cols/rows.
array.forEach(this._selected[type], function(v1){
array.forEach(items, function(v2){
v1.except.push(v2[_theOther[type]]);
});
});
},
_addCellException: function(type, items){
// summary:
// If some cells are deselected, maybe they have created "holes" in selected rows/cols.
array.forEach(this._selected[type], function(v1){
array.forEach(items, function(v2){
if(v1[type] == v2[type]){
v1.except.push(v2[_theOther[type]]);
}
});
});
},
_add: function(type, items){
// summary:
// Add to the selection record.
var cells = this.grid.layout.cells;
if(type == "cell"){
var colMakedup = this._makeupForExceptions("col", items);
var rowMakedup = this._makeupForExceptions("row", items);
//Step over hidden columns.
items = array.filter(items, function(item){
return array.indexOf(colMakedup, item) < 0 && array.indexOf(rowMakedup, item) < 0 &&
!cells[item.col].hidden && !cells[item.col].notselectable;
});
}else{
if(type == "col"){
//Step over hidden columns.
items = array.filter(items, function(item){
return !cells[item.col].hidden && !cells[item.col].notselectable;
});
}
this._makeupForCells(type, items);
this._selected[type] = array.filter(this._selected[type], function(v){
return array.every(items, function(item){
return v[type] !== item[type];
});
});
}
if(type != "col" && this.grid._hasIdentity){
array.forEach(items, function(item){
var record = this.grid._by_idx[item.row];
if(record){
item.id = record.idty;
}
}, this);
}
this._selected[type] = this._selected[type].concat(items);
},
_remove: function(type, items){
// summary:
// Remove from the selection record.
var comp = lang.partial(_isEqual, type);
this._selected[type] = array.filter(this._selected[type], function(v1){
return !array.some(items, function(v2){
return comp(v1, v2);
});
});
if(type == "cell"){
this._addCellException("col", items);
this._addCellException("row", items);
}else if(this._config.cell){
this._addException(_theOther[type], items);
}
},
_isCellNotInExcept: function(type, item){
// summary:
// Return true only when a cell is covered by selected row/col, and its not a "hole".
var attr = item[type], corres = item[_theOther[type]];
return array.some(this._selected[type], function(v){
return v[type] == attr && array.indexOf(v.except, corres) < 0;
});
},
_isSelected: function(type, item){
// summary:
// Return true when the item is selected. (or logically selected, i.e, covered by a row/col).
if(!item){ return false; }
var res = array.some(this._selected[type], function(v){
var ret = _isEqual(type, item, v);
if(ret && type !== "cell"){
return v.except.length === 0;
}
return ret;
});
if(!res && type === "cell"){
res = (this._isCellNotInExcept("col", item) || this._isCellNotInExcept("row", item));
if(type === "cell"){
res = res && !this.grid.layout.cells[item.col].notselectable;
}
}
return res;
},
_isInLastRange: function(type, item, isSelected){
// summary:
// Return true only when the item is in the last seletion/deseletion range.
var start = this[isSelected ? "_lastSelectedAnchorPoint" : "_lastAnchorPoint"][type],
end = this[isSelected ? "_lastSelectedEndPoint" : "_lastEndPoint"][type];
if(!item || !start || !end){ return false; }
return _inRange(type, item, start, end);
},
_isValid: function(type, item, allowNotSelectable){
// summary:
// Check whether the item is a valid __SelectItem for the given type.
if(!item){ return false; }
try{
var g = this.grid, index = item[type];
switch(type){
case "col":
return index >= 0 && index < g.layout.cells.length && lang.isArray(item.except) &&
(allowNotSelectable || !g.layout.cells[index].notselectable);
case "row":
return index >= 0 && index < g.rowCount && lang.isArray(item.except);
case "cell":
return item.col >= 0 && item.col < g.layout.cells.length &&
item.row >= 0 && item.row < g.rowCount &&
(allowNotSelectable || !g.layout.cells[item.col].notselectable);
}
}catch(e){}
return false;
}
});
EnhancedGrid.registerPlugin(Selector/*name:'selector'*/, {
"dependency": ["autoScroll"]
});
return Selector;
});