define("dijit/_KeyNavContainer", [ "dojo/_base/kernel", // kernel.deprecated "./_Container", "./_FocusMixin", "dojo/_base/array", // array.forEach "dojo/keys", // keys.END keys.HOME "dojo/_base/declare", // declare "dojo/_base/event", // event.stop "dojo/dom-attr", // domAttr.set "dojo/_base/lang" // lang.hitch ], function(kernel, _Container, _FocusMixin, array, keys, declare, event, domAttr, lang){ // module: // dijit/_KeyNavContainer return declare("dijit._KeyNavContainer", [_FocusMixin, _Container], { // summary: // A _Container with keyboard navigation of its children. // description: // To use this mixin, call connectKeyNavHandlers() in // postCreate(). // 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: String // 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", connectKeyNavHandlers: function(/*keys[]*/ prevKeyCodes, /*keys[]*/ nextKeyCodes){ // summary: // Call in postCreate() to attach the keyboard handlers // to the container. // preKeyCodes: keys[] // Key codes for navigating to the previous child. // nextKeyCodes: keys[] // Key codes for navigating to the next child. // tags: // protected // TODO: call this automatically from my own postCreate() var keyCodes = (this._keyNavCodes = {}); var prev = lang.hitch(this, "focusPrev"); var next = lang.hitch(this, "focusNext"); array.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; }); array.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; }); keyCodes[keys.HOME] = lang.hitch(this, "focusFirstChild"); keyCodes[keys.END] = lang.hitch(this, "focusLastChild"); this.connect(this.domNode, "onkeypress", "_onContainerKeypress"); this.connect(this.domNode, "onfocus", "_onContainerFocus"); }, startupKeyNavChildren: function(){ kernel.deprecated("startupKeyNavChildren() call no longer needed", "", "2.0"); }, startup: function(){ this.inherited(arguments); array.forEach(this.getChildren(), lang.hitch(this, "_startupChild")); }, addChild: function(/*dijit/_WidgetBase*/ widget, /*int?*/ insertIndex){ this.inherited(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()); }, focusLastChild: function(){ // summary: // Focus the last focusable child in the container. // tags: // protected this.focusChild(this._getLastFocusableChild()); }, focusNext: function(){ // summary: // Focus the next widget // tags: // protected this.focusChild(this._getNextFocusableChild(this.focusedChild, 1)); }, focusPrev: function(){ // summary: // Focus the last focusable node in the previous widget // (ex: go to the ComboButton icon section rather than button section) // tags: // protected this.focusChild(this._getNextFocusableChild(this.focusedChild, -1), true); }, focusChild: function(/*dijit/_WidgetBase*/ widget, /*Boolean*/ last){ // summary: // Focus specified child widget. // widget: // Reference to container's child widget // last: // If true and if widget has multiple focusable nodes, focus the // last one instead of the first one // tags: // protected if(!widget){ return; } if(this.focusedChild && widget !== this.focusedChild){ this._onChildBlur(this.focusedChild); // used by _MenuBase } widget.set("tabIndex", this.tabIndex); // for IE focus outline to appear, must set tabIndex before focs widget.focus(last ? "end" : "start"); this._set("focusedChild", widget); }, _startupChild: function(/*dijit/_WidgetBase*/ widget){ // summary: // Setup for each child widget // description: // Sets tabIndex=-1 on each child, so that the tab key will // leave the container rather than visiting each child. // tags: // private widget.set("tabIndex", "-1"); this.connect(widget, "_onFocus", function(){ // Set valid tabIndex so tabbing away from widget goes to right place, see #10272 widget.set("tabIndex", this.tabIndex); }); this.connect(widget, "_onBlur", function(){ widget.set("tabIndex", "-1"); }); }, _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) // Also, _onFocus() would fire when focus went directly to a child widget due to mouse click. // Ignore spurious focus events: // 1. focus on a child widget bubbles on FF // 2. on IE, clicking the scrollbar of a select dropdown moves focus from the focused child item to me if(evt.target !== this.domNode || this.focusedChild){ return; } this.focusFirstChild(); // and then set the container's tabIndex to -1, // (don't remove as that breaks Safari 4) // so that tab or shift-tab will go to the fields after/before // the container, rather than the container itself domAttr.set(this.domNode, "tabIndex", "-1"); }, _onBlur: function(evt){ // When focus is moved away the container, and its 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){ domAttr.set(this.domNode, "tabIndex", this.tabIndex); } this.focusedChild = null; 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(); event.stop(evt); } }, _onChildBlur: function(/*dijit/_WidgetBase*/ /*===== widget =====*/){ // summary: // Called when focus leaves a child widget to go // to a sibling widget. // Used by MenuBase.js (TODO: move code there) // tags: // protected }, _getFirstFocusableChild: function(){ // summary: // Returns first child that can be focused return this._getNextFocusableChild(null, 1); // dijit/_WidgetBase }, _getLastFocusableChild: function(){ // summary: // Returns last child that can be focused return this._getNextFocusableChild(null, -1); // dijit/_WidgetBase }, _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; // dijit/_WidgetBase } child = this._getSiblingOfChild(child, dir); } // no focusable child found return null; // dijit/_WidgetBase } }); });