# Yahoo! UI Library

### node-focusmanager 3.0.0

Yahoo! UI Library > node-focusmanager > node-focusmanager.js (source view)
Search:
Filters
Show Private Show Protected Show Deprecated

    /**
    * <p>The Focus Manager Node Plugin makes it easy to manage focus among
    * a Node's descendants.  Primarily intended to help with widget development,
    * the Focus Manager Node Plugin can be used to improve the keyboard
    * accessibility of widgets.</p>
    *
    * <p>
    * When designing widgets that manage a set of descendant controls (i.e. buttons
    * in a toolbar, tabs in a tablist, menuitems in a menu, etc.) it is important to
    * limit the number of descendants in the browser's default tab flow.  The fewer
    * number of descendants in the default tab flow, the easier it is for keyboard
    * users to navigate between widgets by pressing the tab key.  When a widget has
    * focus it should provide a set of shortcut keys (typically the arrow keys)
    * to move focus among its descendants.
    * </p>
    *
    * <p>
    * To this end, the Focus Manager Node Plugin makes it easy to define a Node's
    * focusable descendants, define which descendant should be in the default tab
    * flow, and define the keys that move focus among each descendant.
    * Additionally, as the CSS
    * <a href="http://www.w3.org/TR/CSS21/selector.html#x38"><code>:focus</code></a>
    * pseudo class is not supported on all elements in all
    * <a href="http://developer.yahoo.com/yui/articles/gbs/">A-Grade browsers</a>,
    * the Focus Manager Node Plugin provides an easy, cross-browser means of
    * styling focus.
    * </p>
    *
    * @module node-focusmanager
    */

    	//	Frequently used strings

    var ACTIVE_DESCENDANT = "activeDescendant",
    	ID = "id",
    	DISABLED = "disabled",
    	TAB_INDEX = "tabIndex",
    	FOCUSED = "focused",
    	FOCUS_CLASS = "focusClass",
    	CIRCULAR = "circular",
    	UI = "UI",
    	KEY = "key",
    	ACTIVE_DESCENDANT_CHANGE = ACTIVE_DESCENDANT + "Change",
    	HOST = "host",

    	//	Collection of keys that, when pressed, cause the browser viewport
    	//	to scroll.
    	scrollKeys = {
    		37: true,
    		38: true,
    		39: true,
    		40: true
    	},

    	clickableElements = {
    		"a": true,
    		"button": true,
    		"input": true,
    		"object": true
    	},

    	//	Library shortcuts

    	Lang = Y.Lang,
     	UA = Y.UA,

    	/**
    	* The NodeFocusManager class is a plugin for a Node instance.  The class is used
    	* via the <a href="Node.html#method_plug"><code>plug</code></a> method of Node
    	* and should not be instantiated directly.
    	* @namespace plugin
    	* @class NodeFocusManager
    	*/
    	NodeFocusManager = function () {

    		NodeFocusManager.superclass.constructor.apply(this, arguments);

    	};

    NodeFocusManager.ATTRS = {

    	/**
    	* Boolean indicating that one of the descendants is focused.
    	*
    	* @attribute focused
    	* @readOnly
    	* @default false
    	* @type boolean
    	*/
    	focused: {

    		value: false,
    		readOnly: true

    	},

    	/**
    	* String representing the CSS selector used to define the descendant Nodes
    	* whose focus should be managed.
    	*
    	* @attribute descendants
    	* @type Y.NodeList
    	*/
    	descendants: {

    		getter: function (value) {

    			return this.get(HOST).all(value);

    		}

    	},

    	/**
    	* <p>Node, or index of the Node, representing the descendant that is either
    	* focused or is focusable (<code>tabIndex</code> attribute is set to 0).
    	* The value cannot represent a disabled descendant Node.  Use a value of -1
    	* to remove all descendant Nodes from the default tab flow.
    	* If no value is specified, the active descendant will be inferred using
    	* the following criteria:</p>
    	* <ol>
    	* <li>Examining the <code>tabIndex</code> attribute of each descendant and
    	* using the first descendant whose <code>tabIndex</code> attribute is set
    	* to 0</li>
    	* <li>If no default can be inferred then the value is set to either 0 or
    	* the index of the first enabled descendant.</li>
    	* </ol>
    	*
    	* @attribute activeDescendant
    	* @type Number
    	*/
    	activeDescendant: {

    		setter: function (value) {

    			var isNumber = Lang.isNumber,
    				INVALID_VALUE = Y.Attribute.INVALID_VALUE,
    				descendantsMap = this._descendantsMap,
    				descendants = this._descendants,
    				nodeIndex,
    				returnValue,
    				oNode;

    			if (isNumber(value)) {
    				nodeIndex = value;
    				returnValue = nodeIndex;
    			}
    			else if ((value instanceof Y.Node) && descendantsMap) {

    				nodeIndex = descendantsMap[value.get(ID)];

    				if (isNumber(nodeIndex)) {
    					returnValue = nodeIndex;
    				}
    				else {

    					//	The user passed a reference to a Node that wasn't one
    					//	of the descendants.
    					returnValue = INVALID_VALUE;

    				}

    			}
    			else {
    				returnValue = INVALID_VALUE;
    			}

    			if (descendants) {

    				oNode = descendants.item(nodeIndex);

    				if (oNode && oNode.get("disabled")) {

    					//	Setting the "activeDescendant" attribute to the index
    					//	of a disabled descendant is invalid.
    					returnValue = INVALID_VALUE;

    				}

    			}

    			return returnValue;

    		}

    	},

    	/**
    	* Object literal representing the keys to be used to navigate between the
    	* next/previous descendant.  The format for the attribute's value is
    	* <code>{ next: "down:40", previous: "down:38" }</code>.  The value for the
    	* "next" and "previous" properties are used to attach
    	* <a href="event/#keylistener"><code>key</code></a> event listeners. See
    	* the <a href="event/#keylistener">Using the key Event</a> section of
    	* the Event documentation for more information on "key" event listeners.
    	*
    	* @attribute keys
    	* @type Object
    	*/
    	keys: {

    		value: {

    			next: null,
    			previous: null

    		}

    	},

    	/**
    	* String representing the name of class applied to the focused active
    	* descendant Node.  Can also be an object literal used to define both the
    	* class name, and the Node to which the class should be applied.  If using
    	* an object literal, the format is:
    	* <code>{ className: "focus", fn: myFunction }</code>.  The function
    	* referenced by the <code>fn</code> property in the object literal will be
    	* passed a reference to the currently focused active descendant Node.
    	*
    	* @attribute focusClass
    	* @type String|Object
    	*/
    	focusClass: { },

    	/**
    	* Boolean indicating if focus should be set to the first/last descendant
    	* when the end or beginning of the descendants has been reached.
    	*
    	* @attribute circular
    	* @type Boolean
    	*/
    	circular: {
    		value: true
    	}

    };

    Y.extend(NodeFocusManager, Y.Plugin.Base, {

    	//	Protected properties

    	//	Boolean indicating if the NodeFocusManager is active.
    	_stopped: true,

    	//	NodeList representing the descendants selected via the
    	//	"descendants" attribute.
    	_descendants: null,

    	//	Object literal mapping the IDs of each descendant to its index in the
    	//	"_descendants" NodeList.
    	_descendantsMap: null,

    	//	Reference to the Node instance to which the focused class (defined
    	//	by the "focusClass" attribute) is currently applied.
    	_focusedNode: null,

    	//	Number representing the index of the last descendant Node.
    	_lastNodeIndex: 0,

    	//	Array of handles for event handlers used for a NodeFocusManager instance.
    	_eventHandlers: null,

    	//	Protected methods

    	/**
    	* @method _initDescendants
    	* @description Sets the <code>tabIndex</code> attribute of all of the
    	* descendants to -1, except the active descendant, whose
    	* <code>tabIndex</code> attribute is set to 0.
    	* @protected
    	*/
    	_initDescendants: function () {

    		var descendants = this.get("descendants"),
    			descendantsMap = {},
    			nFirstEnabled = -1,
    			nDescendants,
    			nActiveDescendant = this.get(ACTIVE_DESCENDANT),
    			oNode,
    			sID,
    			i = 0;

    		if (Lang.isUndefined(nActiveDescendant)) {
    			nActiveDescendant = -1;
    		}

    		if (descendants) {

    			nDescendants = descendants.size();

    			if (nDescendants > 1) {

    				for (i = 0; i < nDescendants; i++) {

    					oNode = descendants.item(i);

    					if (nFirstEnabled === -1 && !oNode.get(DISABLED)) {
    						nFirstEnabled = i;
    					}

    					//	If the user didn't specify a value for the
    					//	"activeDescendant" attribute try to infer it from
    					//	the markup.

    					//	Need to pass "2" when using "getAttribute" for IE to get
    					//	the attribute value as it is set in the markup.
    					//	Need to use "parseInt" because IE always returns the
    					//	value as a number, whereas all other browsers return
    					//	the attribute as a string when accessed
    					//	via "getAttribute".

    					if (nActiveDescendant < 0 &&
    							parseInt(oNode.getAttribute(TAB_INDEX, 2), 10) === 0) {

    						nActiveDescendant = i;

    					}

    					oNode.set(TAB_INDEX, -1);

    					sID = oNode.get(ID);

    					if (!sID) {
    						sID = Y.guid();
    						oNode.set(ID, sID);
    					}

    					descendantsMap[sID] = i;

    				}

    				//	If the user didn't specify a value for the
    				//	"activeDescendant" attribute and no default value could be
    				//	determined from the markup, then default to 0.

    				if (nActiveDescendant < 0) {
    					nActiveDescendant = 0;
    				}

    				oNode = descendants.item(nActiveDescendant);

    				//	Check to make sure the active descendant isn't disabled,
    				//	and fall back to the first enabled descendant if it is.

    				if (!oNode || oNode.get(DISABLED)) {
    					oNode = descendants.item(nFirstEnabled);
    					nActiveDescendant = nFirstEnabled;
    				}

    				this._lastNodeIndex = nDescendants - 1;
    				this._descendants = descendants;
    				this._descendantsMap = descendantsMap;

    				this.set(ACTIVE_DESCENDANT, nActiveDescendant);

    				//	Need to set the "tabIndex" attribute here, since the
    				//	"activeDescendantChange" event handler used to manage
    				//	the setting of the "tabIndex" attribute isn't wired up yet.

    				oNode.set(TAB_INDEX, 0);

    			}

    		}

    	},

    	/**
    	* @method _isDescendant
    	* @description Determines if the specified Node instance is a descendant
    	* managed by the Focus Manager.
    	* @param node {Node} Node instance to be checked.
    	* @return {Boolean} Boolean indicating if the specified Node instance is a
    	* descendant managed by the Focus Manager.
    	* @protected
    	*/
    	_isDescendant: function (node) {

    		return (node.get(ID) in this._descendantsMap);

    	},

    	/**
    	* @method _removeFocusClass
    	* @description Removes the class name representing focus (as specified by
    	* the "focusClass" attribute) from the Node instance to which it is
    	* currently applied.
    	* @protected
    	*/
    	_removeFocusClass: function () {

    		var oFocusedNode = this._focusedNode,
    			focusClass = this.get(FOCUS_CLASS),
    			sClassName;

    		if (focusClass) {
    			sClassName = Lang.isString(focusClass) ?
    				focusClass : focusClass.className;
    		}

    		if (oFocusedNode && sClassName) {
    			oFocusedNode.removeClass(sClassName);
    		}

    	},

    	/**
    	* @method _detachKeyHandler
    	* @description Detaches the "key" event handlers used to support the "keys"
    	* attribute.
    	* @protected
    	*/
    	_detachKeyHandler: function () {

    		var prevKeyHandler = this._prevKeyHandler,
    			nextKeyHandler = this._nextKeyHandler;

    		if (prevKeyHandler) {
    			prevKeyHandler.detach();
    		}

    		if (nextKeyHandler) {
    			nextKeyHandler.detach();
    		}

    	},

    	/**
    	* @method _preventScroll
    	* @description Prevents the viewport from scolling when the user presses
    	* the up, down, left, or right key.
    	* @protected
    	*/
    	_preventScroll: function (event) {

    		if (scrollKeys[event.keyCode]) {
    			event.preventDefault();
    		}

    	},

    	/**
    	* @method _preventScroll
    	* @description Fires the click event if the enter key is pressed while
    	* focused on an HTML element that is not natively clickable.
    	* @protected
    	*/
    	_fireClick: function (event) {

    		var oTarget = event.target,
    			sNodeName = oTarget.get("nodeName").toLowerCase();

    		if (event.keyCode === 13 && (!clickableElements[sNodeName] ||
    				(sNodeName === "a" && !oTarget.getAttribute("href")))) {

    			Y.log(("Firing click event for node:" + oTarget.get("id")), "info", "nodeFocusManager");

    			oTarget.simulate("click");

    		}

    	},

    	/**
    	* @method _attachKeyHandler
    	* @description Attaches the "key" event handlers used to support the "keys"
    	* attribute.
    	* @protected
    	*/
    	_attachKeyHandler: function () {

    		this._detachKeyHandler();

    		var sNextKey = this.get("keys.next"),
    			sPrevKey = this.get("keys.previous"),
    			oNode = this.get(HOST),
    			aHandlers = this._eventHandlers;

    		if (sPrevKey) {
     			this._prevKeyHandler =
    				Y.on(KEY, Y.bind(this._focusPrevious, this), oNode, sPrevKey);
    		}

    		if (sNextKey) {
     			this._nextKeyHandler =
    				Y.on(KEY, Y.bind(this._focusNext, this), oNode, sNextKey);
    		}

    		//	In Opera it is necessary to call the "preventDefault" method in
    		//	response to the user pressing the arrow keys in order to prevent
    		//	the viewport from scrolling when the user is moving focus among
    		//	the focusable descendants.

    		if (UA.opera) {
    			aHandlers.push(oNode.on("keypress", this._preventScroll, this));
    		}

    		//	For all browsers except Opera: HTML elements that are not natively
    		//	focusable but made focusable via the tabIndex attribute don't
    		//	fire a click event when the user presses the enter key.  It is
    		//	possible to work around this problem by simplying dispatching a
    		//	click event in response to the user pressing the enter key.

    		if (!UA.opera) {
    			aHandlers.push(oNode.on("keypress", this._fireClick, this));
    		}

    	},

    	/**
    	* @method _detachEventHandlers
    	* @description Detaches all event handlers used by the Focus Manager.
    	* @protected
    	*/
    	_detachEventHandlers: function () {

    		this._detachKeyHandler();

    		var aHandlers = this._eventHandlers;

    		if (aHandlers) {

    			Y.Array.each(aHandlers, function (handle) {
    				handle.detach();
    			});

    			this._eventHandlers = null;

    		}

    	},

    	/**
    	* @method _detachEventHandlers
    	* @description Attaches all event handlers used by the Focus Manager.
    	* @protected
    	*/
    	_attachEventHandlers: function () {

    		var descendants = this._descendants,
    			aHandlers,
    			oDocument,
    			handle;

    		if (descendants && descendants.size() > 1) {

    			aHandlers = this._eventHandlers || [];
    			oDocument = this.get(HOST).get("ownerDocument");

    			if (aHandlers.length === 0) {

    		        Y.log("Attaching base set of event handlers.", "info", "nodeFocusManager");

    				aHandlers.push(oDocument.on("focus", this._onDocFocus, this));

    				aHandlers.push(oDocument.on("mousedown",
    					this._onDocMouseDown, this));

    				aHandlers.push(
    						this.after("keysChange", this._attachKeyHandler));

    				aHandlers.push(
    						this.after("descendantsChange", this._initDescendants));

    				aHandlers.push(
    						this.after(ACTIVE_DESCENDANT_CHANGE,
    								this._afterActiveDescendantChange));

    				//	For performance: defer attaching all key-related event
    				//	handlers until the first time one of the specified
    				//	descendants receives focus.

    				handle = this.after("focusedChange", Y.bind(function (event) {

    					if (event.newVal) {

    				        Y.log("Attaching key event handlers.", "info", "nodeFocusManager");

    						this._attachKeyHandler();

    						//	Detach this "focusedChange" handler so that the
    						//	key-related handlers only get attached once.

    						handle.detach();

    					}

    				}, this));

    				aHandlers.push(handle);

    			}

    			this._eventHandlers = aHandlers;

    		}

    	},

    	//	Protected event handlers

    	/**
    	* @method _onDocMouseDown
    	* @description "mousedown" event handler for the owner document of the
    	* Focus Manager's Node.
    	* @protected
    	* @param event {Object} Object representing the DOM event.
    	*/
    	_onDocMouseDown: function (event) {

    		var oHost = this.get(HOST),
    			oTarget = event.target,
    			bChildNode = oHost.contains(oTarget),
    			node,

    			getFocusable = function (node) {

    				var returnVal = false;

    				if (!node.compareTo(oHost)) {

    					returnVal = this._isDescendant(node) ? node :
    									getFocusable.call(this, node.get("parentNode"));

    				}

    				return returnVal;

    			};

    		if (bChildNode) {

    			//	Check to make sure that the target isn't a child node of one
    			//	of the focusable descendants.

    			node = getFocusable.call(this, oTarget);

    			if (node) {
    				oTarget = node;
    			}
    			else if (!node && this.get(FOCUSED)) {

    				//	The target was a non-focusable descendant of the root
    				//	node, so the "focused" attribute should be set to false.

    	 			this._set(FOCUSED, false);
    	 			this._onDocFocus(event);

    			}

    		}

    		if (bChildNode && this._isDescendant(oTarget)) {

    			//	Fix general problem in Webkit: mousing down on a button or an
    			//	anchor element doesn't focus it.

    			//	For all browsers: makes sure that the descendant that
    			//	was the target of the mousedown event is now considered the
    			//	active descendant.

    			this.focus(oTarget);
    		}
    		else if (UA.webkit && this.get(FOCUSED) &&
    			(!bChildNode || (bChildNode && !this._isDescendant(oTarget)))) {

    			//	Fix for Webkit:

    			//	Document doesn't receive focus in Webkit when the user mouses
    			//	down on it, so the "focused" attribute won't get set to the
    			//	correct value.

    			//	The goal is to force a blur if the user moused down on
    			//	either: 1) A descendant node, but not one that managed by
    			//	the FocusManager, or 2) an element outside of the
    			//	FocusManager

     			this._set(FOCUSED, false);
     			this._onDocFocus(event);

    		}

    	},

    	/**
    	* @method _onDocFocus
    	* @description "focus" event handler for the owner document of the
    	* Focus Manager's Node.
    	* @protected
    	* @param event {Object} Object representing the DOM event.
    	*/
    	_onDocFocus: function (event) {

    		var oTarget = this._focusTarget || event.target,
    			bFocused = this.get(FOCUSED),
    			focusClass = this.get(FOCUS_CLASS),
    			oFocusedNode = this._focusedNode,
    			bInCollection;

    		if (this._focusTarget) {
    			this._focusTarget = null;
    		}

    		if (this.get(HOST).contains(oTarget)) {

    			//	The target is a descendant of the root Node.

    			bInCollection = this._isDescendant(oTarget);

    			if (!bFocused && bInCollection) {

    				//	The user has focused a focusable descendant.

    				bFocused = true;

    			}
    			else if (bFocused && !bInCollection) {

    				//	The user has focused a child of the root Node that is
    				//	not one of the descendants managed by this Focus Manager
    				//	so clear the currently focused descendant.

    				bFocused = false;

    			}

    		}
    		else {

    			// The target is some other node in the document.

    			bFocused = false;

    		}

    		if (focusClass) {

    			if (oFocusedNode && (!oFocusedNode.compareTo(oTarget) || !bFocused)) {
    				this._removeFocusClass();
    			}

    			if (bInCollection && bFocused) {

    				if (focusClass.fn) {
    					oTarget = focusClass.fn(oTarget);
    					oTarget.addClass(focusClass.className);
    				}
    				else {
    					oTarget.addClass(focusClass);
    				}

    				this._focusedNode = oTarget;

    			}

    		}

    		this._set(FOCUSED, bFocused);

    	},

    	/**
    	* @method _focusNext
    	* @description Keydown event handler that moves focus to the next
    	* enabled descendant.
    	* @protected
    	* @param event {Object} Object representing the DOM event.
    	* @param activeDescendant {Number} Number representing the index of the
    	* next descendant to be focused
    	*/
    	_focusNext: function (event, activeDescendant) {

    		var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
    			oNode;

    		if (this._isDescendant(event.target) &&
    			(nActiveDescendant <= this._lastNodeIndex)) {

    			nActiveDescendant = nActiveDescendant + 1;

    			if (nActiveDescendant === (this._lastNodeIndex + 1) &&
    				this.get(CIRCULAR)) {

    				nActiveDescendant = 0;

    			}

    			oNode = this._descendants.item(nActiveDescendant);

    			if (oNode.get(DISABLED)) {
    				this._focusNext(event, nActiveDescendant);
    			}
    			else {
    				this.focus(nActiveDescendant);
    			}

    		}

    		this._preventScroll(event);

    	},

    	/**
    	* @method _focusPrevious
    	* @description Keydown event handler that moves focus to the previous
    	* enabled descendant.
    	* @protected
    	* @param event {Object} Object representing the DOM event.
    	* @param activeDescendant {Number} Number representing the index of the
    	* next descendant to be focused.
    	*/
    	_focusPrevious: function (event, activeDescendant) {

    		var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
    			oNode;

    		if (this._isDescendant(event.target) && nActiveDescendant >= 0) {

    			nActiveDescendant = nActiveDescendant - 1;

    			if (nActiveDescendant === -1 && this.get(CIRCULAR)) {
    				nActiveDescendant = this._lastNodeIndex;
    			}

    			oNode = this._descendants.item(nActiveDescendant);

    			if (oNode.get(DISABLED)) {
    				this._focusPrevious(event, nActiveDescendant);
    			}
    			else {
    				this.focus(nActiveDescendant);
    			}

    		}

    		this._preventScroll(event);

    	},

    	/**
    	* @method _afterActiveDescendantChange
    	* @description afterChange event handler for the
    	* "activeDescendant" attribute.
    	* @protected
    	* @param event {Object} Object representing the change event.
    	*/
    	_afterActiveDescendantChange: function (event) {

    		var oNode = this._descendants.item(event.prevVal);

    		if (oNode) {
    			oNode.set(TAB_INDEX, -1);
    		}

    		oNode = this._descendants.item(event.newVal);

    		if (oNode) {
    			oNode.set(TAB_INDEX, 0);
    		}

    	},

    	//	Public methods

        initializer: function (config) {

    		this.start();

        },

    	destructor: function () {

    		this.stop();
    		this.get(HOST).focusManager = null;

        },

    	/**
    	* @method focus
    	* @description Focuses the active descendant and sets the
    	* <code>focused</code> attribute to true.
    	* @param index {Number} Optional. Number representing the index of the
    	* descendant to be set as the active descendant.
    	* @param index {Node} Optional. Node instance representing the
    	* descendant to be set as the active descendant.
    	*/
    	focus: function (index) {

    		if (Lang.isUndefined(index)) {
    			index = this.get(ACTIVE_DESCENDANT);
    		}

    		this.set(ACTIVE_DESCENDANT, index, { src: UI });

    		var oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));

    		if (oNode) {

    			oNode.focus();

    			//	In Opera focusing a <BUTTON> element programmatically
    			//	will result in the document-level focus event handler
    			//	"_onDocFocus" being called, resulting in the handler
    			//	incorrectly setting the "focused" Attribute to false.  To fix
    			//	this, set a flag ("_focusTarget") that the "_onDocFocus" method
    			//	can look for to properly handle this edge case.

    			if (UA.opera && oNode.get("nodeName").toLowerCase() === "button") {
    				this._focusTarget = oNode;
    			}

    		}

    	},

    	/**
    	* @method blur
    	* @description Blurs the current active descendant and sets the
    	* <code>focused</code> attribute to false.
    	*/
    	blur: function () {

    		var oNode;

    		if (this.get(FOCUSED)) {

    			oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));

    			if (oNode) {

    				oNode.blur();

    				//	For Opera and Webkit:  Blurring an element in either browser
    				//	doesn't result in another element (such as the document)
    				//	being focused.  Therefore, the "_onDocFocus" method
    				//	responsible for managing the application and removal of the
    				//	focus indicator class name is never called.

    				this._removeFocusClass();

    			}

    			this._set(FOCUSED, false, { src: UI });
    		}

    	},

    	/**
    	* @method start
    	* @description Enables the Focus Manager.
    	*/
    	start: function () {

    		if (this._stopped) {

    			this._initDescendants();
    			this._attachEventHandlers();

    			this._stopped = false;

    		}

    	},

    	/**
    	* @method stop
    	* @description Disables the Focus Manager by detaching all event handlers.
    	*/
    	stop: function () {

    		if (!this._stopped) {

    			this._detachEventHandlers();

    			this._descendants = null;
    			this._focusedNode = null;
    			this._lastNodeIndex = 0;
    			this._stopped = true;

    		}

    	},

    	/**
    	* @method refresh
    	* @description Refreshes the Focus Manager's descendants by re-executing the
    	* CSS selector query specified by the <code>descendants</code> attribute.
    	*/
    	refresh: function () {

    		this._initDescendants();

    	}

    });

    NodeFocusManager.NAME = "nodeFocusManager";
    NodeFocusManager.NS = "focusManager";

    Y.namespace("Plugin");
    Y.Plugin.NodeFocusManager = NodeFocusManager;

Copyright © 2009 Yahoo! Inc. All rights reserved.