PopupMenuItemLinks.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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 MenuItem = function (domNode, menuObj) {
  6. if (typeof popupObj !== 'object') {
  7. popupObj = false;
  8. }
  9. this.domNode = domNode;
  10. this.menu = menuObj;
  11. this.popupMenu = false;
  12. this.isMenubarItem = false;
  13. this.keyCode = Object.freeze({
  14. 'TAB': 9,
  15. 'RETURN': 13,
  16. 'ESC': 27,
  17. 'SPACE': 32,
  18. 'PAGEUP': 33,
  19. 'PAGEDOWN': 34,
  20. 'END': 35,
  21. 'HOME': 36,
  22. 'LEFT': 37,
  23. 'UP': 38,
  24. 'RIGHT': 39,
  25. 'DOWN': 40
  26. });
  27. };
  28. MenuItem.prototype.init = function () {
  29. this.domNode.tabIndex = -1;
  30. this.domNode.addEventListener('keydown', this.handleKeydown.bind(this));
  31. this.domNode.addEventListener('click', this.handleClick.bind(this));
  32. this.domNode.addEventListener('focus', this.handleFocus.bind(this));
  33. this.domNode.addEventListener('blur', this.handleBlur.bind(this));
  34. this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this));
  35. this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this));
  36. // Initialize flyout menu
  37. var nextElement = this.domNode.nextElementSibling;
  38. if (nextElement && nextElement.tagName === 'UL') {
  39. this.popupMenu = new PopupMenu(nextElement, this);
  40. this.popupMenu.init();
  41. }
  42. };
  43. MenuItem.prototype.isExpanded = function () {
  44. return this.domNode.getAttribute('aria-expanded') === 'true';
  45. };
  46. /* EVENT HANDLERS */
  47. MenuItem.prototype.handleKeydown = function (event) {
  48. var tgt = event.currentTarget,
  49. char = event.key,
  50. flag = false,
  51. clickEvent;
  52. function isPrintableCharacter (str) {
  53. return str.length === 1 && str.match(/\S/);
  54. }
  55. switch (event.keyCode) {
  56. case this.keyCode.SPACE:
  57. case this.keyCode.RETURN:
  58. if (this.popupMenu) {
  59. this.popupMenu.open();
  60. this.popupMenu.setFocusToFirstItem();
  61. }
  62. else {
  63. // Create simulated mouse event to mimic the behavior of ATs
  64. // and let the event handler handleClick do the housekeeping.
  65. try {
  66. clickEvent = new MouseEvent('click', {
  67. 'view': window,
  68. 'bubbles': true,
  69. 'cancelable': true
  70. });
  71. }
  72. catch (err) {
  73. if (document.createEvent) {
  74. // DOM Level 3 for IE 9+
  75. clickEvent = document.createEvent('MouseEvents');
  76. clickEvent.initEvent('click', true, true);
  77. }
  78. }
  79. tgt.dispatchEvent(clickEvent);
  80. }
  81. flag = true;
  82. break;
  83. case this.keyCode.UP:
  84. this.menu.setFocusToPreviousItem(this);
  85. flag = true;
  86. break;
  87. case this.keyCode.DOWN:
  88. this.menu.setFocusToNextItem(this);
  89. flag = true;
  90. break;
  91. case this.keyCode.LEFT:
  92. this.menu.setFocusToController('previous', true);
  93. this.menu.close(true);
  94. flag = true;
  95. break;
  96. case this.keyCode.RIGHT:
  97. if (this.popupMenu) {
  98. this.popupMenu.open();
  99. this.popupMenu.setFocusToFirstItem();
  100. }
  101. else {
  102. this.menu.setFocusToController('next', true);
  103. this.menu.close(true);
  104. }
  105. flag = true;
  106. break;
  107. case this.keyCode.HOME:
  108. case this.keyCode.PAGEUP:
  109. this.menu.setFocusToFirstItem();
  110. flag = true;
  111. break;
  112. case this.keyCode.END:
  113. case this.keyCode.PAGEDOWN:
  114. this.menu.setFocusToLastItem();
  115. flag = true;
  116. break;
  117. case this.keyCode.ESC:
  118. this.menu.setFocusToController();
  119. this.menu.close(true);
  120. flag = true;
  121. break;
  122. case this.keyCode.TAB:
  123. this.menu.setFocusToController();
  124. break;
  125. default:
  126. if (isPrintableCharacter(char)) {
  127. this.menu.setFocusByFirstCharacter(this, char);
  128. flag = true;
  129. }
  130. break;
  131. }
  132. if (flag) {
  133. event.stopPropagation();
  134. event.preventDefault();
  135. }
  136. };
  137. MenuItem.prototype.setExpanded = function (value) {
  138. if (value) {
  139. this.domNode.setAttribute('aria-expanded', 'true');
  140. }
  141. else {
  142. this.domNode.setAttribute('aria-expanded', 'false');
  143. }
  144. };
  145. MenuItem.prototype.handleClick = function (event) {
  146. this.menu.setFocusToController();
  147. this.menu.close(true);
  148. };
  149. MenuItem.prototype.handleFocus = function (event) {
  150. this.menu.hasFocus = true;
  151. };
  152. MenuItem.prototype.handleBlur = function (event) {
  153. this.menu.hasFocus = false;
  154. setTimeout(this.menu.close.bind(this.menu, false), 300);
  155. };
  156. MenuItem.prototype.handleMouseover = function (event) {
  157. this.menu.hasHover = true;
  158. this.menu.open();
  159. if (this.popupMenu) {
  160. this.popupMenu.hasHover = true;
  161. this.popupMenu.open();
  162. }
  163. };
  164. MenuItem.prototype.handleMouseout = function (event) {
  165. if (this.popupMenu) {
  166. this.popupMenu.hasHover = false;
  167. this.popupMenu.close(true);
  168. }
  169. this.menu.hasHover = false;
  170. setTimeout(this.menu.close.bind(this.menu, false), 300);
  171. };