[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/node-focusmanager/ -> node-focusmanager.js (source)

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1