~landscape/lazr-js/trunk

« back to all changes in this revision

Viewing changes to src-js/lazrjs/yui/3.0.0/build/node-focusmanager/node-focusmanager.js

  • Committer: Sidnei da Silva
  • Date: 2009-10-21 21:43:07 UTC
  • mfrom: (120.2.15 yui-3.0.0)
  • mto: (124.5.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 126.
  • Revision ID: sidnei.da.silva@canonical.com-20091021214307-mpul9404n317puk5
- Merge from yui-3.0.0, resolving conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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']});