MenubarLinks.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. /*
  2. * This content is licensed according to the W3C Software License at
  3. * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
  4. */
  5. var Menubar = function (domNode) {
  6. var elementChildren,
  7. msgPrefix = 'Menubar constructor argument menubarNode ';
  8. // Check whether menubarNode is a DOM element
  9. if (!domNode instanceof Element) {
  10. throw new TypeError(msgPrefix + 'is not a DOM Element.');
  11. }
  12. // Check whether menubarNode has descendant elements
  13. if (domNode.childElementCount === 0) {
  14. throw new Error(msgPrefix + 'has no element children.');
  15. }
  16. // Check whether menubarNode has A elements
  17. e = domNode.firstElementChild;
  18. while (e) {
  19. var menubarItem = e.firstElementChild;
  20. if (e && menubarItem && menubarItem.tagName !== 'A') {
  21. throw new Error(msgPrefix + 'has child elements are not A elements.');
  22. }
  23. e = e.nextElementSibling;
  24. }
  25. this.isMenubar = true;
  26. this.domNode = domNode;
  27. this.menubarItems = []; // See Menubar init method
  28. this.firstChars = []; // See Menubar init method
  29. this.firstItem = null; // See Menubar init method
  30. this.lastItem = null; // See Menubar init method
  31. this.hasFocus = false; // See MenubarItem handleFocus, handleBlur
  32. this.hasHover = false; // See Menubar handleMouseover, handleMouseout
  33. };
  34. /*
  35. * @method Menubar.prototype.init
  36. *
  37. * @desc
  38. * Adds ARIA role to the menubar node
  39. * Traverse menubar children for A elements to configure each A element as a ARIA menuitem
  40. * and populate menuitems array. Initialize firstItem and lastItem properties.
  41. */
  42. Menubar.prototype.init = function () {
  43. var menubarItem, childElement, menuElement, textContent, numItems;
  44. // Traverse the element children of menubarNode: configure each with
  45. // menuitem role behavior and store reference in menuitems array.
  46. elem = this.domNode.firstElementChild;
  47. while (elem) {
  48. var menuElement = elem.firstElementChild;
  49. if (elem && menuElement && menuElement.tagName === 'A') {
  50. menubarItem = new MenubarItem(menuElement, this);
  51. menubarItem.init();
  52. this.menubarItems.push(menubarItem);
  53. textContent = menuElement.textContent.trim();
  54. this.firstChars.push(textContent.substring(0, 1).toLowerCase());
  55. }
  56. elem = elem.nextElementSibling;
  57. }
  58. // Use populated menuitems array to initialize firstItem and lastItem.
  59. numItems = this.menubarItems.length;
  60. if (numItems > 0) {
  61. this.firstItem = this.menubarItems[ 0 ];
  62. this.lastItem = this.menubarItems[ numItems - 1 ];
  63. }
  64. this.firstItem.domNode.tabIndex = 0;
  65. };
  66. /* FOCUS MANAGEMENT METHODS */
  67. Menubar.prototype.setFocusToItem = function (newItem) {
  68. var flag = false;
  69. for (var i = 0; i < this.menubarItems.length; i++) {
  70. var mbi = this.menubarItems[i];
  71. if (mbi.domNode.tabIndex == 0) {
  72. flag = mbi.domNode.getAttribute('aria-expanded') === 'true';
  73. }
  74. mbi.domNode.tabIndex = -1;
  75. if (mbi.popupMenu) {
  76. mbi.popupMenu.close();
  77. }
  78. }
  79. newItem.domNode.focus();
  80. newItem.domNode.tabIndex = 0;
  81. if (flag && newItem.popupMenu) {
  82. newItem.popupMenu.open();
  83. }
  84. };
  85. Menubar.prototype.setFocusToFirstItem = function (flag) {
  86. this.setFocusToItem(this.firstItem);
  87. };
  88. Menubar.prototype.setFocusToLastItem = function (flag) {
  89. this.setFocusToItem(this.lastItem);
  90. };
  91. Menubar.prototype.setFocusToPreviousItem = function (currentItem) {
  92. var index;
  93. if (currentItem === this.firstItem) {
  94. newItem = this.lastItem;
  95. }
  96. else {
  97. index = this.menubarItems.indexOf(currentItem);
  98. newItem = this.menubarItems[ index - 1 ];
  99. }
  100. this.setFocusToItem(newItem);
  101. };
  102. Menubar.prototype.setFocusToNextItem = function (currentItem) {
  103. var index;
  104. if (currentItem === this.lastItem) {
  105. newItem = this.firstItem;
  106. }
  107. else {
  108. index = this.menubarItems.indexOf(currentItem);
  109. newItem = this.menubarItems[ index + 1 ];
  110. }
  111. this.setFocusToItem(newItem);
  112. };
  113. Menubar.prototype.setFocusByFirstCharacter = function (currentItem, char) {
  114. var start, index, char = char.toLowerCase();
  115. var flag = currentItem.domNode.getAttribute('aria-expanded') === 'true';
  116. // Get start index for search based on position of currentItem
  117. start = this.menubarItems.indexOf(currentItem) + 1;
  118. if (start === this.menubarItems.length) {
  119. start = 0;
  120. }
  121. // Check remaining slots in the menu
  122. index = this.getIndexFirstChars(start, char);
  123. // If not found in remaining slots, check from beginning
  124. if (index === -1) {
  125. index = this.getIndexFirstChars(0, char);
  126. }
  127. // If match was found...
  128. if (index > -1) {
  129. this.setFocusToItem(this.menubarItems[ index ]);
  130. }
  131. };
  132. Menubar.prototype.getIndexFirstChars = function (startIndex, char) {
  133. for (var i = startIndex; i < this.firstChars.length; i++) {
  134. if (char === this.firstChars[ i ]) {
  135. return i;
  136. }
  137. }
  138. return -1;
  139. };