The TabKeyManagedWidget was originally a widget I wrote in YUI 2 for the popups on Mint.com. I have changed it a lot sense then, especially in its conversion to YUI 3. The problem we solved, is that sometimes, especially when using in-page popups, one must prevent users from being able to tab through the entire page. So we bound the end-user to a tabbing context, such as a popup or form, preventing the browser from executing its default behavior.
Once this widget is instantiated, it will listen for end-user 'click' and 'keydown:tab' events on the document. When a 'click' event fires, it evaluates if the event target is a descendant of any of the instantiated TabKeyManagedWidget nodes. If so, then 'keydown:tab' will be bound to fields found inside of that element. Otherwise, 'keydown:tab' will behave normally, as defined by the browser/OS.
In YUI 3 TabKeyManagedWidget extends the "Y.Base" and therefore leverages the 'set'/'get' methods and 'ATTR' object.
Example 1: TabKeyManagedWidget Constants
// the name of the widget, used by Y.Node
_F.NAME = "tabKeyManagedWidget";
// the default attributes of widget, used by Y.Node
_F.ATTRS = {
// When active this widget uses document clicking to set the form for tabbing.
autoFocus: {
value: true
},
// A collection of current fields for tabbing.
fieldsForTabbing: {
value: []
},
// The HTML className to apply to elements when focused.
focusClass: {
value: 'focus'
},
// The node to bind this widget.
node: {
setter: function(node) {
var n = Node.get(node);
if (!n) {
Y.fail('TabKeyManagedWidget: Invalid Node Given: ' + node);
}
return n;
}
},
// The selector string to find elements for tabbing.
tabElements: {
value: 'a, textarea, input, select'
// value: 'input'
},
// The current index in the collection of tabbing fields.
tabIndex: {
value: 0
},
// The waits to focus on a tab event until the previous BLUR event fires.
waitForBlur: {
value: true
}
};
// the event that fires before tabbing, if the callback function returns false, tabbing does not happen
_F.CE_BEFORE_TAB = 'beforeTab';
// the event that fires after tabbing
_F.CE_AFTER_TAB = 'afterTab';
There are a lot of properties for this widget: autoFocus, fieldsForTabbing, focusClass, node, tabElements, tabIndex, waitForBlur. These properties can be overridden by the configuration object passed into the constructor, or by using 'set' method of an instantiated widget. The 'autoFocus' property is used when tabbing should be bound to the fields inside of 'node' anytime a 'click' event occurs somewhere inside of that 'node' (this is on by default). Otherwise, the developer will need to manage binding by calling the public 'toggleBinding' method on a TabKeyManagedWidget instance. The 'fieldsForTabbing' and 'tabIndex' are internal properties used by the widget to know what fields should be tabbed through and the current field index. The 'focusClass' is a CSS class to be applied automatically to a element when focused on. The 'node' is the root node of all the fields that should be managed; this should always be provided to the constructor. The 'tabElements' is a comma separated list of all HTML tags that should be included for tabbing; by default this is 'a', 'textarea', 'input', and 'select' elements. The 'waitForBlur' property will wait for the 'blur' event of the previously focused element to fire before focusing on the next element; this is useful if you have other listeners attached to an element that are not managed by this widget. There are two custom events that will fire before and after tabbing. If the callback function of 'CE_BEFORE_TAB' returns false, then the tabbing is canceled.
The "Y.Node" object that TabKeyManagedWidget inherits from, provides some syntactic sugar by calling the 'initializer' method during instantiation and 'destructor' methods when deleting. The widget leverages this to fully initialize the 'tabElements' (the tags need to be bound to the form), and to register the widget with the shared widget manager.
Example 2: Initializer and Destructor
destructor: function() {
_sharedObject.destroy(this);
},
initializer: function(conf) {
var tabElements = this.get('tabElements').split(','),
idprefix = '#' + this.get('node').get('id') + ' ';
_sharedObject.init(this);
this.set('tabElements', idprefix + tabElements.join(',' + idprefix));
},