Dashboard sipadu mbip
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

node-focusmanager-debug.js 24KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076
  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.4.0
  6. build: nightly
  7. */
  8. YUI.add('node-focusmanager', function(Y) {
  9. /**
  10. * <p>The Focus Manager Node Plugin makes it easy to manage focus among
  11. * a Node's descendants. Primarily intended to help with widget development,
  12. * the Focus Manager Node Plugin can be used to improve the keyboard
  13. * accessibility of widgets.</p>
  14. *
  15. * <p>
  16. * When designing widgets that manage a set of descendant controls (i.e. buttons
  17. * in a toolbar, tabs in a tablist, menuitems in a menu, etc.) it is important to
  18. * limit the number of descendants in the browser's default tab flow. The fewer
  19. * number of descendants in the default tab flow, the easier it is for keyboard
  20. * users to navigate between widgets by pressing the tab key. When a widget has
  21. * focus it should provide a set of shortcut keys (typically the arrow keys)
  22. * to move focus among its descendants.
  23. * </p>
  24. *
  25. * <p>
  26. * To this end, the Focus Manager Node Plugin makes it easy to define a Node's
  27. * focusable descendants, define which descendant should be in the default tab
  28. * flow, and define the keys that move focus among each descendant.
  29. * Additionally, as the CSS
  30. * <a href="http://www.w3.org/TR/CSS21/selector.html#x38"><code>:focus</code></a>
  31. * pseudo class is not supported on all elements in all
  32. * <a href="http://developer.yahoo.com/yui/articles/gbs/">A-Grade browsers</a>,
  33. * the Focus Manager Node Plugin provides an easy, cross-browser means of
  34. * styling focus.
  35. * </p>
  36. *
  37. * @module node-focusmanager
  38. */
  39. // Frequently used strings
  40. var ACTIVE_DESCENDANT = "activeDescendant",
  41. ID = "id",
  42. DISABLED = "disabled",
  43. TAB_INDEX = "tabIndex",
  44. FOCUSED = "focused",
  45. FOCUS_CLASS = "focusClass",
  46. CIRCULAR = "circular",
  47. UI = "UI",
  48. KEY = "key",
  49. ACTIVE_DESCENDANT_CHANGE = ACTIVE_DESCENDANT + "Change",
  50. HOST = "host",
  51. // Collection of keys that, when pressed, cause the browser viewport
  52. // to scroll.
  53. scrollKeys = {
  54. 37: true,
  55. 38: true,
  56. 39: true,
  57. 40: true
  58. },
  59. clickableElements = {
  60. "a": true,
  61. "button": true,
  62. "input": true,
  63. "object": true
  64. },
  65. // Library shortcuts
  66. Lang = Y.Lang,
  67. UA = Y.UA,
  68. /**
  69. * The NodeFocusManager class is a plugin for a Node instance. The class is used
  70. * via the <a href="Node.html#method_plug"><code>plug</code></a> method of Node
  71. * and should not be instantiated directly.
  72. * @namespace plugin
  73. * @class NodeFocusManager
  74. */
  75. NodeFocusManager = function () {
  76. NodeFocusManager.superclass.constructor.apply(this, arguments);
  77. };
  78. NodeFocusManager.ATTRS = {
  79. /**
  80. * Boolean indicating that one of the descendants is focused.
  81. *
  82. * @attribute focused
  83. * @readOnly
  84. * @default false
  85. * @type boolean
  86. */
  87. focused: {
  88. value: false,
  89. readOnly: true
  90. },
  91. /**
  92. * String representing the CSS selector used to define the descendant Nodes
  93. * whose focus should be managed.
  94. *
  95. * @attribute descendants
  96. * @type Y.NodeList
  97. */
  98. descendants: {
  99. getter: function (value) {
  100. return this.get(HOST).all(value);
  101. }
  102. },
  103. /**
  104. * <p>Node, or index of the Node, representing the descendant that is either
  105. * focused or is focusable (<code>tabIndex</code> attribute is set to 0).
  106. * The value cannot represent a disabled descendant Node. Use a value of -1
  107. * to remove all descendant Nodes from the default tab flow.
  108. * If no value is specified, the active descendant will be inferred using
  109. * the following criteria:</p>
  110. * <ol>
  111. * <li>Examining the <code>tabIndex</code> attribute of each descendant and
  112. * using the first descendant whose <code>tabIndex</code> attribute is set
  113. * to 0</li>
  114. * <li>If no default can be inferred then the value is set to either 0 or
  115. * the index of the first enabled descendant.</li>
  116. * </ol>
  117. *
  118. * @attribute activeDescendant
  119. * @type Number
  120. */
  121. activeDescendant: {
  122. setter: function (value) {
  123. var isNumber = Lang.isNumber,
  124. INVALID_VALUE = Y.Attribute.INVALID_VALUE,
  125. descendantsMap = this._descendantsMap,
  126. descendants = this._descendants,
  127. nodeIndex,
  128. returnValue,
  129. oNode;
  130. if (isNumber(value)) {
  131. nodeIndex = value;
  132. returnValue = nodeIndex;
  133. }
  134. else if ((value instanceof Y.Node) && descendantsMap) {
  135. nodeIndex = descendantsMap[value.get(ID)];
  136. if (isNumber(nodeIndex)) {
  137. returnValue = nodeIndex;
  138. }
  139. else {
  140. // The user passed a reference to a Node that wasn't one
  141. // of the descendants.
  142. returnValue = INVALID_VALUE;
  143. }
  144. }
  145. else {
  146. returnValue = INVALID_VALUE;
  147. }
  148. if (descendants) {
  149. oNode = descendants.item(nodeIndex);
  150. if (oNode && oNode.get("disabled")) {
  151. // Setting the "activeDescendant" attribute to the index
  152. // of a disabled descendant is invalid.
  153. returnValue = INVALID_VALUE;
  154. }
  155. }
  156. return returnValue;
  157. }
  158. },
  159. /**
  160. * Object literal representing the keys to be used to navigate between the
  161. * next/previous descendant. The format for the attribute's value is
  162. * <code>{ next: "down:40", previous: "down:38" }</code>. The value for the
  163. * "next" and "previous" properties are used to attach
  164. * <a href="event/#keylistener"><code>key</code></a> event listeners. See
  165. * the <a href="event/#keylistener">Using the key Event</a> section of
  166. * the Event documentation for more information on "key" event listeners.
  167. *
  168. * @attribute keys
  169. * @type Object
  170. */
  171. keys: {
  172. value: {
  173. next: null,
  174. previous: null
  175. }
  176. },
  177. /**
  178. * String representing the name of class applied to the focused active
  179. * descendant Node. Can also be an object literal used to define both the
  180. * class name, and the Node to which the class should be applied. If using
  181. * an object literal, the format is:
  182. * <code>{ className: "focus", fn: myFunction }</code>. The function
  183. * referenced by the <code>fn</code> property in the object literal will be
  184. * passed a reference to the currently focused active descendant Node.
  185. *
  186. * @attribute focusClass
  187. * @type String|Object
  188. */
  189. focusClass: { },
  190. /**
  191. * Boolean indicating if focus should be set to the first/last descendant
  192. * when the end or beginning of the descendants has been reached.
  193. *
  194. * @attribute circular
  195. * @type Boolean
  196. * @default true
  197. */
  198. circular: {
  199. value: true
  200. }
  201. };
  202. Y.extend(NodeFocusManager, Y.Plugin.Base, {
  203. // Protected properties
  204. // Boolean indicating if the NodeFocusManager is active.
  205. _stopped: true,
  206. // NodeList representing the descendants selected via the
  207. // "descendants" attribute.
  208. _descendants: null,
  209. // Object literal mapping the IDs of each descendant to its index in the
  210. // "_descendants" NodeList.
  211. _descendantsMap: null,
  212. // Reference to the Node instance to which the focused class (defined
  213. // by the "focusClass" attribute) is currently applied.
  214. _focusedNode: null,
  215. // Number representing the index of the last descendant Node.
  216. _lastNodeIndex: 0,
  217. // Array of handles for event handlers used for a NodeFocusManager instance.
  218. _eventHandlers: null,
  219. // Protected methods
  220. /**
  221. * @method _initDescendants
  222. * @description Sets the <code>tabIndex</code> attribute of all of the
  223. * descendants to -1, except the active descendant, whose
  224. * <code>tabIndex</code> attribute is set to 0.
  225. * @protected
  226. */
  227. _initDescendants: function () {
  228. var descendants = this.get("descendants"),
  229. descendantsMap = {},
  230. nFirstEnabled = -1,
  231. nDescendants,
  232. nActiveDescendant = this.get(ACTIVE_DESCENDANT),
  233. oNode,
  234. sID,
  235. i = 0;
  236. if (Lang.isUndefined(nActiveDescendant)) {
  237. nActiveDescendant = -1;
  238. }
  239. if (descendants) {
  240. nDescendants = descendants.size();
  241. for (i = 0; i < nDescendants; i++) {
  242. oNode = descendants.item(i);
  243. if (nFirstEnabled === -1 && !oNode.get(DISABLED)) {
  244. nFirstEnabled = i;
  245. }
  246. // If the user didn't specify a value for the
  247. // "activeDescendant" attribute try to infer it from
  248. // the markup.
  249. // Need to pass "2" when using "getAttribute" for IE to get
  250. // the attribute value as it is set in the markup.
  251. // Need to use "parseInt" because IE always returns the
  252. // value as a number, whereas all other browsers return
  253. // the attribute as a string when accessed
  254. // via "getAttribute".
  255. if (nActiveDescendant < 0 &&
  256. parseInt(oNode.getAttribute(TAB_INDEX, 2), 10) === 0) {
  257. nActiveDescendant = i;
  258. }
  259. if (oNode) {
  260. oNode.set(TAB_INDEX, -1);
  261. }
  262. sID = oNode.get(ID);
  263. if (!sID) {
  264. sID = Y.guid();
  265. oNode.set(ID, sID);
  266. }
  267. descendantsMap[sID] = i;
  268. }
  269. // If the user didn't specify a value for the
  270. // "activeDescendant" attribute and no default value could be
  271. // determined from the markup, then default to 0.
  272. if (nActiveDescendant < 0) {
  273. nActiveDescendant = 0;
  274. }
  275. oNode = descendants.item(nActiveDescendant);
  276. // Check to make sure the active descendant isn't disabled,
  277. // and fall back to the first enabled descendant if it is.
  278. if (!oNode || oNode.get(DISABLED)) {
  279. oNode = descendants.item(nFirstEnabled);
  280. nActiveDescendant = nFirstEnabled;
  281. }
  282. this._lastNodeIndex = nDescendants - 1;
  283. this._descendants = descendants;
  284. this._descendantsMap = descendantsMap;
  285. this.set(ACTIVE_DESCENDANT, nActiveDescendant);
  286. // Need to set the "tabIndex" attribute here, since the
  287. // "activeDescendantChange" event handler used to manage
  288. // the setting of the "tabIndex" attribute isn't wired up yet.
  289. if (oNode) {
  290. oNode.set(TAB_INDEX, 0);
  291. }
  292. }
  293. },
  294. /**
  295. * @method _isDescendant
  296. * @description Determines if the specified Node instance is a descendant
  297. * managed by the Focus Manager.
  298. * @param node {Node} Node instance to be checked.
  299. * @return {Boolean} Boolean indicating if the specified Node instance is a
  300. * descendant managed by the Focus Manager.
  301. * @protected
  302. */
  303. _isDescendant: function (node) {
  304. return (node.get(ID) in this._descendantsMap);
  305. },
  306. /**
  307. * @method _removeFocusClass
  308. * @description Removes the class name representing focus (as specified by
  309. * the "focusClass" attribute) from the Node instance to which it is
  310. * currently applied.
  311. * @protected
  312. */
  313. _removeFocusClass: function () {
  314. var oFocusedNode = this._focusedNode,
  315. focusClass = this.get(FOCUS_CLASS),
  316. sClassName;
  317. if (focusClass) {
  318. sClassName = Lang.isString(focusClass) ?
  319. focusClass : focusClass.className;
  320. }
  321. if (oFocusedNode && sClassName) {
  322. oFocusedNode.removeClass(sClassName);
  323. }
  324. },
  325. /**
  326. * @method _detachKeyHandler
  327. * @description Detaches the "key" event handlers used to support the "keys"
  328. * attribute.
  329. * @protected
  330. */
  331. _detachKeyHandler: function () {
  332. var prevKeyHandler = this._prevKeyHandler,
  333. nextKeyHandler = this._nextKeyHandler;
  334. if (prevKeyHandler) {
  335. prevKeyHandler.detach();
  336. }
  337. if (nextKeyHandler) {
  338. nextKeyHandler.detach();
  339. }
  340. },
  341. /**
  342. * @method _preventScroll
  343. * @description Prevents the viewport from scolling when the user presses
  344. * the up, down, left, or right key.
  345. * @protected
  346. */
  347. _preventScroll: function (event) {
  348. if (scrollKeys[event.keyCode] && this._isDescendant(event.target)) {
  349. event.preventDefault();
  350. }
  351. },
  352. /**
  353. * @method _fireClick
  354. * @description Fires the click event if the enter key is pressed while
  355. * focused on an HTML element that is not natively clickable.
  356. * @protected
  357. */
  358. _fireClick: function (event) {
  359. var oTarget = event.target,
  360. sNodeName = oTarget.get("nodeName").toLowerCase();
  361. if (event.keyCode === 13 && (!clickableElements[sNodeName] ||
  362. (sNodeName === "a" && !oTarget.getAttribute("href")))) {
  363. Y.log(("Firing click event for node:" + oTarget.get("id")), "info", "nodeFocusManager");
  364. oTarget.simulate("click");
  365. }
  366. },
  367. /**
  368. * @method _attachKeyHandler
  369. * @description Attaches the "key" event handlers used to support the "keys"
  370. * attribute.
  371. * @protected
  372. */
  373. _attachKeyHandler: function () {
  374. this._detachKeyHandler();
  375. var sNextKey = this.get("keys.next"),
  376. sPrevKey = this.get("keys.previous"),
  377. oNode = this.get(HOST),
  378. aHandlers = this._eventHandlers;
  379. if (sPrevKey) {
  380. this._prevKeyHandler =
  381. Y.on(KEY, Y.bind(this._focusPrevious, this), oNode, sPrevKey);
  382. }
  383. if (sNextKey) {
  384. this._nextKeyHandler =
  385. Y.on(KEY, Y.bind(this._focusNext, this), oNode, sNextKey);
  386. }
  387. // In Opera it is necessary to call the "preventDefault" method in
  388. // response to the user pressing the arrow keys in order to prevent
  389. // the viewport from scrolling when the user is moving focus among
  390. // the focusable descendants.
  391. if (UA.opera) {
  392. aHandlers.push(oNode.on("keypress", this._preventScroll, this));
  393. }
  394. // For all browsers except Opera: HTML elements that are not natively
  395. // focusable but made focusable via the tabIndex attribute don't
  396. // fire a click event when the user presses the enter key. It is
  397. // possible to work around this problem by simplying dispatching a
  398. // click event in response to the user pressing the enter key.
  399. if (!UA.opera) {
  400. aHandlers.push(oNode.on("keypress", this._fireClick, this));
  401. }
  402. },
  403. /**
  404. * @method _detachEventHandlers
  405. * @description Detaches all event handlers used by the Focus Manager.
  406. * @protected
  407. */
  408. _detachEventHandlers: function () {
  409. this._detachKeyHandler();
  410. var aHandlers = this._eventHandlers;
  411. if (aHandlers) {
  412. Y.Array.each(aHandlers, function (handle) {
  413. handle.detach();
  414. });
  415. this._eventHandlers = null;
  416. }
  417. },
  418. /**
  419. * @method _detachEventHandlers
  420. * @description Attaches all event handlers used by the Focus Manager.
  421. * @protected
  422. */
  423. _attachEventHandlers: function () {
  424. var descendants = this._descendants,
  425. aHandlers,
  426. oDocument,
  427. handle;
  428. if (descendants && descendants.size()) {
  429. aHandlers = this._eventHandlers || [];
  430. oDocument = this.get(HOST).get("ownerDocument");
  431. if (aHandlers.length === 0) {
  432. Y.log("Attaching base set of event handlers.", "info", "nodeFocusManager");
  433. aHandlers.push(oDocument.on("focus", this._onDocFocus, this));
  434. aHandlers.push(oDocument.on("mousedown",
  435. this._onDocMouseDown, this));
  436. aHandlers.push(
  437. this.after("keysChange", this._attachKeyHandler));
  438. aHandlers.push(
  439. this.after("descendantsChange", this._initDescendants));
  440. aHandlers.push(
  441. this.after(ACTIVE_DESCENDANT_CHANGE,
  442. this._afterActiveDescendantChange));
  443. // For performance: defer attaching all key-related event
  444. // handlers until the first time one of the specified
  445. // descendants receives focus.
  446. handle = this.after("focusedChange", Y.bind(function (event) {
  447. if (event.newVal) {
  448. Y.log("Attaching key event handlers.", "info", "nodeFocusManager");
  449. this._attachKeyHandler();
  450. // Detach this "focusedChange" handler so that the
  451. // key-related handlers only get attached once.
  452. handle.detach();
  453. }
  454. }, this));
  455. aHandlers.push(handle);
  456. }
  457. this._eventHandlers = aHandlers;
  458. }
  459. },
  460. // Protected event handlers
  461. /**
  462. * @method _onDocMouseDown
  463. * @description "mousedown" event handler for the owner document of the
  464. * Focus Manager's Node.
  465. * @protected
  466. * @param event {Object} Object representing the DOM event.
  467. */
  468. _onDocMouseDown: function (event) {
  469. var oHost = this.get(HOST),
  470. oTarget = event.target,
  471. bChildNode = oHost.contains(oTarget),
  472. node,
  473. getFocusable = function (node) {
  474. var returnVal = false;
  475. if (!node.compareTo(oHost)) {
  476. returnVal = this._isDescendant(node) ? node :
  477. getFocusable.call(this, node.get("parentNode"));
  478. }
  479. return returnVal;
  480. };
  481. if (bChildNode) {
  482. // Check to make sure that the target isn't a child node of one
  483. // of the focusable descendants.
  484. node = getFocusable.call(this, oTarget);
  485. if (node) {
  486. oTarget = node;
  487. }
  488. else if (!node && this.get(FOCUSED)) {
  489. // The target was a non-focusable descendant of the root
  490. // node, so the "focused" attribute should be set to false.
  491. this._set(FOCUSED, false);
  492. this._onDocFocus(event);
  493. }
  494. }
  495. if (bChildNode && this._isDescendant(oTarget)) {
  496. // Fix general problem in Webkit: mousing down on a button or an
  497. // anchor element doesn't focus it.
  498. // For all browsers: makes sure that the descendant that
  499. // was the target of the mousedown event is now considered the
  500. // active descendant.
  501. this.focus(oTarget);
  502. }
  503. else if (UA.webkit && this.get(FOCUSED) &&
  504. (!bChildNode || (bChildNode && !this._isDescendant(oTarget)))) {
  505. // Fix for Webkit:
  506. // Document doesn't receive focus in Webkit when the user mouses
  507. // down on it, so the "focused" attribute won't get set to the
  508. // correct value.
  509. // The goal is to force a blur if the user moused down on
  510. // either: 1) A descendant node, but not one that managed by
  511. // the FocusManager, or 2) an element outside of the
  512. // FocusManager
  513. this._set(FOCUSED, false);
  514. this._onDocFocus(event);
  515. }
  516. },
  517. /**
  518. * @method _onDocFocus
  519. * @description "focus" event handler for the owner document of the
  520. * Focus Manager's Node.
  521. * @protected
  522. * @param event {Object} Object representing the DOM event.
  523. */
  524. _onDocFocus: function (event) {
  525. var oTarget = this._focusTarget || event.target,
  526. bFocused = this.get(FOCUSED),
  527. focusClass = this.get(FOCUS_CLASS),
  528. oFocusedNode = this._focusedNode,
  529. bInCollection;
  530. if (this._focusTarget) {
  531. this._focusTarget = null;
  532. }
  533. if (this.get(HOST).contains(oTarget)) {
  534. // The target is a descendant of the root Node.
  535. bInCollection = this._isDescendant(oTarget);
  536. if (!bFocused && bInCollection) {
  537. // The user has focused a focusable descendant.
  538. bFocused = true;
  539. }
  540. else if (bFocused && !bInCollection) {
  541. // The user has focused a child of the root Node that is
  542. // not one of the descendants managed by this Focus Manager
  543. // so clear the currently focused descendant.
  544. bFocused = false;
  545. }
  546. }
  547. else {
  548. // The target is some other node in the document.
  549. bFocused = false;
  550. }
  551. if (focusClass) {
  552. if (oFocusedNode && (!oFocusedNode.compareTo(oTarget) || !bFocused)) {
  553. this._removeFocusClass();
  554. }
  555. if (bInCollection && bFocused) {
  556. if (focusClass.fn) {
  557. oTarget = focusClass.fn(oTarget);
  558. oTarget.addClass(focusClass.className);
  559. }
  560. else {
  561. oTarget.addClass(focusClass);
  562. }
  563. this._focusedNode = oTarget;
  564. }
  565. }
  566. this._set(FOCUSED, bFocused);
  567. },
  568. /**
  569. * @method _focusNext
  570. * @description Keydown event handler that moves focus to the next
  571. * enabled descendant.
  572. * @protected
  573. * @param event {Object} Object representing the DOM event.
  574. * @param activeDescendant {Number} Number representing the index of the
  575. * next descendant to be focused
  576. */
  577. _focusNext: function (event, activeDescendant) {
  578. var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
  579. oNode;
  580. if (this._isDescendant(event.target) &&
  581. (nActiveDescendant <= this._lastNodeIndex)) {
  582. nActiveDescendant = nActiveDescendant + 1;
  583. if (nActiveDescendant === (this._lastNodeIndex + 1) &&
  584. this.get(CIRCULAR)) {
  585. nActiveDescendant = 0;
  586. }
  587. oNode = this._descendants.item(nActiveDescendant);
  588. if (oNode) {
  589. if (oNode.get("disabled")) {
  590. this._focusNext(event, nActiveDescendant);
  591. }
  592. else {
  593. this.focus(nActiveDescendant);
  594. }
  595. }
  596. }
  597. this._preventScroll(event);
  598. },
  599. /**
  600. * @method _focusPrevious
  601. * @description Keydown event handler that moves focus to the previous
  602. * enabled descendant.
  603. * @protected
  604. * @param event {Object} Object representing the DOM event.
  605. * @param activeDescendant {Number} Number representing the index of the
  606. * next descendant to be focused.
  607. */
  608. _focusPrevious: function (event, activeDescendant) {
  609. var nActiveDescendant = activeDescendant || this.get(ACTIVE_DESCENDANT),
  610. oNode;
  611. if (this._isDescendant(event.target) && nActiveDescendant >= 0) {
  612. nActiveDescendant = nActiveDescendant - 1;
  613. if (nActiveDescendant === -1 && this.get(CIRCULAR)) {
  614. nActiveDescendant = this._lastNodeIndex;
  615. }
  616. oNode = this._descendants.item(nActiveDescendant);
  617. if (oNode) {
  618. if (oNode.get("disabled")) {
  619. this._focusPrevious(event, nActiveDescendant);
  620. }
  621. else {
  622. this.focus(nActiveDescendant);
  623. }
  624. }
  625. }
  626. this._preventScroll(event);
  627. },
  628. /**
  629. * @method _afterActiveDescendantChange
  630. * @description afterChange event handler for the
  631. * "activeDescendant" attribute.
  632. * @protected
  633. * @param event {Object} Object representing the change event.
  634. */
  635. _afterActiveDescendantChange: function (event) {
  636. var oNode = this._descendants.item(event.prevVal);
  637. if (oNode) {
  638. oNode.set(TAB_INDEX, -1);
  639. }
  640. oNode = this._descendants.item(event.newVal);
  641. if (oNode) {
  642. oNode.set(TAB_INDEX, 0);
  643. }
  644. },
  645. // Public methods
  646. initializer: function (config) {
  647. this.start();
  648. },
  649. destructor: function () {
  650. this.stop();
  651. this.get(HOST).focusManager = null;
  652. },
  653. /**
  654. * @method focus
  655. * @description Focuses the active descendant and sets the
  656. * <code>focused</code> attribute to true.
  657. * @param index {Number} Optional. Number representing the index of the
  658. * descendant to be set as the active descendant.
  659. * @param index {Node} Optional. Node instance representing the
  660. * descendant to be set as the active descendant.
  661. */
  662. focus: function (index) {
  663. if (Lang.isUndefined(index)) {
  664. index = this.get(ACTIVE_DESCENDANT);
  665. }
  666. this.set(ACTIVE_DESCENDANT, index, { src: UI });
  667. var oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
  668. if (oNode) {
  669. oNode.focus();
  670. // In Opera focusing a <BUTTON> element programmatically
  671. // will result in the document-level focus event handler
  672. // "_onDocFocus" being called, resulting in the handler
  673. // incorrectly setting the "focused" Attribute to false. To fix
  674. // this, set a flag ("_focusTarget") that the "_onDocFocus" method
  675. // can look for to properly handle this edge case.
  676. if (UA.opera && oNode.get("nodeName").toLowerCase() === "button") {
  677. this._focusTarget = oNode;
  678. }
  679. }
  680. },
  681. /**
  682. * @method blur
  683. * @description Blurs the current active descendant and sets the
  684. * <code>focused</code> attribute to false.
  685. */
  686. blur: function () {
  687. var oNode;
  688. if (this.get(FOCUSED)) {
  689. oNode = this._descendants.item(this.get(ACTIVE_DESCENDANT));
  690. if (oNode) {
  691. oNode.blur();
  692. // For Opera and Webkit: Blurring an element in either browser
  693. // doesn't result in another element (such as the document)
  694. // being focused. Therefore, the "_onDocFocus" method
  695. // responsible for managing the application and removal of the
  696. // focus indicator class name is never called.
  697. this._removeFocusClass();
  698. }
  699. this._set(FOCUSED, false, { src: UI });
  700. }
  701. },
  702. /**
  703. * @method start
  704. * @description Enables the Focus Manager.
  705. */
  706. start: function () {
  707. if (this._stopped) {
  708. this._initDescendants();
  709. this._attachEventHandlers();
  710. this._stopped = false;
  711. }
  712. },
  713. /**
  714. * @method stop
  715. * @description Disables the Focus Manager by detaching all event handlers.
  716. */
  717. stop: function () {
  718. if (!this._stopped) {
  719. this._detachEventHandlers();
  720. this._descendants = null;
  721. this._focusedNode = null;
  722. this._lastNodeIndex = 0;
  723. this._stopped = true;
  724. }
  725. },
  726. /**
  727. * @method refresh
  728. * @description Refreshes the Focus Manager's descendants by re-executing the
  729. * CSS selector query specified by the <code>descendants</code> attribute.
  730. */
  731. refresh: function () {
  732. this._initDescendants();
  733. if (!this._eventHandlers) {
  734. this._attachEventHandlers();
  735. }
  736. }
  737. });
  738. NodeFocusManager.NAME = "nodeFocusManager";
  739. NodeFocusManager.NS = "focusManager";
  740. Y.namespace("Plugin");
  741. Y.Plugin.NodeFocusManager = NodeFocusManager;
  742. }, '3.4.0' ,{requires:['attribute', 'node', 'plugin', 'node-event-simulate', 'event-key', 'event-focus']});