diff options
Diffstat (limited to 'js/dojo-1.7.2/dojox/grid/_FocusManager.js')
| -rw-r--r-- | js/dojo-1.7.2/dojox/grid/_FocusManager.js | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/js/dojo-1.7.2/dojox/grid/_FocusManager.js b/js/dojo-1.7.2/dojox/grid/_FocusManager.js new file mode 100644 index 0000000..76d5784 --- /dev/null +++ b/js/dojo-1.7.2/dojox/grid/_FocusManager.js @@ -0,0 +1,642 @@ +//>>built +define("dojox/grid/_FocusManager", [ + "dojo/_base/array", + "dojo/_base/lang", + "dojo/_base/declare", + "dojo/_base/connect", + "dojo/_base/event", + "dojo/_base/sniff", + "dojo/query", + "./util", + "dojo/_base/html" +], function(array, lang, declare, connect, event, has, query, util, html){ + +// focus management +return declare("dojox.grid._FocusManager", null, { + // summary: + // Controls grid cell focus. Owned by grid and used internally for focusing. + // Note: grid cell actually receives keyboard input only when cell is being edited. + constructor: function(inGrid){ + this.grid = inGrid; + this.cell = null; + this.rowIndex = -1; + this._connects = []; + this._headerConnects = []; + this.headerMenu = this.grid.headerMenu; + this._connects.push(connect.connect(this.grid.domNode, "onfocus", this, "doFocus")); + this._connects.push(connect.connect(this.grid.domNode, "onblur", this, "doBlur")); + this._connects.push(connect.connect(this.grid.domNode, "mousedown", this, "_mouseDown")); + this._connects.push(connect.connect(this.grid.domNode, "mouseup", this, "_mouseUp")); + this._connects.push(connect.connect(this.grid.domNode, "oncontextmenu", this, "doContextMenu")); + this._connects.push(connect.connect(this.grid.lastFocusNode, "onfocus", this, "doLastNodeFocus")); + this._connects.push(connect.connect(this.grid.lastFocusNode, "onblur", this, "doLastNodeBlur")); + this._connects.push(connect.connect(this.grid,"_onFetchComplete", this, "_delayedCellFocus")); + this._connects.push(connect.connect(this.grid,"postrender", this, "_delayedHeaderFocus")); + }, + destroy: function(){ + array.forEach(this._connects, connect.disconnect); + array.forEach(this._headerConnects, connect.disconnect); + delete this.grid; + delete this.cell; + }, + _colHeadNode: null, + _colHeadFocusIdx: null, + _contextMenuBindNode: null, + tabbingOut: false, + focusClass: "dojoxGridCellFocus", + focusView: null, + initFocusView: function(){ + this.focusView = this.grid.views.getFirstScrollingView() || this.focusView || this.grid.views.views[0]; + this._initColumnHeaders(); + }, + isFocusCell: function(inCell, inRowIndex){ + // summary: + // states if the given cell is focused + // inCell: object + // grid cell object + // inRowIndex: int + // grid row index + // returns: + // true of the given grid cell is focused + return (this.cell == inCell) && (this.rowIndex == inRowIndex); + }, + isLastFocusCell: function(){ + if(this.cell){ + return (this.rowIndex == this.grid.rowCount-1) && (this.cell.index == this.grid.layout.cellCount-1); + } + return false; + }, + isFirstFocusCell: function(){ + if(this.cell){ + return (this.rowIndex === 0) && (this.cell.index === 0); + } + return false; + }, + isNoFocusCell: function(){ + return (this.rowIndex < 0) || !this.cell; + }, + isNavHeader: function(){ + // summary: + // states whether currently navigating among column headers. + // returns: + // true if focus is on a column header; false otherwise. + return (!!this._colHeadNode); + }, + getHeaderIndex: function(){ + // summary: + // if one of the column headers currently has focus, return its index. + // returns: + // index of the focused column header, or -1 if none have focus. + if(this._colHeadNode){ + return array.indexOf(this._findHeaderCells(), this._colHeadNode); + }else{ + return -1; + } + }, + _focusifyCellNode: function(inBork){ + var n = this.cell && this.cell.getNode(this.rowIndex); + if(n){ + html.toggleClass(n, this.focusClass, inBork); + if(inBork){ + var sl = this.scrollIntoView(); + try{ + if(!this.grid.edit.isEditing()){ + util.fire(n, "focus"); + if(sl){ this.cell.view.scrollboxNode.scrollLeft = sl; } + } + }catch(e){} + } + } + }, + _delayedCellFocus: function(){ + if(this.isNavHeader()||!this.grid.focused){ + return; + } + var n = this.cell && this.cell.getNode(this.rowIndex); + if(n){ + try{ + if(!this.grid.edit.isEditing()){ + html.toggleClass(n, this.focusClass, true); + if(this._colHeadNode){ + this.blurHeader(); + } + util.fire(n, "focus"); + } + } + catch(e){} + } + }, + _delayedHeaderFocus: function(){ + if(this.isNavHeader()){ + this.focusHeader(); + this.grid.domNode.focus(); + } + }, + _initColumnHeaders: function(){ + array.forEach(this._headerConnects, connect.disconnect); + this._headerConnects = []; + var headers = this._findHeaderCells(); + for(var i = 0; i < headers.length; i++){ + this._headerConnects.push(connect.connect(headers[i], "onfocus", this, "doColHeaderFocus")); + this._headerConnects.push(connect.connect(headers[i], "onblur", this, "doColHeaderBlur")); + } + }, + _findHeaderCells: function(){ + // This should be a one liner: + // query("th[tabindex=-1]", this.grid.viewsHeaderNode); + // But there is a bug in query() for IE -- see trac #7037. + var allHeads = query("th", this.grid.viewsHeaderNode); + var headers = []; + for (var i = 0; i < allHeads.length; i++){ + var aHead = allHeads[i]; + var hasTabIdx = html.hasAttr(aHead, "tabIndex"); + var tabindex = html.attr(aHead, "tabIndex"); + if (hasTabIdx && tabindex < 0) { + headers.push(aHead); + } + } + return headers; + }, + _setActiveColHeader: function(/*Node*/colHeaderNode, /*Integer*/colFocusIdx, /*Integer*/ prevColFocusIdx){ + //console.log("setActiveColHeader() - colHeaderNode:colFocusIdx:prevColFocusIdx = " + colHeaderNode + ":" + colFocusIdx + ":" + prevColFocusIdx); + this.grid.domNode.setAttribute("aria-activedescendant",colHeaderNode.id); + if (prevColFocusIdx != null && prevColFocusIdx >= 0 && prevColFocusIdx != colFocusIdx){ + html.toggleClass(this._findHeaderCells()[prevColFocusIdx],this.focusClass,false); + } + html.toggleClass(colHeaderNode,this.focusClass, true); + this._colHeadNode = colHeaderNode; + this._colHeadFocusIdx = colFocusIdx; + this._scrollHeader(this._colHeadFocusIdx); + }, + scrollIntoView: function(){ + var info = (this.cell ? this._scrollInfo(this.cell) : null); + if(!info || !info.s){ + return null; + } + var rt = this.grid.scroller.findScrollTop(this.rowIndex); + // place cell within horizontal view + if(info.n && info.sr){ + if(info.n.offsetLeft + info.n.offsetWidth > info.sr.l + info.sr.w){ + info.s.scrollLeft = info.n.offsetLeft + info.n.offsetWidth - info.sr.w; + }else if(info.n.offsetLeft < info.sr.l){ + info.s.scrollLeft = info.n.offsetLeft; + } + } + // place cell within vertical view + if(info.r && info.sr){ + if(rt + info.r.offsetHeight > info.sr.t + info.sr.h){ + this.grid.setScrollTop(rt + info.r.offsetHeight - info.sr.h); + }else if(rt < info.sr.t){ + this.grid.setScrollTop(rt); + } + } + + return info.s.scrollLeft; + }, + _scrollInfo: function(cell, domNode){ + if(cell){ + var cl = cell, + sbn = cl.view.scrollboxNode, + sbnr = { + w: sbn.clientWidth, + l: sbn.scrollLeft, + t: sbn.scrollTop, + h: sbn.clientHeight + }, + rn = cl.view.getRowNode(this.rowIndex); + return { + c: cl, + s: sbn, + sr: sbnr, + n: (domNode ? domNode : cell.getNode(this.rowIndex)), + r: rn + }; + } + return null; + }, + _scrollHeader: function(currentIdx){ + var info = null; + if(this._colHeadNode){ + var cell = this.grid.getCell(currentIdx); + if(!cell){ return; } + info = this._scrollInfo(cell, cell.getNode(0)); + } + if(info && info.s && info.sr && info.n){ + // scroll horizontally as needed. + var scroll = info.sr.l + info.sr.w; + if(info.n.offsetLeft + info.n.offsetWidth > scroll){ + info.s.scrollLeft = info.n.offsetLeft + info.n.offsetWidth - info.sr.w; + }else if(info.n.offsetLeft < info.sr.l){ + info.s.scrollLeft = info.n.offsetLeft; + }else if(has("ie") <= 7 && cell && cell.view.headerNode){ + // Trac 7158: scroll dojoxGridHeader for IE7 and lower + cell.view.headerNode.scrollLeft = info.s.scrollLeft; + } + } + }, + _isHeaderHidden: function(){ + // summary: + // determine if the grid headers are hidden + // relies on documented technique of setting .dojoxGridHeader { display:none; } + // returns: Boolean + // true if headers are hidden + // false if headers are not hidden + + var curView = this.focusView; + if (!curView){ + // find one so we can determine if headers are hidden + // there is no focusView after adding items to empty grid (test_data_grid_empty.html) + for (var i = 0, cView; (cView = this.grid.views.views[i]); i++) { + if(cView.headerNode ){ + curView=cView; + break; + } + } + } + return (curView && html.getComputedStyle(curView.headerNode).display == "none"); + }, + colSizeAdjust: function (e, colIdx, delta){ // adjust the column specified by colIdx by the specified delta px + var headers = this._findHeaderCells(); + var view = this.focusView; + if (!view) { + for (var i = 0, cView; (cView = this.grid.views.views[i]); i++) { + // find first view with a tableMap in order to work with empty grid + if(cView.header.tableMap.map ){ + view=cView; + break; + } + } + } + var curHeader = headers[colIdx]; + if (!view || (colIdx == headers.length-1 && colIdx === 0)){ + return; // can't adjust single col. grid + } + view.content.baseDecorateEvent(e); + // need to adjust event with header cell info since focus is no longer on header cell + e.cellNode = curHeader; //this.findCellTarget(e.target, e.rowNode); + e.cellIndex = view.content.getCellNodeIndex(e.cellNode); + e.cell = (e.cellIndex >= 0 ? this.grid.getCell(e.cellIndex) : null); + if (view.header.canResize(e)){ + var deltaObj = { + l: delta + }; + var drag = view.header.colResizeSetup(e,false); + view.header.doResizeColumn(drag, null, deltaObj); + view.update(); + } + }, + styleRow: function(inRow){ + return; + }, + setFocusIndex: function(inRowIndex, inCellIndex){ + // summary: + // focuses the given grid cell + // inRowIndex: int + // grid row index + // inCellIndex: int + // grid cell index + this.setFocusCell(this.grid.getCell(inCellIndex), inRowIndex); + }, + setFocusCell: function(inCell, inRowIndex){ + // summary: + // focuses the given grid cell + // inCell: object + // grid cell object + // inRowIndex: int + // grid row index + if(inCell && !this.isFocusCell(inCell, inRowIndex)){ + this.tabbingOut = false; + if (this._colHeadNode){ + this.blurHeader(); + } + this._colHeadNode = this._colHeadFocusIdx = null; + this.focusGridView(); + this._focusifyCellNode(false); + this.cell = inCell; + this.rowIndex = inRowIndex; + this._focusifyCellNode(true); + } + // even if this cell isFocusCell, the document focus may need to be rejiggered + // call opera on delay to prevent keypress from altering focus + if(has("opera")){ + setTimeout(lang.hitch(this.grid, 'onCellFocus', this.cell, this.rowIndex), 1); + }else{ + this.grid.onCellFocus(this.cell, this.rowIndex); + } + }, + next: function(){ + // summary: + // focus next grid cell + if(this.cell){ + var row=this.rowIndex, col=this.cell.index+1, cc=this.grid.layout.cellCount-1, rc=this.grid.rowCount-1; + if(col > cc){ + col = 0; + row++; + } + if(row > rc){ + col = cc; + row = rc; + } + if(this.grid.edit.isEditing()){ //when editing, only navigate to editable cells + var nextCell = this.grid.getCell(col); + if (!this.isLastFocusCell() && (!nextCell.editable || + this.grid.canEdit && !this.grid.canEdit(nextCell, row))){ + this.cell=nextCell; + this.rowIndex=row; + this.next(); + return; + } + } + this.setFocusIndex(row, col); + } + }, + previous: function(){ + // summary: + // focus previous grid cell + if(this.cell){ + var row=(this.rowIndex || 0), col=(this.cell.index || 0) - 1; + if(col < 0){ + col = this.grid.layout.cellCount-1; + row--; + } + if(row < 0){ + row = 0; + col = 0; + } + if(this.grid.edit.isEditing()){ //when editing, only navigate to editable cells + var prevCell = this.grid.getCell(col); + if (!this.isFirstFocusCell() && !prevCell.editable){ + this.cell=prevCell; + this.rowIndex=row; + this.previous(); + return; + } + } + this.setFocusIndex(row, col); + } + }, + move: function(inRowDelta, inColDelta) { + // summary: + // focus grid cell or simulate focus to column header based on position relative to current focus + // inRowDelta: int + // vertical distance from current focus + // inColDelta: int + // horizontal distance from current focus + + var colDir = inColDelta < 0 ? -1 : 1; + // Handle column headers. + if(this.isNavHeader()){ + var headers = this._findHeaderCells(); + var savedIdx = currentIdx = array.indexOf(headers, this._colHeadNode); + currentIdx += inColDelta; + while(currentIdx >=0 && currentIdx < headers.length && headers[currentIdx].style.display == "none"){ + // skip over hidden column headers + currentIdx += colDir; + } + if((currentIdx >= 0) && (currentIdx < headers.length)){ + this._setActiveColHeader(headers[currentIdx],currentIdx, savedIdx); + } + }else{ + if(this.cell){ + // Handle grid proper. + var sc = this.grid.scroller, + r = this.rowIndex, + rc = this.grid.rowCount-1, + row = Math.min(rc, Math.max(0, r+inRowDelta)); + if(inRowDelta){ + if(inRowDelta>0){ + if(row > sc.getLastPageRow(sc.page)){ + //need to load additional data, let scroller do that + this.grid.setScrollTop(this.grid.scrollTop+sc.findScrollTop(row)-sc.findScrollTop(r)); + } + }else if(inRowDelta<0){ + if(row <= sc.getPageRow(sc.page)){ + //need to load additional data, let scroller do that + this.grid.setScrollTop(this.grid.scrollTop-sc.findScrollTop(r)-sc.findScrollTop(row)); + } + } + } + var cc = this.grid.layout.cellCount-1, + i = this.cell.index, + col = Math.min(cc, Math.max(0, i+inColDelta)); + var cell = this.grid.getCell(col); + while(col>=0 && col < cc && cell && cell.hidden === true){ + // skip hidden cells + col += colDir; + cell = this.grid.getCell(col); + } + if (!cell || cell.hidden === true){ + // don't change col if would move to hidden + col = i; + } + //skip hidden row|cell + var n = cell.getNode(row); + if(!n && inRowDelta){ + if((row + inRowDelta) >= 0 && (row + inRowDelta) <= rc){ + this.move(inRowDelta > 0 ? ++inRowDelta : --inRowDelta, inColDelta); + } + return; + }else if((!n || html.style(n, "display") === "none") && inColDelta){ + if((col + inRowDelta) >= 0 && (col + inRowDelta) <= cc){ + this.move(inRowDelta, inColDelta > 0 ? ++inColDelta : --inColDelta); + } + return; + } + this.setFocusIndex(row, col); + if(inRowDelta){ + this.grid.updateRow(r); + } + } + } + }, + previousKey: function(e){ + if(this.grid.edit.isEditing()){ + event.stop(e); + this.previous(); + }else if(!this.isNavHeader() && !this._isHeaderHidden()) { + this.grid.domNode.focus(); // will call doFocus and set focus into header. + event.stop(e); + }else{ + this.tabOut(this.grid.domNode); + if (this._colHeadFocusIdx != null) { // clear grid header focus + html.toggleClass(this._findHeaderCells()[this._colHeadFocusIdx], this.focusClass, false); + this._colHeadFocusIdx = null; + } + this._focusifyCellNode(false); + } + }, + nextKey: function(e) { + var isEmpty = (this.grid.rowCount === 0); + if(e.target === this.grid.domNode && this._colHeadFocusIdx == null){ + this.focusHeader(); + event.stop(e); + }else if(this.isNavHeader()){ + // if tabbing from col header, then go to grid proper. + this.blurHeader(); + if(!this.findAndFocusGridCell()){ + this.tabOut(this.grid.lastFocusNode); + } + this._colHeadNode = this._colHeadFocusIdx= null; + }else if(this.grid.edit.isEditing()){ + event.stop(e); + this.next(); + }else{ + this.tabOut(this.grid.lastFocusNode); + } + }, + tabOut: function(inFocusNode){ + this.tabbingOut = true; + inFocusNode.focus(); + }, + focusGridView: function(){ + util.fire(this.focusView, "focus"); + }, + focusGrid: function(inSkipFocusCell){ + this.focusGridView(); + this._focusifyCellNode(true); + }, + findAndFocusGridCell: function(){ + // summary: + // find the first focusable grid cell + // returns: Boolean + // true if focus was set to a cell + // false if no cell found to set focus onto + + var didFocus = true; + var isEmpty = (this.grid.rowCount === 0); // If grid is empty this.grid.rowCount == 0 + if (this.isNoFocusCell() && !isEmpty){ + var cellIdx = 0; + var cell = this.grid.getCell(cellIdx); + if (cell.hidden) { + // if first cell isn't visible, use _colHeadFocusIdx + // could also use a while loop to find first visible cell - not sure that is worth it + cellIdx = this.isNavHeader() ? this._colHeadFocusIdx : 0; + } + this.setFocusIndex(0, cellIdx); + } + 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 { + didFocus = false; + } + this._colHeadNode = this._colHeadFocusIdx= null; + return didFocus; + }, + focusHeader: function(){ + var headerNodes = this._findHeaderCells(); + var saveColHeadFocusIdx = this._colHeadFocusIdx; + if (this._isHeaderHidden()){ + // grid header is hidden, focus a cell + this.findAndFocusGridCell(); + } + else if (!this._colHeadFocusIdx) { + if (this.isNoFocusCell()) { + this._colHeadFocusIdx = 0; + } + else { + this._colHeadFocusIdx = this.cell.index; + } + } + this._colHeadNode = headerNodes[this._colHeadFocusIdx]; + while(this._colHeadNode && this._colHeadFocusIdx >=0 && this._colHeadFocusIdx < headerNodes.length && + this._colHeadNode.style.display == "none"){ + // skip over hidden column headers + this._colHeadFocusIdx++; + this._colHeadNode = headerNodes[this._colHeadFocusIdx]; + } + if(this._colHeadNode && this._colHeadNode.style.display != "none"){ + // Column header cells know longer receive actual focus. So, for keyboard invocation of + // contextMenu to work, the contextMenu must be bound to the grid.domNode rather than the viewsHeaderNode. + // unbind the contextmenu from the viewsHeaderNode and to the grid when header cells are active. Reset + // the binding back to the viewsHeaderNode when header cells are no longer acive (in blurHeader) #10483 + if (this.headerMenu && this._contextMenuBindNode != this.grid.domNode){ + this.headerMenu.unBindDomNode(this.grid.viewsHeaderNode); + this.headerMenu.bindDomNode(this.grid.domNode); + this._contextMenuBindNode = this.grid.domNode; + } + this._setActiveColHeader(this._colHeadNode, this._colHeadFocusIdx, saveColHeadFocusIdx); + this._scrollHeader(this._colHeadFocusIdx); + this._focusifyCellNode(false); + }else { + // all col head nodes are hidden - focus the grid + this.findAndFocusGridCell(); + } + }, + blurHeader: function(){ + 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) + if (this.headerMenu && this._contextMenuBindNode == this.grid.domNode) { + var viewsHeader = this.grid.viewsHeaderNode; + this.headerMenu.unBindDomNode(this.grid.domNode); + this.headerMenu.bindDomNode(viewsHeader); + this._contextMenuBindNode = viewsHeader; + } + }, + doFocus: function(e){ + // trap focus only for grid dom node + if(e && e.target != e.currentTarget){ + event.stop(e); + return; + } + // don't change focus if clicking on scroller bar + if(this._clickFocus){ + return; + } + // do not focus for scrolling if grid is about to blur + if(!this.tabbingOut){ + this.focusHeader(); + } + this.tabbingOut = false; + event.stop(e); + }, + doBlur: function(e){ + event.stop(e); // FF2 + }, + doContextMenu: function(e){ + //stop contextMenu event if no header Menu to prevent default/browser contextMenu + if (!this.headerMenu){ + event.stop(e); + } + }, + doLastNodeFocus: function(e){ + if (this.tabbingOut){ + this._focusifyCellNode(false); + }else if(this.grid.rowCount >0){ + if (this.isNoFocusCell()){ + this.setFocusIndex(0,0); + } + this._focusifyCellNode(true); + }else { + this.focusHeader(); + } + this.tabbingOut = false; + event.stop(e); // FF2 + }, + doLastNodeBlur: function(e){ + event.stop(e); // FF2 + }, + doColHeaderFocus: function(e){ + this._setActiveColHeader(e.target,html.attr(e.target, "idx"),this._colHeadFocusIdx); + this._scrollHeader(this.getHeaderIndex()); + event.stop(e); + }, + doColHeaderBlur: function(e){ + html.toggleClass(e.target, this.focusClass, false); + }, + _mouseDown: function(e){ + // a flag indicating grid is being focused by clicking + this._clickFocus = dojo.some(this.grid.views.views, function(v){ + return v.scrollboxNode === e.target; + }); + }, + _mouseUp: function(e){ + this._clickFocus = false; + } +}); +});
\ No newline at end of file |
