]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/node-focusmanager/node-focusmanager.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / node-focusmanager / node-focusmanager.js
1 /*
2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 3.3.0
6 build: 3167
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             for (i = 0; i < nDescendants; i++) {
316
317                 oNode = descendants.item(i);
318
319                 if (nFirstEnabled === -1 && !oNode.get(DISABLED)) {
320                     nFirstEnabled = i;
321                 }
322
323
324                 //      If the user didn't specify a value for the
325                 //      "activeDescendant" attribute try to infer it from
326                 //      the markup.
327
328                 //      Need to pass "2" when using "getAttribute" for IE to get
329                 //      the attribute value as it is set in the markup.
330                 //      Need to use "parseInt" because IE always returns the
331                 //      value as a number, whereas all other browsers return
332                 //      the attribute as a string when accessed
333                 //      via "getAttribute".
334
335                 if (nActiveDescendant < 0 &&
336                         parseInt(oNode.getAttribute(TAB_INDEX, 2), 10) === 0) {
337
338                     nActiveDescendant = i;
339
340                 }
341
342                 if (oNode) {
343                     oNode.set(TAB_INDEX, -1);
344                 }
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             if (oNode) {
388                 oNode.set(TAB_INDEX, 0);
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] && this._isDescendant(event.target)) {
468                         event.preventDefault();
469                 }
470
471         },
472
473
474         /**
475         * @method _fireClick
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()) {
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) {
837
838                 if (oNode.get("disabled")) {
839                     this._focusNext(event, nActiveDescendant);
840                 }
841                 else {
842                     this.focus(nActiveDescendant);
843                 }
844
845             }
846
847                 }
848
849                 this._preventScroll(event);
850
851         },
852
853
854         /**
855         * @method _focusPrevious
856         * @description Keydown event handler that moves focus to the previous
857         * enabled descendant.
858         * @protected
859         * @param event {Object} Object representing the DOM event.
860         * @param activeDescendant {Number} Number representing the index of the
861         * next descendant to be focused.
862         */
863         _focusPrevious: function (event, activeDescendant) {
864
865                 var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
866                         oNode;
867
868                 if (this._isDescendant(event.target) && nActiveDescendant >= 0) {
869
870                         nActiveDescendant = nActiveDescendant - 1;
871
872                         if (nActiveDescendant === -1 && this.get(CIRCULAR)) {
873                                 nActiveDescendant = this._lastNodeIndex;
874                         }
875
876             oNode = this._descendants.item(nActiveDescendant);
877
878             if (oNode) {
879
880                 if (oNode.get("disabled")) {
881                     this._focusPrevious(event, nActiveDescendant);
882                 }
883                 else {
884                     this.focus(nActiveDescendant);
885                 }
886
887             }
888
889                 }
890
891                 this._preventScroll(event);
892
893         },
894
895
896         /**
897         * @method _afterActiveDescendantChange
898         * @description afterChange event handler for the
899         * "activeDescendant" attribute.
900         * @protected
901         * @param event {Object} Object representing the change event.
902         */
903         _afterActiveDescendantChange: function (event) {
904
905                 var oNode = this._descendants.item(event.prevVal);
906
907                 if (oNode) {
908                         oNode.set(TAB_INDEX, -1);
909                 }
910
911                 oNode = this._descendants.item(event.newVal);
912
913                 if (oNode) {
914                         oNode.set(TAB_INDEX, 0);
915                 }
916
917         },
918
919
920
921         //      Public methods
922
923     initializer: function (config) {
924
925                 this.start();
926
927     },
928
929         destructor: function () {
930
931                 this.stop();
932                 this.get(HOST).focusManager = null;
933
934     },
935
936
937         /**
938         * @method focus
939         * @description Focuses the active descendant and sets the
940         * <code>focused</code> attribute to true.
941         * @param index {Number} Optional. Number representing the index of the
942         * descendant to be set as the active descendant.
943         * @param index {Node} Optional. Node instance representing the
944         * descendant to be set as the active descendant.
945         */
946         focus: function (index) {
947
948                 if (Lang.isUndefined(index)) {
949                         index = this.get(ACTIVE_DESCENDANT);
950                 }
951
952                 this.set(ACTIVE_DESCENDANT, index, { src: UI });
953
954                 var oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
955
956                 if (oNode) {
957
958                         oNode.focus();
959
960                         //      In Opera focusing a <BUTTON> element programmatically
961                         //      will result in the document-level focus event handler
962                         //      "_onDocFocus" being called, resulting in the handler
963                         //      incorrectly setting the "focused" Attribute to false.  To fix
964                         //      this, set a flag ("_focusTarget") that the "_onDocFocus" method
965                         //      can look for to properly handle this edge case.
966
967                         if (UA.opera && oNode.get("nodeName").toLowerCase() === "button") {
968                                 this._focusTarget = oNode;
969                         }
970
971                 }
972
973         },
974
975
976         /**
977         * @method blur
978         * @description Blurs the current active descendant and sets the
979         * <code>focused</code> attribute to false.
980         */
981         blur: function () {
982
983                 var oNode;
984
985                 if (this.get(FOCUSED)) {
986
987                         oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
988
989                         if (oNode) {
990
991                                 oNode.blur();
992
993                                 //      For Opera and Webkit:  Blurring an element in either browser
994                                 //      doesn't result in another element (such as the document)
995                                 //      being focused.  Therefore, the "_onDocFocus" method
996                                 //      responsible for managing the application and removal of the
997                                 //      focus indicator class name is never called.
998
999                                 this._removeFocusClass();
1000
1001                         }
1002
1003                         this._set(FOCUSED, false, { src: UI });
1004                 }
1005
1006         },
1007
1008
1009         /**
1010         * @method start
1011         * @description Enables the Focus Manager.
1012         */
1013         start: function () {
1014
1015                 if (this._stopped) {
1016
1017                         this._initDescendants();
1018                         this._attachEventHandlers();
1019
1020                         this._stopped = false;
1021
1022                 }
1023
1024         },
1025
1026
1027         /**
1028         * @method stop
1029         * @description Disables the Focus Manager by detaching all event handlers.
1030         */
1031         stop: function () {
1032
1033                 if (!this._stopped) {
1034
1035                         this._detachEventHandlers();
1036
1037                         this._descendants = null;
1038                         this._focusedNode = null;
1039                         this._lastNodeIndex = 0;
1040                         this._stopped = true;
1041
1042                 }
1043
1044         },
1045
1046
1047         /**
1048         * @method refresh
1049         * @description Refreshes the Focus Manager's descendants by re-executing the
1050         * CSS selector query specified by the <code>descendants</code> attribute.
1051         */
1052         refresh: function () {
1053
1054                 this._initDescendants();
1055
1056                 if (!this._eventHandlers) {
1057                         this._attachEventHandlers();
1058                 }
1059
1060         }
1061
1062 });
1063
1064
1065 NodeFocusManager.NAME = "nodeFocusManager";
1066 NodeFocusManager.NS = "focusManager";
1067
1068 Y.namespace("Plugin");
1069 Y.Plugin.NodeFocusManager = NodeFocusManager;
1070
1071
1072 }, '3.3.0' ,{requires:['attribute', 'node', 'plugin', 'node-event-simulate', 'event-key', 'event-focus']});