/*
Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
if(!dojo._hasResource["dojox.grid.enhanced._FocusManager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid.enhanced._FocusManager"] = true;
dojo.provide("dojox.grid.enhanced._FocusManager");
dojo.declare("dojox.grid.enhanced._FocusArea",null,{
// summary:
// This is a friend class of _FocusManager
/*=====
// name: string
// Name of this area.
name: "",
// onFocus: function(event, step)
// Called when this area logically gets focus.
// event: Event object
// May be unavailable, should check before use.
// step: Integer
// The distance in the tab sequence from last focused area to this area.
// returns:
// whether this area is successfully focused. If not, the next area will get focus.
onFocus: function(event, step){return true;},
// onBlur: function(event, step)
// Called when this area logically loses focus.
// event: Event object
// May be unavailable, should check before use.
// step: Integer
// The distance in the tab sequence from this area to the area to focus.
// returns:
// If Boolean, means whether this area has successfully blurred. If not, the next area to focus is still this one.
// If String, means the next area to focus is given by this returned name.
onBlur: function(event, step){return true;},
// onMove: function(rowStep, colStep, event)
// Called when focus is moving around within this area.
// rowStep: Integer
// colStep: Integer
// event: Event object
// May be unavailable, should check before use.
onMove: function(rowStep, colStep, event){},
// onKey: function(event, isBubble)
// Called when some key is pressed when focus is logically in this area.
// event: Event object
// isBubble: Boolean
// Whether is in bubble stage (true) or catch stage (false).
// returns:
// If you do NOT want the event to propagate any further along the area stack, return exactly false.
// So if you return nothing (undefined), this event is still propagating.
onKey: function(event, isBubble){return true},
// getRegions: function()
// Define the small regions (dom nodes) in this area.
// returns: Array of dom nodes.
getRegions: function(){},
// onRegionFocus: function(event)
// Connected to the onfocus event of the defined regions (if any)
onRegionFocus: function(event){},
// onRegionBlur: function(event)
// Connected to the onblur event of the defined regions (if any)
onRegionBlur: function(event){},
=====*/
constructor: function(area, focusManager){
this._fm = focusManager;
this._evtStack = [area.name];
var dummy = function(){return true;};
area.onFocus = area.onFocus || dummy;
area.onBlur = area.onBlur || dummy;
area.onMove = area.onMove || dummy;
area.onKeyUp = area.onKeyUp || dummy;
area.onKeyDown = area.onKeyDown || dummy;
dojo.mixin(this, area);
},
move: function(rowStep, colStep, evt){
if(this.name){
var i, len = this._evtStack.length;
for(i = len - 1; i >= 0; --i){
if(this._fm._areas[this._evtStack[i]].onMove(rowStep, colStep, evt) === false){
return false;
}
}
}
return true;
},
_onKeyEvent: function(evt, funcName){
if(this.name){
var i, len = this._evtStack.length;
for(i = len - 1; i >= 0; --i){
if(this._fm._areas[this._evtStack[i]][funcName](evt, false) === false){
return false;
}
}
for(i = 0; i < len; ++i){
if(this._fm._areas[this._evtStack[i]][funcName](evt, true) === false){
return false;
}
}
}
return true;
},
keydown: function(evt){
return this._onKeyEvent(evt, "onKeyDown");
},
keyup: function(evt){
return this._onKeyEvent(evt, "onKeyUp");
},
contentMouseEventPlanner: function(){
return 0;
},
headerMouseEventPlanner: function(){
return 0;
}
});
dojo.declare("dojox.grid.enhanced._FocusManager", dojox.grid._FocusManager, {
_stopEvent: function(evt){
try{
if(evt && evt.preventDefault){
dojo.stopEvent(evt);
}
}catch(e){}
},
constructor: function(grid){
this.grid = grid;
this._areas = {};
this._areaQueue = [];
this._contentMouseEventHandlers = [];
this._headerMouseEventHandlers = [];
this._currentAreaIdx = -1;
this._gridBlured = true;
this._connects.push(dojo.connect(grid, "onBlur", this, "_doBlur"));
this._connects.push(dojo.connect(grid.scroller, "renderPage", this, "_delayedCellFocus"));
this.addArea({
name: "header",
onFocus: dojo.hitch(this, this.focusHeader),
onBlur: dojo.hitch(this, this._blurHeader),
onMove: dojo.hitch(this, this._navHeader),
getRegions: dojo.hitch(this, this._findHeaderCells),
onRegionFocus: dojo.hitch(this, this.doColHeaderFocus),
onRegionBlur: dojo.hitch(this, this.doColHeaderBlur),
onKeyDown: dojo.hitch(this, this._onHeaderKeyDown)
});
this.addArea({
name: "content",
onFocus: dojo.hitch(this, this._focusContent),
onBlur: dojo.hitch(this, this._blurContent),
onMove: dojo.hitch(this, this._navContent),
onKeyDown: dojo.hitch(this, this._onContentKeyDown)
});
this.addArea({
name: "editableCell",
onFocus: dojo.hitch(this, this._focusEditableCell),
onBlur: dojo.hitch(this, this._blurEditableCell),
onKeyDown: dojo.hitch(this, this._onEditableCellKeyDown),
onContentMouseEvent: dojo.hitch(this, this._onEditableCellMouseEvent),
contentMouseEventPlanner: function(evt, areas){ return -1; }
});
this.placeArea("header");
this.placeArea("content");
this.placeArea("editableCell");
this.placeArea("editableCell","above","content");
},
destroy: function(){
for(var name in this._areas){
var area = this._areas[name];
dojo.forEach(area._connects, dojo.disconnect);
area._connects = null;
if(area.uninitialize){
area.uninitialize();
}
}
this.inherited(arguments);
},
addArea: function(area){
if(area.name && dojo.isString(area.name)){
if(this._areas[area.name]){
//Just replace the original area, instead of remove it, so the position does not change.
dojo.forEach(area._connects, dojo.disconnect);
}
this._areas[area.name] = new dojox.grid.enhanced._FocusArea(area, this);
if(area.onHeaderMouseEvent){
this._headerMouseEventHandlers.push(area.name);
}
if(area.onContentMouseEvent){
this._contentMouseEventHandlers.push(area.name);
}
}
},
getArea: function(areaName){
return this._areas[areaName];
},
_bindAreaEvents: function(){
var area, hdl, areas = this._areas;
dojo.forEach(this._areaQueue, function(name){
area = areas[name];
if(!area._initialized && dojo.isFunction(area.initialize)){
area.initialize();
area._initialized = true;
}
if(area.getRegions){
area._regions = area.getRegions() || [];
dojo.forEach(area._connects || [], dojo.disconnect);
area._connects = [];
dojo.forEach(area._regions, function(r){
if(area.onRegionFocus){
hdl = dojo.connect(r, "onfocus", area.onRegionFocus);
area._connects.push(hdl);
}
if(area.onRegionBlur){
hdl = dojo.connect(r, "onblur", area.onRegionBlur);
area._connects.push(hdl);
}
});
}
});
},
removeArea: function(areaName){
var area = this._areas[areaName];
if(area){
this.ignoreArea(areaName);
var i = dojo.indexOf(this._contentMouseEventHandlers, areaName);
if(i >= 0){
this._contentMouseEventHandlers.splice(i, 1);
}
i = dojo.indexOf(this._headerMouseEventHandlers, areaName);
if(i >= 0){
this._headerMouseEventHandlers.splice(i, 1);
}
dojo.forEach(area._connects, dojo.disconnect);
if(area.uninitialize){
area.uninitialize();
}
delete this._areas[areaName];
}
},
currentArea: function(areaName, toBlurOld){
// summary:
// Set current area to the one areaName refers.
// areaName: String
var idx, cai = this._currentAreaIdx;
if(dojo.isString(areaName) && (idx = dojo.indexOf(this._areaQueue, areaName)) >= 0){
if(cai != idx){
this.tabbingOut = false;
if(toBlurOld && cai >= 0 && cai < this._areaQueue.length){
this._areas[this._areaQueue[cai]].onBlur();
}
this._currentAreaIdx = idx;
}
}else{
return (cai < 0 || cai >= this._areaQueue.length) ?
new dojox.grid.enhanced._FocusArea({}, this) :
this._areas[this._areaQueue[this._currentAreaIdx]];
}
return null;
},
placeArea: function(name, pos, otherAreaName){
// summary:
// Place the area refered by *name* at some logical position relative to an existing area.
// example:
// placeArea("myarea","before"|"after",...)
// placeArea("myarea","below"|"above",...)
if(!this._areas[name]){ return; }
var idx = dojo.indexOf(this._areaQueue,otherAreaName);
switch(pos){
case "after":
if(idx >= 0){ ++idx; }
//intentional drop through
case "before":
if(idx >= 0){
this._areaQueue.splice(idx,0,name);
break;
}
//intentional drop through
default:
this._areaQueue.push(name);
break;
case "above":
var isAbove = true;
//intentional drop through
case "below":
var otherArea = this._areas[otherAreaName];
if(otherArea){
if(isAbove){
otherArea._evtStack.push(name);
}else{
otherArea._evtStack.splice(0,0,name);
}
}
}
},
ignoreArea: function(name){
this._areaQueue = dojo.filter(this._areaQueue,function(areaName){
return areaName != name;
});
},
focusArea: function(/* int|string|areaObj */areaId,evt){
var idx;
if(typeof areaId == "number"){
idx = areaId < 0 ? this._areaQueue.length + areaId : areaId;
}else{
idx = dojo.indexOf(this._areaQueue,
dojo.isString(areaId) ? areaId : (areaId && areaId.name));
}
if(idx < 0){ idx = 0; }
var step = idx - this._currentAreaIdx;
this._gridBlured = false;
if(step){
this.tab(step, evt);
}else{
this.currentArea().onFocus(evt, step);
}
},
tab: function(step,evt){
//console.log("===========tab",step,"curArea",this._currentAreaIdx,"areaCnt",this._areaQueue.length);
this._gridBlured = false;
this.tabbingOut = false;
if(step === 0){
return;
}
var cai = this._currentAreaIdx;
var dir = step > 0 ? 1:-1;
if(cai < 0 || cai >= this._areaQueue.length){
cai = (this._currentAreaIdx += step);
}else{
var nextArea = this._areas[this._areaQueue[cai]].onBlur(evt,step);
if(nextArea === true){
cai = (this._currentAreaIdx += step);
}else if(dojo.isString(nextArea) && this._areas[nextArea]){
cai = this._currentAreaIdx = dojo.indexOf(this._areaQueue,nextArea);
}
}
//console.log("target area:",cai);
for(; cai >= 0 && cai < this._areaQueue.length; cai += dir){
this._currentAreaIdx = cai;
if(this._areaQueue[cai] && this._areas[this._areaQueue[cai]].onFocus(evt,step)){
//console.log("final target area:",this._currentAreaIdx);
return;
}
}
//console.log("tab out");
this.tabbingOut = true;
if(step < 0){
this._currentAreaIdx = -1;
dijit.focus(this.grid.domNode);
}else{
this._currentAreaIdx = this._areaQueue.length;
dijit.focus(this.grid.lastFocusNode);
}
},
_onMouseEvent: function(type, evt){
var lowercase = type.toLowerCase(),
handlers = this["_" + lowercase + "MouseEventHandlers"],
res = dojo.map(handlers, function(areaName){
return {
"area": areaName,
"idx": this._areas[areaName][lowercase + "MouseEventPlanner"](evt, handlers)
};
}, this).sort(function(a, b){
return b.idx - a.idx;
}),
resHandlers = dojo.map(res, function(handler){
return res.area;
}),
i = res.length;
while(--i >= 0){
if(this._areas[res[i].area]["on" + type + "MouseEvent"](evt, resHandlers) === false){
return;
}
}
},
contentMouseEvent: function(evt){
this._onMouseEvent("Content", evt);
},
headerMouseEvent: function(evt){
this._onMouseEvent("Header", evt);
},
initFocusView: function(){
// summary:
// Overwritten
this.focusView = this.grid.views.getFirstScrollingView() || this.focusView || this.grid.views.views[0];
this._bindAreaEvents();
},
isNavHeader: function(){
// summary:
// Overwritten
// Check whether currently navigating among column headers.
// return:
// true - focus is on a certain column header | false otherwise
return this._areaQueue[this._currentAreaIdx] == "header";
},
previousKey: function(e){
// summary:
// Overwritten
this.tab(-1,e);
},
nextKey: function(e){
// summary:
// Overwritten
this.tab(1,e);
},
setFocusCell: function(/* Object */inCell, /* Integer */inRowIndex){
// summary:
// Overwritten - focuses the given grid cell
if(inCell){
this.currentArea(this.grid.edit.isEditing() ? "editableCell" : "content", true);
//This is very slow when selecting cells!
//this.focusGridView();
this._focusifyCellNode(false);
this.cell = inCell;
this.rowIndex = inRowIndex;
this._focusifyCellNode(true);
}
this.grid.onCellFocus(this.cell, this.rowIndex);
},
doFocus: function(e){
// summary:
// Overwritten
// trap focus only for grid dom node
// do not focus for scrolling if grid is about to blur
if(e && e.target == e.currentTarget && !this.tabbingOut){
if(this._gridBlured){
this._gridBlured = false;
if(this._currentAreaIdx < 0 || this._currentAreaIdx >= this._areaQueue.length){
this.focusArea(0, e);
}else{
this.focusArea(this._currentAreaIdx, e);
}
}
}else{
this.tabbingOut = false;
}
dojo.stopEvent(e);
},
_doBlur: function(){
this._gridBlured = true;
},
doLastNodeFocus: function(e){
// summary:
// Overwritten
if(this.tabbingOut){
this.tabbingOut = false;
}else{
this.focusArea(-1, e);
}
},
_delayedHeaderFocus: function(){
// summary:
// Overwritten
if(this.isNavHeader()){
this.focusHeader();
}
},
_delayedCellFocus: function(){
// summary:
// Overwritten
this.currentArea("header", true);
this.focusArea(this._currentAreaIdx);
},
_changeMenuBindNode: function(oldBindNode, newBindNode){
var hm = this.grid.headerMenu;
if(hm && this._contextMenuBindNode == oldBindNode){
hm.unBindDomNode(oldBindNode);
hm.bindDomNode(newBindNode);
this._contextMenuBindNode = newBindNode;
}
},
//---------------Header Area------------------------------------------
focusHeader: function(evt, step){ //need a further look why these changes to parent's
// summary:
// Overwritten
var didFocus = false;
this.inherited(arguments);
if(this._colHeadNode && dojo.style(this._colHeadNode, 'display') != "none"){
dijit.focus(this._colHeadNode);
this._stopEvent(evt);
didFocus = true;
}
return didFocus;
},
_blurHeader: function(evt,step){
// summary:
// Overwritten
if(this._colHeadNode){
dojo.removeClass(this._colHeadNode, this.focusClass);
}
dojo.removeAttr(this.grid.domNode,"aria-activedescendant");
// reset contextMenu onto viewsHeaderNode so right mouse on header will invoke (see focusHeader)
this._changeMenuBindNode(this.grid.domNode,this.grid.viewsHeaderNode);
//moved here from nextKey
this._colHeadNode = this._colHeadFocusIdx = null;
return true;
},
_navHeader: function(rowStep, colStep, evt){
var colDir = colStep < 0 ? -1 : 1,
savedIdx = dojo.indexOf(this._findHeaderCells(), this._colHeadNode);
if(savedIdx >= 0 && (evt.shiftKey && evt.ctrlKey)){
this.colSizeAdjust(evt, savedIdx, colDir * 5);
return;
}
this.move(rowStep, colStep);
},
_onHeaderKeyDown: function(e, isBubble){
if(isBubble){
var dk = dojo.keys;
switch(e.keyCode){
case dk.ENTER:
case dk.SPACE:
var colIdx = this.getHeaderIndex();
if(colIdx >= 0 && !this.grid.pluginMgr.isFixedCell(e.cell)/*TODO*/){
this.grid.setSortIndex(colIdx, null, e);
dojo.stopEvent(e);
}
break;
}
}
return true;
},
_setActiveColHeader: function(){
// summary:
// Overwritten
this.inherited(arguments);
//EDG now will decorate event on header key events, if no focus, the cell will be wrong
dijit.focus(this._colHeadNode);
},
//---------------Content Area------------------------------------------
findAndFocusGridCell: function(){
// summary:
// Overwritten
this._focusContent();
},
_focusContent: function(evt,step){
var didFocus = true;
var isEmpty = (this.grid.rowCount === 0); // If grid is empty this.grid.rowCount == 0
if(this.isNoFocusCell() && !isEmpty){
//skip all the hidden cells
for(var i = 0, cell = this.grid.getCell(0); cell && cell.hidden; cell = this.grid.getCell(++i)){}
this.setFocusIndex(0, cell ? i : 0);
}else if(this.cell && !isEmpty){
if(this.focusView && !this.focusView.rowNodes[this.rowIndex]){
// if rowNode for current index is undefined (likely as a result of a sort and because of #7304)
// scroll to that row
this.grid.scrollToRow(this.rowIndex);
this.focusGrid();
}else{
this.setFocusIndex(this.rowIndex, this.cell.index);
}
}else{
didFocus = false;
}
if(didFocus){ this._stopEvent(evt); }
return didFocus;
},
_blurContent: function(evt,step){
this._focusifyCellNode(false);
return true;
},
_navContent: function(rowStep, colStep, evt){
if((this.rowIndex === 0 && rowStep < 0) || (this.rowIndex === this.grid.rowCount - 1 && rowStep > 0)){
return;
}
this._colHeadNode = null;
this.move(rowStep, colStep, evt);
if(evt){
dojo.stopEvent(evt);
}
},
_onContentKeyDown: function(e, isBubble){
if(isBubble){
var dk = dojo.keys, s = this.grid.scroller;
switch(e.keyCode){
case dk.ENTER:
case dk.SPACE:
var g = this.grid;
if(g.indirectSelection){ break; }
g.selection.clickSelect(this.rowIndex, dojo.isCopyKey(e), e.shiftKey);
g.onRowClick(e);
dojo.stopEvent(e);
break;
case dk.PAGE_UP:
if(this.rowIndex !== 0){
if(this.rowIndex != s.firstVisibleRow + 1){
this._navContent(s.firstVisibleRow - this.rowIndex, 0);
}else{
this.grid.setScrollTop(s.findScrollTop(this.rowIndex - 1));
this._navContent(s.firstVisibleRow - s.lastVisibleRow + 1, 0);
}
dojo.stopEvent(e);
}
break;
case dk.PAGE_DOWN:
if(this.rowIndex + 1 != this.grid.rowCount){
dojo.stopEvent(e);
if(this.rowIndex != s.lastVisibleRow - 1){
this._navContent(s.lastVisibleRow - this.rowIndex - 1, 0);
}else{
this.grid.setScrollTop(s.findScrollTop(this.rowIndex + 1));
this._navContent(s.lastVisibleRow - s.firstVisibleRow - 1, 0);
}
dojo.stopEvent(e);
}
break;
}
}
return true;
},
//------------------editable content area-------------------------
_blurFromEditableCell: false,
_isNavigating: false,
_navElems: null,
_focusEditableCell: function(evt,step){
var didFocus = false;
if(this._isNavigating){
didFocus = true;
}else if(this.grid.edit.isEditing() && this.cell){
if(this._blurFromEditableCell || !this._blurEditableCell(evt, step)){
this.setFocusIndex(this.rowIndex,this.cell.index);
didFocus = true;
}
this._stopEvent(evt);
}
return didFocus;
},
_applyEditableCell: function(){
try{
this.grid.edit.apply();
}catch(e){
console.warn("_FocusManager._applyEditableCell() error:", e);
}
},
_blurEditableCell: function(evt,step){
this._blurFromEditableCell = false;
if(this._isNavigating){
var toBlur = true;
if(evt){
var elems = this._navElems;
var firstElem = elems.lowest || elems.first;
var lastElem = elems.last || elems.highest || firstElem;
var target = dojo.isIE ? evt.srcElement : evt.target;
toBlur = target == (step > 0 ? lastElem : firstElem);
}
if(toBlur){
this._isNavigating = false;
return "content";
}
return false;
}else if(this.grid.edit.isEditing() && this.cell){
if(!step || typeof step != "number"){ return false; }
var dir = step > 0 ? 1 : -1;
var cc = this.grid.layout.cellCount;
for(var cell, col = this.cell.index + dir; col >= 0 && col < cc; col += dir){
cell = this.grid.getCell(col);
if(cell.editable){
this.cell = cell;
this._blurFromEditableCell = true;
return false;
}
}
if((this.rowIndex > 0 || dir == 1) && (this.rowIndex < this.grid.rowCount || dir == -1)){
this.rowIndex += dir;
//this.cell = this.grid.getCell(0); //There must be an editable cell, so this is not necessary.
for(col = dir > 0 ? 0 : cc - 1; col >= 0 && col < cc; col += dir){
cell = this.grid.getCell(col);
if(cell.editable){
this.cell = cell;
break;
}
}
this._applyEditableCell();
return "content";
}
}
return true;
},
_initNavigatableElems: function(){
this._navElems = dijit._getTabNavigable(this.cell.getNode(this.rowIndex));
},
_onEditableCellKeyDown: function(e, isBubble){
var dk = dojo.keys,
g = this.grid,
edit = g.edit,
editApplied = false,
toPropagate = true;
switch(e.keyCode){
case dk.ENTER:
if(isBubble && edit.isEditing()){
this._applyEditableCell();
editApplied = true;
dojo.stopEvent(e);
}
//intentional drop through
case dk.SPACE:
if(!isBubble && this._isNavigating){
toPropagate = false;
break;
}
if(isBubble){
if(!this.cell.editable && this.cell.navigatable){
this._initNavigatableElems();
var toFocus = this._navElems.lowest || this._navElems.first;
if(toFocus){
this._isNavigating = true;
dijit.focus(toFocus);
dojo.stopEvent(e);
this.currentArea("editableCell", true);
break;
}
}
if(!editApplied && !edit.isEditing() && !g.pluginMgr.isFixedCell(this.cell)){
edit.setEditCell(this.cell, this.rowIndex);
}
if(editApplied){
this.currentArea("content", true);
}else if(this.cell.editable && g.canEdit()){
this.currentArea("editableCell", true);
}
}
break;
case dk.PAGE_UP:
case dk.PAGE_DOWN:
if(!isBubble && edit.isEditing()){
//prevent propagating to content area
toPropagate = false;
}
break;
case dk.ESCAPE:
if(!isBubble){
edit.cancel();
this.currentArea("content", true);
}
}
return toPropagate;
},
_onEditableCellMouseEvent: function(evt){
if(evt.type == "click"){
var cell = this.cell || evt.cell;
if(cell && !cell.editable && cell.navigatable){
this._initNavigatableElems();
if(this._navElems.lowest || this._navElems.first){
var target = dojo.isIE ? evt.srcElement : evt.target;
if(target != cell.getNode(evt.rowIndex)){
this._isNavigating = true;
this.focusArea("editableCell", evt);
dijit.focus(target);
return false;
}
}
}else if(this.grid.singleClickEdit){
this.currentArea("editableCell");
return false;
}
}
return true;
}
});
}