]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/node-focusmanager/node-focusmanager.js
Release 6.2.0beta4
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / node-focusmanager / node-focusmanager.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 3.0.0
6 build: 1549
7 */
8 YUI.add('node-focusmanager', function(Y) {
9
10 /**
11 * <p>The Focus Manager Node Plugin makes it easy to manage focus among  
12 * a Node's descendants.  Primarily intended to help with widget development, 
13 * the Focus Manager Node Plugin can be used to improve the keyboard 
14 * accessibility of widgets.</p>
15
16 * <p>
17 * When designing widgets that manage a set of descendant controls (i.e. buttons
18 * in a toolbar, tabs in a tablist, menuitems in a menu, etc.) it is important to 
19 * limit the number of descendants in the browser's default tab flow.  The fewer 
20 * number of descendants in the default tab flow, the easier it is for keyboard 
21 * users to navigate between widgets by pressing the tab key.  When a widget has 
22 * focus it should provide a set of shortcut keys (typically the arrow keys) 
23 * to move focus among its descendants.
24 * </p>
25
26 * <p>
27 * To this end, the Focus Manager Node Plugin makes it easy to define a Node's 
28 * focusable descendants, define which descendant should be in the default tab 
29 * flow, and define the keys that move focus among each descendant.
30 * Additionally, as the CSS 
31 * <a href="http://www.w3.org/TR/CSS21/selector.html#x38"><code>:focus</code></a> 
32 * pseudo class is not supported on all elements in all 
33 * <a href="http://developer.yahoo.com/yui/articles/gbs/">A-Grade browsers</a>,
34 * the Focus Manager Node Plugin provides an easy, cross-browser means of 
35 * styling focus.
36 * </p>
37 *  
38 * @module node-focusmanager
39 */
40
41         //      Frequently used strings
42
43 var ACTIVE_DESCENDANT = "activeDescendant",
44         ID = "id",
45         DISABLED = "disabled",
46         TAB_INDEX = "tabIndex",
47         FOCUSED = "focused",
48         FOCUS_CLASS = "focusClass",
49         CIRCULAR = "circular",
50         UI = "UI",
51         KEY = "key",
52         ACTIVE_DESCENDANT_CHANGE = ACTIVE_DESCENDANT + "Change",
53         HOST = "host",
54
55         //      Collection of keys that, when pressed, cause the browser viewport
56         //      to scroll.
57         scrollKeys = {
58                 37: true,
59                 38: true,
60                 39: true,
61                 40: true
62         },
63         
64         clickableElements = {
65                 "a": true,
66                 "button": true,
67                 "input": true,
68                 "object": true
69         },      
70
71         //      Library shortcuts
72
73         Lang = Y.Lang,
74         UA = Y.UA,
75
76         /**
77         * The NodeFocusManager class is a plugin for a Node instance.  The class is used 
78         * via the <a href="Node.html#method_plug"><code>plug</code></a> method of Node 
79         * and should not be instantiated directly.
80         * @namespace plugin
81         * @class NodeFocusManager
82         */      
83         NodeFocusManager = function () {
84
85                 NodeFocusManager.superclass.constructor.apply(this, arguments);
86
87         };
88
89
90 NodeFocusManager.ATTRS = {
91
92         /**
93         * Boolean indicating that one of the descendants is focused.
94         *
95         * @attribute focused
96         * @readOnly
97         * @default false
98         * @type boolean
99         */
100         focused: {
101                 
102                 value: false,
103                 readOnly: true
104                 
105         },
106
107
108         /**
109         * String representing the CSS selector used to define the descendant Nodes 
110         * whose focus should be managed.
111         *
112         * @attribute descendants
113         * @type Y.NodeList
114         */
115         descendants: {
116
117                 getter: function (value) {
118
119                         return this.get(HOST).all(value);
120                         
121                 }
122
123         },
124
125
126         /**
127         * <p>Node, or index of the Node, representing the descendant that is either 
128         * focused or is focusable (<code>tabIndex</code> attribute is set to 0).  
129         * The value cannot represent a disabled descendant Node.  Use a value of -1
130         * to remove all descendant Nodes from the default tab flow.
131         * If no value is specified, the active descendant will be inferred using 
132         * the following criteria:</p>
133         * <ol>
134         * <li>Examining the <code>tabIndex</code> attribute of each descendant and 
135         * using the first descendant whose <code>tabIndex</code> attribute is set 
136         * to 0</li>
137         * <li>If no default can be inferred then the value is set to either 0 or 
138         * the index of the first enabled descendant.</li>
139         * </ol>
140         * 
141         * @attribute activeDescendant
142         * @type Number
143         */
144         activeDescendant: {
145
146                 setter: function (value) {
147                         
148                         var isNumber = Lang.isNumber,
149                                 INVALID_VALUE = Y.Attribute.INVALID_VALUE,
150                                 descendantsMap = this._descendantsMap,
151                                 descendants = this._descendants,
152                                 nodeIndex,
153                                 returnValue,
154                                 oNode;
155                         
156
157                         if (isNumber(value)) {
158                                 nodeIndex = value;
159                                 returnValue = nodeIndex;
160                         }
161                         else if ((value instanceof Y.Node) && descendantsMap) {
162
163                                 nodeIndex = descendantsMap[value.get(ID)];
164
165                                 if (isNumber(nodeIndex)) {
166                                         returnValue = nodeIndex;
167                                 }
168                                 else {
169
170                                         //      The user passed a reference to a Node that wasn't one
171                                         //      of the descendants.
172                                         returnValue = INVALID_VALUE;                                    
173
174                                 }
175
176                         }
177                         else {
178                                 returnValue = INVALID_VALUE;
179                         }
180
181
182                         if (descendants) {
183
184                                 oNode = descendants.item(nodeIndex);
185                         
186                                 if (oNode && oNode.get("disabled")) {
187
188                                         //      Setting the "activeDescendant" attribute to the index
189                                         //      of a disabled descendant is invalid.
190                                         returnValue = INVALID_VALUE;
191                                         
192                                 }
193
194                         }
195
196                         return returnValue;
197                         
198                 }               
199
200         },
201
202
203         /**
204         * Object literal representing the keys to be used to navigate between the 
205         * next/previous descendant.  The format for the attribute's value is 
206         * <code>{ next: "down:40", previous: "down:38" }</code>.  The value for the 
207         * "next" and "previous" properties are used to attach 
208         * <a href="event/#keylistener"><code>key</code></a> event listeners. See 
209         * the <a href="event/#keylistener">Using the key Event</a> section of 
210         * the Event documentation for more information on "key" event listeners.
211         * 
212         * @attribute keys
213         * @type Object
214         */
215         keys: {
216         
217                 value: {
218
219                         next: null,
220                         previous: null
221                         
222                 }
223
224                 
225         },
226
227
228         /**
229         * String representing the name of class applied to the focused active  
230         * descendant Node.  Can also be an object literal used to define both the 
231         * class name, and the Node to which the class should be applied.  If using 
232         * an object literal, the format is:
233         * <code>{ className: "focus", fn: myFunction }</code>.  The function 
234         * referenced by the <code>fn</code> property in the object literal will be
235         * passed a reference to the currently focused active descendant Node.
236         * 
237         * @attribute focusClass
238         * @type String|Object
239         */
240         focusClass: { },
241
242
243         /**
244         * Boolean indicating if focus should be set to the first/last descendant 
245         * when the end or beginning of the descendants has been reached.
246         * 
247         * @attribute circular
248         * @type Boolean
249         */
250         circular: {
251                 value: true
252         }
253         
254 };
255
256 Y.extend(NodeFocusManager, Y.Plugin.Base, {
257
258         //      Protected properties
259
260         //      Boolean indicating if the NodeFocusManager is active.
261         _stopped: true,
262
263         //      NodeList representing the descendants selected via the 
264         //      "descendants" attribute.
265         _descendants: null,
266         
267         //      Object literal mapping the IDs of each descendant to its index in the 
268         //      "_descendants" NodeList.
269         _descendantsMap: null,
270
271         //      Reference to the Node instance to which the focused class (defined 
272         //      by the "focusClass" attribute) is currently applied.
273         _focusedNode: null,
274         
275         //      Number representing the index of the last descendant Node.
276         _lastNodeIndex: 0,
277
278         //      Array of handles for event handlers used for a NodeFocusManager instance.
279         _eventHandlers: null,
280
281
282
283         //      Protected methods
284
285         /**
286         * @method _initDescendants
287         * @description Sets the <code>tabIndex</code> attribute of all of the 
288         * descendants to -1, except the active descendant, whose 
289         * <code>tabIndex</code> attribute is set to 0.
290         * @protected
291         */
292         _initDescendants: function () {
293
294                 var descendants = this.get("descendants"),
295                         descendantsMap = {},
296                         nFirstEnabled = -1,
297                         nDescendants,
298                         nActiveDescendant = this.get(ACTIVE_DESCENDANT),
299                         oNode,
300                         sID,
301                         i = 0;
302
303
304
305                 if (Lang.isUndefined(nActiveDescendant)) {
306                         nActiveDescendant = -1;
307                 }
308
309
310                 if (descendants) {
311
312                         nDescendants = descendants.size();
313                         
314
315                         if (nDescendants > 1) {
316
317                                 for (i = 0; i < nDescendants; i++) {
318
319                                         oNode = descendants.item(i);
320
321                                         if (nFirstEnabled === -1 && !oNode.get(DISABLED)) {
322                                                 nFirstEnabled = i;
323                                         }
324
325
326                                         //      If the user didn't specify a value for the 
327                                         //      "activeDescendant" attribute try to infer it from 
328                                         //      the markup.
329
330                                         //      Need to pass "2" when using "getAttribute" for IE to get
331                                         //      the attribute value as it is set in the markup.
332                                         //      Need to use "parseInt" because IE always returns the 
333                                         //      value as a number, whereas all other browsers return
334                                         //      the attribute as a string when accessed 
335                                         //      via "getAttribute".
336
337                                         if (nActiveDescendant < 0 && 
338                                                         parseInt(oNode.getAttribute(TAB_INDEX, 2), 10) === 0) {
339
340                                                 nActiveDescendant = i;
341
342                                         }
343
344                                         oNode.set(TAB_INDEX, -1);
345
346                                         sID = oNode.get(ID);
347
348                                         if (!sID) {
349                                                 sID = Y.guid();
350                                                 oNode.set(ID, sID);
351                                         }
352                                         
353                                         descendantsMap[sID] = i;
354                                         
355                                 }
356                                 
357
358                                 //      If the user didn't specify a value for the  
359                                 //      "activeDescendant" attribute and no default value could be 
360                                 //      determined from the markup, then default to 0.
361                                 
362                                 if (nActiveDescendant < 0) {
363                                         nActiveDescendant = 0;
364                                 }
365                                 
366
367                                 oNode = descendants.item(nActiveDescendant);
368
369                                 //      Check to make sure the active descendant isn't disabled, 
370                                 //      and fall back to the first enabled descendant if it is.
371
372                                 if (!oNode || oNode.get(DISABLED)) {
373                                         oNode = descendants.item(nFirstEnabled);
374                                         nActiveDescendant = nFirstEnabled;
375                                 }
376
377                                 this._lastNodeIndex = nDescendants - 1;
378                                 this._descendants = descendants;
379                                 this._descendantsMap = descendantsMap;
380
381                                 this.set(ACTIVE_DESCENDANT, nActiveDescendant);
382
383                                 //      Need to set the "tabIndex" attribute here, since the 
384                                 //      "activeDescendantChange" event handler used to manage
385                                 //      the setting of the "tabIndex" attribute isn't wired up yet.
386
387                                 oNode.set(TAB_INDEX, 0);
388
389                         }
390                         
391                 }
392
393         },
394
395
396         /**
397         * @method _isDescendant
398         * @description Determines if the specified Node instance is a descendant
399         * managed by the Focus Manager.
400         * @param node {Node} Node instance to be checked.
401         * @return {Boolean} Boolean indicating if the specified Node instance is a 
402         * descendant managed by the Focus Manager.
403         * @protected
404         */
405         _isDescendant: function (node) {
406
407                 return (node.get(ID) in this._descendantsMap);
408                 
409         },
410         
411
412         /**
413         * @method _removeFocusClass
414         * @description Removes the class name representing focus (as specified by 
415         * the "focusClass" attribute) from the Node instance to which it is 
416         * currently applied.
417         * @protected
418         */
419         _removeFocusClass: function () {
420
421                 var oFocusedNode = this._focusedNode,
422                         focusClass = this.get(FOCUS_CLASS),
423                         sClassName;
424
425                 if (focusClass) {
426                         sClassName = Lang.isString(focusClass) ? 
427                                 focusClass : focusClass.className;              
428                 }
429
430                 if (oFocusedNode && sClassName) {
431                         oFocusedNode.removeClass(sClassName);
432                 }
433                 
434         },
435
436
437         /**
438         * @method _detachKeyHandler
439         * @description Detaches the "key" event handlers used to support the "keys"
440         * attribute.
441         * @protected
442         */
443         _detachKeyHandler: function () {
444
445                 var prevKeyHandler = this._prevKeyHandler,
446                         nextKeyHandler = this._nextKeyHandler;
447
448                 if (prevKeyHandler) {
449                         prevKeyHandler.detach();
450                 }
451                 
452                 if (nextKeyHandler) {
453                         nextKeyHandler.detach();
454                 }
455                 
456         },
457
458
459         /**
460         * @method _preventScroll
461         * @description Prevents the viewport from scolling when the user presses 
462         * the up, down, left, or right key.
463         * @protected
464         */
465         _preventScroll: function (event) {
466
467                 if (scrollKeys[event.keyCode]) {
468                         event.preventDefault();
469                 }
470                 
471         },
472
473
474         /**
475         * @method _preventScroll
476         * @description Fires the click event if the enter key is pressed while 
477         * focused on an HTML element that is not natively clickable.
478         * @protected
479         */
480         _fireClick: function (event) {
481                 
482                 var oTarget = event.target,
483                         sNodeName = oTarget.get("nodeName").toLowerCase();
484
485                 if (event.keyCode === 13 && (!clickableElements[sNodeName] || 
486                                 (sNodeName === "a" && !oTarget.getAttribute("href")))) {
487
488
489                         oTarget.simulate("click");
490                         
491                 }
492                 
493         },
494
495
496         /**
497         * @method _attachKeyHandler
498         * @description Attaches the "key" event handlers used to support the "keys"
499         * attribute.
500         * @protected
501         */
502         _attachKeyHandler: function () {
503
504                 this._detachKeyHandler();
505
506                 var sNextKey = this.get("keys.next"),
507                         sPrevKey = this.get("keys.previous"),
508                         oNode = this.get(HOST),
509                         aHandlers = this._eventHandlers;
510
511                 if (sPrevKey) {
512                         this._prevKeyHandler = 
513                                 Y.on(KEY, Y.bind(this._focusPrevious, this), oNode, sPrevKey);
514                 }
515
516                 if (sNextKey) {
517                         this._nextKeyHandler = 
518                                 Y.on(KEY, Y.bind(this._focusNext, this), oNode, sNextKey);
519                 }
520
521
522                 //      In Opera it is necessary to call the "preventDefault" method in  
523                 //      response to the user pressing the arrow keys in order to prevent 
524                 //      the viewport from scrolling when the user is moving focus among 
525                 //      the focusable descendants.
526                 
527                 if (UA.opera) { 
528                         aHandlers.push(oNode.on("keypress", this._preventScroll, this));
529                 }
530
531
532                 //      For all browsers except Opera: HTML elements that are not natively
533                 //      focusable but made focusable via the tabIndex attribute don't 
534                 //      fire a click event when the user presses the enter key.  It is 
535                 //      possible to work around this problem by simplying dispatching a 
536                 //      click event in response to the user pressing the enter key.
537
538                 if (!UA.opera) {
539                         aHandlers.push(oNode.on("keypress", this._fireClick, this));
540                 }
541
542         },
543
544
545         /**
546         * @method _detachEventHandlers
547         * @description Detaches all event handlers used by the Focus Manager.
548         * @protected
549         */
550         _detachEventHandlers: function () {
551
552                 this._detachKeyHandler();
553
554                 var aHandlers = this._eventHandlers;
555
556                 if (aHandlers) {
557
558                         Y.Array.each(aHandlers, function (handle) {
559                                 handle.detach();
560                         });
561
562                         this._eventHandlers = null;
563
564                 }
565                 
566         },
567
568
569         /**
570         * @method _detachEventHandlers
571         * @description Attaches all event handlers used by the Focus Manager.
572         * @protected    
573         */
574         _attachEventHandlers: function () {
575
576                 var descendants = this._descendants,
577                         aHandlers,
578                         oDocument,
579                         handle;
580
581                 if (descendants && descendants.size() > 1) {
582
583                         aHandlers = this._eventHandlers || [];
584                         oDocument = this.get(HOST).get("ownerDocument");
585
586
587                         if (aHandlers.length === 0) {
588
589
590                                 aHandlers.push(oDocument.on("focus", this._onDocFocus, this));
591
592                                 aHandlers.push(oDocument.on("mousedown", 
593                                         this._onDocMouseDown, this));
594
595                                 aHandlers.push(
596                                                 this.after("keysChange", this._attachKeyHandler));
597
598                                 aHandlers.push(
599                                                 this.after("descendantsChange", this._initDescendants));
600
601                                 aHandlers.push(
602                                                 this.after(ACTIVE_DESCENDANT_CHANGE, 
603                                                                 this._afterActiveDescendantChange));
604
605
606                                 //      For performance: defer attaching all key-related event 
607                                 //      handlers until the first time one of the specified 
608                                 //      descendants receives focus.
609
610                                 handle = this.after("focusedChange", Y.bind(function (event) {
611                                         
612                                         if (event.newVal) {
613                                                 
614                                                 
615                                                 this._attachKeyHandler();
616
617                                                 //      Detach this "focusedChange" handler so that the 
618                                                 //      key-related handlers only get attached once.
619
620                                                 handle.detach();
621
622                                         }
623                                         
624                                 }, this));
625                                 
626                                 aHandlers.push(handle);
627                                 
628                         }
629
630
631                         this._eventHandlers = aHandlers;
632                         
633                 }
634                 
635         },      
636         
637         
638         //      Protected event handlers
639
640         /**
641         * @method _onDocMouseDown
642         * @description "mousedown" event handler for the owner document of the 
643         * Focus Manager's Node.
644         * @protected
645         * @param event {Object} Object representing the DOM event.
646         */
647         _onDocMouseDown: function (event) {
648         
649                 var oHost = this.get(HOST),
650                         oTarget = event.target,
651                         bChildNode = oHost.contains(oTarget),
652                         node,
653
654                         getFocusable = function (node) {
655
656                                 var returnVal = false;
657
658                                 if (!node.compareTo(oHost)) {
659                                         
660                                         returnVal = this._isDescendant(node) ? node : 
661                                                                         getFocusable.call(this, node.get("parentNode"));
662
663                                 }
664                 
665                                 return returnVal;
666
667                         };
668                 
669
670                 if (bChildNode) {
671
672                         //      Check to make sure that the target isn't a child node of one 
673                         //      of the focusable descendants.
674
675                         node = getFocusable.call(this, oTarget);
676
677                         if (node) {
678                                 oTarget = node;
679                         }
680                         else if (!node && this.get(FOCUSED)) {
681
682                                 //      The target was a non-focusable descendant of the root 
683                                 //      node, so the "focused" attribute should be set to false.
684
685                                 this._set(FOCUSED, false);
686                                 this._onDocFocus(event);
687                                                                 
688                         }
689
690                 }
691                 
692
693                 if (bChildNode && this._isDescendant(oTarget)) {
694
695                         //      Fix general problem in Webkit: mousing down on a button or an 
696                         //      anchor element doesn't focus it.
697
698                         //      For all browsers: makes sure that the descendant that 
699                         //      was the target of the mousedown event is now considered the
700                         //      active descendant.
701
702                         this.focus(oTarget);
703                 }
704                 else if (UA.webkit && this.get(FOCUSED) && 
705                         (!bChildNode || (bChildNode && !this._isDescendant(oTarget)))) {
706
707                         //      Fix for Webkit:
708                         
709                         //      Document doesn't receive focus in Webkit when the user mouses 
710                         //      down on it, so the "focused" attribute won't get set to the 
711                         //      correct value.
712
713                         //      The goal is to force a blur if the user moused down on 
714                         //      either: 1) A descendant node, but not one that managed by 
715                         //      the FocusManager, or 2) an element outside of the 
716                         //      FocusManager
717
718                         this._set(FOCUSED, false);
719                         this._onDocFocus(event);
720
721                 }
722         
723         },
724
725
726         /**
727         * @method _onDocFocus
728         * @description "focus" event handler for the owner document of the 
729         * Focus Manager's Node.
730         * @protected
731         * @param event {Object} Object representing the DOM event.
732         */
733         _onDocFocus: function (event) {
734
735                 var oTarget = this._focusTarget || event.target,
736                         bFocused = this.get(FOCUSED),
737                         focusClass = this.get(FOCUS_CLASS),
738                         oFocusedNode = this._focusedNode,
739                         bInCollection;
740
741                 if (this._focusTarget) {
742                         this._focusTarget = null;
743                 }
744
745
746                 if (this.get(HOST).contains(oTarget)) { 
747
748                         //      The target is a descendant of the root Node.
749
750                         bInCollection = this._isDescendant(oTarget);
751
752                         if (!bFocused && bInCollection) {
753
754                                 //      The user has focused a focusable descendant.
755
756                                 bFocused = true;
757
758                         }
759                         else if (bFocused && !bInCollection) {  
760                         
761                                 //      The user has focused a child of the root Node that is 
762                                 //      not one of the descendants managed by this Focus Manager
763                                 //      so clear the currently focused descendant.
764                                 
765                                 bFocused = false;
766                         
767                         }
768                         
769                 }
770                 else { 
771                 
772                         // The target is some other node in the document.
773
774                         bFocused = false;
775                         
776                 }
777
778
779                 if (focusClass) {
780
781                         if (oFocusedNode && (!oFocusedNode.compareTo(oTarget) || !bFocused)) {
782                                 this._removeFocusClass();
783                         }
784
785                         if (bInCollection && bFocused) {
786
787                                 if (focusClass.fn) {
788                                         oTarget = focusClass.fn(oTarget);
789                                         oTarget.addClass(focusClass.className);
790                                 }
791                                 else {
792                                         oTarget.addClass(focusClass);
793                                 }
794
795                                 this._focusedNode = oTarget;
796
797                         }
798                         
799                 }
800
801
802                 this._set(FOCUSED, bFocused);                   
803
804         },
805
806
807         /**
808         * @method _focusNext
809         * @description Keydown event handler that moves focus to the next 
810         * enabled descendant.
811         * @protected
812         * @param event {Object} Object representing the DOM event.
813         * @param activeDescendant {Number} Number representing the index of the 
814         * next descendant to be focused
815         */
816         _focusNext: function (event, activeDescendant) {
817
818                 var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
819                         oNode;
820
821
822                 if (this._isDescendant(event.target) && 
823                         (nActiveDescendant <= this._lastNodeIndex)) {
824
825                         nActiveDescendant = nActiveDescendant + 1;
826
827                         if (nActiveDescendant === (this._lastNodeIndex + 1) && 
828                                 this.get(CIRCULAR)) {
829
830                                 nActiveDescendant = 0;
831
832                         }
833
834                         oNode = this._descendants.item(nActiveDescendant);
835                         
836                         if (oNode.get(DISABLED)) {
837                                 this._focusNext(event, nActiveDescendant);
838                         }
839                         else {
840                                 this.focus(nActiveDescendant);
841                         }
842
843                 }
844                 
845                 this._preventScroll(event);
846
847         },
848
849
850         /**
851         * @method _focusPrevious
852         * @description Keydown event handler that moves focus to the previous 
853         * enabled descendant.
854         * @protected
855         * @param event {Object} Object representing the DOM event.
856         * @param activeDescendant {Number} Number representing the index of the 
857         * next descendant to be focused.
858         */
859         _focusPrevious: function (event, activeDescendant) {
860         
861                 var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
862                         oNode;
863         
864                 if (this._isDescendant(event.target) && nActiveDescendant >= 0) {
865
866                         nActiveDescendant = nActiveDescendant - 1;
867
868                         if (nActiveDescendant === -1 && this.get(CIRCULAR)) {
869                                 nActiveDescendant = this._lastNodeIndex;
870                         }
871
872                         oNode = this._descendants.item(nActiveDescendant);
873
874                         if (oNode.get(DISABLED)) {
875                                 this._focusPrevious(event, nActiveDescendant);
876                         }
877                         else {
878                                 this.focus(nActiveDescendant);                          
879                         }
880
881                 }
882                 
883                 this._preventScroll(event);                     
884         
885         },
886
887
888         /**
889         * @method _afterActiveDescendantChange
890         * @description afterChange event handler for the 
891         * "activeDescendant" attribute.
892         * @protected
893         * @param event {Object} Object representing the change event.
894         */      
895         _afterActiveDescendantChange: function (event) {
896
897                 var oNode = this._descendants.item(event.prevVal);
898                 
899                 if (oNode) {
900                         oNode.set(TAB_INDEX, -1);
901                 }
902
903                 oNode = this._descendants.item(event.newVal);
904
905                 if (oNode) {
906                         oNode.set(TAB_INDEX, 0);
907                 }
908                 
909         },
910
911
912
913         //      Public methods
914
915     initializer: function (config) {
916
917                 this.start();
918
919     },
920
921         destructor: function () {
922                 
923                 this.stop();
924                 this.get(HOST).focusManager = null;
925                 
926     },
927
928
929         /**
930         * @method focus
931         * @description Focuses the active descendant and sets the  
932         * <code>focused</code> attribute to true.
933         * @param index {Number} Optional. Number representing the index of the 
934         * descendant to be set as the active descendant.
935         * @param index {Node} Optional. Node instance representing the 
936         * descendant to be set as the active descendant.
937         */
938         focus: function (index) {
939
940                 if (Lang.isUndefined(index)) {
941                         index = this.get(ACTIVE_DESCENDANT);
942                 }
943
944                 this.set(ACTIVE_DESCENDANT, index, { src: UI });
945
946                 var oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
947
948                 if (oNode) {
949
950                         oNode.focus();
951
952                         //      In Opera focusing a <BUTTON> element programmatically 
953                         //      will result in the document-level focus event handler 
954                         //      "_onDocFocus" being called, resulting in the handler 
955                         //      incorrectly setting the "focused" Attribute to false.  To fix 
956                         //      this, set a flag ("_focusTarget") that the "_onDocFocus" method 
957                         //      can look for to properly handle this edge case.
958
959                         if (UA.opera && oNode.get("nodeName").toLowerCase() === "button") {
960                                 this._focusTarget = oNode;
961                         }
962
963                 }
964
965         },
966
967
968         /**
969         * @method blur
970         * @description Blurs the current active descendant and sets the 
971         * <code>focused</code> attribute to false.
972         */
973         blur: function () {
974
975                 var oNode;
976
977                 if (this.get(FOCUSED)) {
978
979                         oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
980
981                         if (oNode) {
982
983                                 oNode.blur();
984
985                                 //      For Opera and Webkit:  Blurring an element in either browser
986                                 //      doesn't result in another element (such as the document)
987                                 //      being focused.  Therefore, the "_onDocFocus" method 
988                                 //      responsible for managing the application and removal of the 
989                                 //      focus indicator class name is never called.
990
991                                 this._removeFocusClass();
992                                 
993                         }
994
995                         this._set(FOCUSED, false, { src: UI });
996                 }
997                 
998         },
999
1000
1001         /**
1002         * @method start
1003         * @description Enables the Focus Manager.
1004         */
1005         start: function () {
1006
1007                 if (this._stopped) {
1008
1009                         this._initDescendants();
1010                         this._attachEventHandlers();
1011
1012                         this._stopped = false;
1013
1014                 }
1015                 
1016         },
1017
1018
1019         /**
1020         * @method stop
1021         * @description Disables the Focus Manager by detaching all event handlers.
1022         */      
1023         stop: function () {
1024
1025                 if (!this._stopped) {
1026
1027                         this._detachEventHandlers();
1028
1029                         this._descendants = null;
1030                         this._focusedNode = null;
1031                         this._lastNodeIndex = 0;
1032                         this._stopped = true;
1033                         
1034                 }
1035
1036         },
1037
1038
1039         /**
1040         * @method refresh
1041         * @description Refreshes the Focus Manager's descendants by re-executing the 
1042         * CSS selector query specified by the <code>descendants</code> attribute.
1043         */
1044         refresh: function () {
1045
1046                 this._initDescendants();
1047                 
1048         }
1049         
1050 });
1051
1052
1053 NodeFocusManager.NAME = "nodeFocusManager";
1054 NodeFocusManager.NS = "focusManager";
1055
1056 Y.namespace("Plugin");
1057 Y.Plugin.NodeFocusManager = NodeFocusManager;
1058
1059
1060 }, '3.0.0' ,{requires:['attribute', 'node', 'plugin', 'node-event-simulate', 'event-key', 'event-focus']});