258 lines
7.8 KiB
JavaScript
258 lines
7.8 KiB
JavaScript
|
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
|
||
|
}
|
||
|
});
|
||
|
});
|