diff options
Diffstat (limited to 'js/dojo/dojox/grid/enhanced/_FocusManager.js')
| -rw-r--r-- | js/dojo/dojox/grid/enhanced/_FocusManager.js | 774 |
1 files changed, 774 insertions, 0 deletions
diff --git a/js/dojo/dojox/grid/enhanced/_FocusManager.js b/js/dojo/dojox/grid/enhanced/_FocusManager.js new file mode 100644 index 0000000..872d089 --- /dev/null +++ b/js/dojo/dojox/grid/enhanced/_FocusManager.js @@ -0,0 +1,774 @@ +//>>built +define("dojox/grid/enhanced/_FocusManager", [ + "dojo/_base/kernel", + "dojo/_base/lang", + "dojo/_base/declare", + "dojo/_base/array", + "dojo/_base/connect", + "dojo/_base/event", + "dojo/_base/sniff", + "dojo/_base/html", + "dojo/keys", + "dijit/a11y", + "dijit/focus", + "../_FocusManager" +], function(dojo, lang, declare, array, connect, event, has, html, keys, dijitA11y, dijitFocus, _FocusManager){ + +var _FocusArea = 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; + lang.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; + } +}); + +return declare("dojox.grid.enhanced._FocusManager", _FocusManager, { + _stopEvent: function(evt){ + try{ + if(evt && evt.preventDefault){ + event.stop(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(connect.connect(grid, "onBlur", this, "_doBlur")); + this._connects.push(connect.connect(grid.scroller, "renderPage", this, "_delayedCellFocus")); + + this.addArea({ + name: "header", + onFocus: lang.hitch(this, this.focusHeader), + onBlur: lang.hitch(this, this._blurHeader), + onMove: lang.hitch(this, this._navHeader), + getRegions: lang.hitch(this, this._findHeaderCells), + onRegionFocus: lang.hitch(this, this.doColHeaderFocus), + onRegionBlur: lang.hitch(this, this.doColHeaderBlur), + onKeyDown: lang.hitch(this, this._onHeaderKeyDown) + }); + this.addArea({ + name: "content", + onFocus: lang.hitch(this, this._focusContent), + onBlur: lang.hitch(this, this._blurContent), + onMove: lang.hitch(this, this._navContent), + onKeyDown: lang.hitch(this, this._onContentKeyDown) + }); + this.addArea({ + name: "editableCell", + onFocus: lang.hitch(this, this._focusEditableCell), + onBlur: lang.hitch(this, this._blurEditableCell), + onKeyDown: lang.hitch(this, this._onEditableCellKeyDown), + onContentMouseEvent: lang.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]; + array.forEach(area._connects, connect.disconnect); + area._connects = null; + if(area.uninitialize){ + area.uninitialize(); + } + } + this.inherited(arguments); + }, + addArea: function(area){ + if(area.name && lang.isString(area.name)){ + if(this._areas[area.name]){ + //Just replace the original area, instead of remove it, so the position does not change. + array.forEach(area._connects, connect.disconnect); + } + this._areas[area.name] = new _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; + array.forEach(this._areaQueue, function(name){ + area = areas[name]; + if(!area._initialized && lang.isFunction(area.initialize)){ + area.initialize(); + area._initialized = true; + } + if(area.getRegions){ + area._regions = area.getRegions() || []; + array.forEach(area._connects || [], connect.disconnect); + area._connects = []; + array.forEach(area._regions, function(r){ + if(area.onRegionFocus){ + hdl = connect.connect(r, "onfocus", area.onRegionFocus); + area._connects.push(hdl); + } + if(area.onRegionBlur){ + hdl = connect.connect(r, "onblur", area.onRegionBlur); + area._connects.push(hdl); + } + }); + } + }); + }, + removeArea: function(areaName){ + var area = this._areas[areaName]; + if(area){ + this.ignoreArea(areaName); + var i = array.indexOf(this._contentMouseEventHandlers, areaName); + if(i >= 0){ + this._contentMouseEventHandlers.splice(i, 1); + } + i = array.indexOf(this._headerMouseEventHandlers, areaName); + if(i >= 0){ + this._headerMouseEventHandlers.splice(i, 1); + } + array.forEach(area._connects, connect.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(lang.isString(areaName) && (idx = array.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 _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 = array.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 = array.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 = array.indexOf(this._areaQueue, + lang.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(lang.isString(nextArea) && this._areas[nextArea]){ + cai = this._currentAreaIdx = array.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; + dijitFocus.focus(this.grid.domNode); + }else{ + this._currentAreaIdx = this._areaQueue.length; + dijitFocus.focus(this.grid.lastFocusNode); + } + }, + _onMouseEvent: function(type, evt){ + var lowercase = type.toLowerCase(), + handlers = this["_" + lowercase + "MouseEventHandlers"], + res = array.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 = array.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; + } + event.stop(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 && html.style(this._colHeadNode, 'display') != "none"){ + dijitFocus.focus(this._colHeadNode); + this._stopEvent(evt); + didFocus = true; + } + return didFocus; + }, + _blurHeader: function(evt,step){ + // summary: + // Overwritten + if(this._colHeadNode){ + html.removeClass(this._colHeadNode, this.focusClass); + } + html.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 = array.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 = 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); + event.stop(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 + dijitFocus.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){ + event.stop(evt); + } + }, + _onContentKeyDown: function(e, isBubble){ + if(isBubble){ + var dk = 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, connect.isCopyKey(e), e.shiftKey); + g.onRowClick(e); + event.stop(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); + } + event.stop(e); + } + break; + case dk.PAGE_DOWN: + if(this.rowIndex + 1 != this.grid.rowCount){ + event.stop(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); + } + event.stop(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 = has("ie") ? evt.srcElement : evt.target; + toBlur = target == (step > 0 ? lastElem : firstElem); + } + if(toBlur){ + this._isNavigating = false; + html.setSelectable(this.cell.getNode(this.rowIndex), 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 = dijitA11y._getTabNavigable(this.cell.getNode(this.rowIndex)); + }, + _onEditableCellKeyDown: function(e, isBubble){ + var dk = 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; + event.stop(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; + html.setSelectable(this.cell.getNode(this.rowIndex), true); + dijitFocus.focus(toFocus); + event.stop(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 = has("ie") ? evt.srcElement : evt.target; + if(target != cell.getNode(evt.rowIndex)){ + this._isNavigating = true; + this.focusArea("editableCell", evt); + html.setSelectable(cell.getNode(evt.rowIndex), true); + dijitFocus.focus(target); + return false; + } + } + }else if(this.grid.singleClickEdit){ + this.currentArea("editableCell"); + return false; + } + } + return true; + } +}); +});
\ No newline at end of file |
