dojo.provide("dijit._KeyNavContainer"); dojo.require("dijit._Container"); dojo.declare("dijit._KeyNavContainer", [dijit._Container], { // summary: // A _Container with keyboard navigation of its children. // description: // To use this mixin, call connectKeyNavHandlers() in // postCreate() and call startupKeyNavChildren() in startup(). // It provides normalized keyboard and focusing code for Container // widgets. /*===== // focusedChild: [protected] Widget // The currently focused child widget, or null if there isn't one focusedChild: null, =====*/ // tabIndex: Integer // Tab index of the container; same as HTML tabindex attribute. // Note then when user tabs into the container, focus is immediately // moved to the first item in the container. tabIndex: "0", _keyNavCodes: {}, connectKeyNavHandlers: function(/*dojo.keys[]*/ prevKeyCodes, /*dojo.keys[]*/ nextKeyCodes){ // summary: // Call in postCreate() to attach the keyboard handlers // to the container. // preKeyCodes: dojo.keys[] // Key codes for navigating to the previous child. // nextKeyCodes: dojo.keys[] // Key codes for navigating to the next child. // tags: // protected var keyCodes = this._keyNavCodes = {}; var prev = dojo.hitch(this, this.focusPrev); var next = dojo.hitch(this, this.focusNext); dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; }); dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; }); this.connect(this.domNode, "onkeypress", "_onContainerKeypress"); this.connect(this.domNode, "onfocus", "_onContainerFocus"); }, startupKeyNavChildren: function(){ // summary: // Call in startup() to set child tabindexes to -1 // tags: // protected dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild")); }, addChild: function(/*Widget*/ widget, /*int?*/ insertIndex){ // summary: // Add a child to our _Container dijit._KeyNavContainer.superclass.addChild.apply(this, arguments); this._startupChild(widget); }, focus: function(){ // summary: // Default focus() implementation: focus the first child. this.focusFirstChild(); }, focusFirstChild: function(){ // summary: // Focus the first focusable child in the container. // tags: // protected this.focusChild(this._getFirstFocusableChild()); }, focusNext: function(){ // summary: // Focus the next widget or focal node (for widgets // with multiple focal nodes) within this container. // tags: // protected if(this.focusedChild && this.focusedChild.hasNextFocalNode && this.focusedChild.hasNextFocalNode()){ this.focusedChild.focusNext(); return; } var child = this._getNextFocusableChild(this.focusedChild, 1); if(child.getFocalNodes){ this.focusChild(child, child.getFocalNodes()[0]); }else{ this.focusChild(child); } }, focusPrev: function(){ // summary: // Focus the previous widget or focal node (for widgets // with multiple focal nodes) within this container. // tags: // protected if(this.focusedChild && this.focusedChild.hasPrevFocalNode && this.focusedChild.hasPrevFocalNode()){ this.focusedChild.focusPrev(); return; } var child = this._getNextFocusableChild(this.focusedChild, -1); if(child.getFocalNodes){ var nodes = child.getFocalNodes(); this.focusChild(child, nodes[nodes.length-1]); }else{ this.focusChild(child); } }, focusChild: function(/*Widget*/ widget, /*Node?*/ node){ // summary: // Focus widget. Optionally focus 'node' within widget. // tags: // protected if(widget){ if(this.focusedChild && widget !== this.focusedChild){ this._onChildBlur(this.focusedChild); } this.focusedChild = widget; if(node && widget.focusFocalNode){ widget.focusFocalNode(node); }else{ widget.focus(); } } }, _startupChild: function(/*Widget*/ widget){ // summary: // Set tabindex="-1" on focusable widgets so that we // can focus them programmatically and by clicking. // Connect focus and blur handlers. // tags: // private if(widget.getFocalNodes){ dojo.forEach(widget.getFocalNodes(), function(node){ dojo.attr(node, "tabindex", -1); this._connectNode(node); }, this); }else{ var node = widget.focusNode || widget.domNode; if(widget.isFocusable()){ dojo.attr(node, "tabindex", -1); } this._connectNode(node); } }, _connectNode: function(/*Element*/ node){ // summary: // Monitor focus and blur events on the node // tags: // private this.connect(node, "onfocus", "_onNodeFocus"); this.connect(node, "onblur", "_onNodeBlur"); }, _onContainerFocus: function(evt){ // summary: // Handler for when the container gets focus // description: // Initially the container itself has a tabIndex, but when it gets // focus, switch focus to first child... // tags: // private // Note that we can't use _onFocus() because switching focus from the // _onFocus() handler confuses the focus.js code // (because it causes _onFocusNode() to be called recursively) // focus bubbles on Firefox, // so just make sure that focus has really gone to the container if(evt.target !== this.domNode){ return; } this.focusFirstChild(); // and then remove the container's tabIndex, // so that tab or shift-tab will go to the fields after/before // the container, rather than the container itself dojo.removeAttr(this.domNode, "tabIndex"); }, _onBlur: function(evt){ // When focus is moved away the container, and it's descendant (popup) widgets, // then restore the container's tabIndex so that user can tab to it again. // Note that using _onBlur() so that this doesn't happen when focus is shifted // to one of my child widgets (typically a popup) if(this.tabIndex){ dojo.attr(this.domNode, "tabindex", this.tabIndex); } // TODO: this.inherited(arguments); }, _onContainerKeypress: function(evt){ // summary: // When a key is pressed, if it's an arrow key etc. then // it's handled here. // tags: // private if(evt.ctrlKey || evt.altKey){ return; } var func = this._keyNavCodes[evt.charOrCode]; if(func){ func(); dojo.stopEvent(evt); } }, _onNodeFocus: function(evt){ // summary: // Handler for onfocus event on a child node // tags: // private // record the child that has been focused var widget = dijit.getEnclosingWidget(evt.target); if(widget && widget.isFocusable()){ this.focusedChild = widget; } dojo.stopEvent(evt); }, _onNodeBlur: function(evt){ // summary: // Handler for onblur event on a child node // tags: // private dojo.stopEvent(evt); }, _onChildBlur: function(/*Widget*/ widget){ // summary: // Called when focus leaves a child widget to go // to a sibling widget. // tags: // protected }, _getFirstFocusableChild: function(){ // summary: // Returns first child that can be focused return this._getNextFocusableChild(null, 1); }, _getNextFocusableChild: function(child, dir){ // summary: // Returns the next or previous focusable child, compared // to "child" // child: Widget // The current widget // dir: Integer // * 1 = after // * -1 = before if(child){ child = this._getSiblingOfChild(child, dir); } var children = this.getChildren(); for(var i=0; i < children.length; i++){ if(!child){ child = children[(dir>0) ? 0 : (children.length-1)]; } if(child.isFocusable()){ return child; } child = this._getSiblingOfChild(child, dir); } // no focusable child found return null; } } );