Menu.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. "use strict";
  2. /**
  3. * Licensed Materials - Property of IBM
  4. * IBM Cognos Products: BI Cloud (C) Copyright IBM Corp. 2014, 2017
  5. * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  6. */
  7. define(['bi/commons/ui/View', 'jquery', 'underscore', 'bi/commons/utils/BidiUtil', 'text!../templates/Menu.html'], function (View, $, _, bidi, template) {
  8. var MenuItem = null;
  9. /**
  10. * Represents a drop down menu that can be added to an app bar
  11. */
  12. MenuItem = View.extend({
  13. templateString: template,
  14. events: {
  15. 'keydown': '_handleKeyboard',
  16. 'click .dropdown-menuitem': '_handleClick'
  17. },
  18. itemActions: null,
  19. itemSpecMap: null,
  20. baseClass: 'toolbar',
  21. hcLabel: null,
  22. // create an element for high contrast label
  23. showTitle: true,
  24. /**
  25. * Creates a new menu item
  26. *
  27. * @param spec -
  28. * The menu item spec
  29. * @param root -
  30. * A reference to the root menu item, where items go if the app bar is collapsed horizontally
  31. */
  32. init: function init(spec) {
  33. _.defaults(spec, {
  34. hcLabel: true
  35. });
  36. _.extend(this, spec);
  37. this.itemActions = {};
  38. this.itemSpecMap = {};
  39. _.each(this.items, function (item) {
  40. this.itemSpecMap[item.name] = item;
  41. }.bind(this));
  42. MenuItem.inherited('init', this, arguments);
  43. },
  44. _handleClick: function _handleClick(event) {
  45. event.preventDefault();
  46. this._performAction(event.currentTarget.id);
  47. },
  48. _performAction: function _performAction(id) {
  49. var action = this.itemActions[id];
  50. if (action) {
  51. // Delay the action slightly to allow bootstrap to close the menu before
  52. // the action is fired
  53. setTimeout(function () {
  54. action();
  55. }, 10);
  56. }
  57. },
  58. /**
  59. * Removes a menu item
  60. */
  61. removeItem: function removeItem(name) {
  62. var id = this.viewId + '_' + name;
  63. this.$menu.children('#' + id).remove();
  64. delete this.itemSpecMap[name];
  65. delete this.itemActions[id];
  66. },
  67. addDivider: function addDivider() {
  68. var divider = $('<li role="presentation" class="divider">');
  69. this.$menu.append(divider);
  70. return divider;
  71. },
  72. /**
  73. * Adds a new menu item to the drop down
  74. */
  75. addItem: function addItem(item) {
  76. this.itemSpecMap[item.name] = item;
  77. var menuItem = $('<li id="' + this._getItemId(item.name) + '">');
  78. var action = null;
  79. if (item.checked) {
  80. action = $('<a role="menuitem" href="#" aria-label="' + item.label + '" aria-checked="true" class="menuitem-toggled">' + item.label + '</a>');
  81. } else {
  82. action = $('<a role="menuitem" href="#" aria-label="' + item.label + '" aria-checked="false">' + item.label + '</a>');
  83. }
  84. menuItem.append(action);
  85. menuItem.addClass('dropdown-menuitem');
  86. menuItem.addClass(item.name);
  87. this.$menu.append(menuItem);
  88. this._buildMenuItem(item);
  89. return menuItem;
  90. },
  91. showItem: function showItem(name) {
  92. this.$menu.find('.' + name).removeClass('hidden');
  93. },
  94. hideItem: function hideItem(name) {
  95. this.$menu.find('.' + name).addClass('hidden');
  96. },
  97. /*jshint maxcomplexity:13 */
  98. _handleKeyboard: function _handleKeyboard(e) {
  99. var keyCode = e.which || e.keyCode || e.charCode;
  100. var $target = $(e.target);
  101. var $nextFocus;
  102. switch (keyCode) {
  103. case 9:
  104. //tabkey
  105. if (e.shiftKey) {
  106. if ($target.hasClass('firstMenuItem')) {
  107. $nextFocus = $target.closest('ul').find('a.lastMenuItem');
  108. $nextFocus.focus();
  109. e.preventDefault();
  110. }
  111. } else {
  112. if ($target.hasClass('lastMenuItem')) {
  113. $nextFocus = $target.closest('ul').find('a.firstMenuItem');
  114. $nextFocus.focus();
  115. e.preventDefault();
  116. }
  117. }
  118. break;
  119. case 13: // enter key
  120. case 32:
  121. // space
  122. e.preventDefault();
  123. this.$toggle.dropdown('toggle');
  124. if ($(e.target).hasClass('dropdown-toggle')) {
  125. var first = this.$el.find('.dropdown-menu a').first();
  126. first.focus();
  127. } else {
  128. var currentItem = this.getTarget(e.target, 'dropdown-menuitem');
  129. this._performAction(currentItem.id);
  130. }
  131. break;
  132. case 37: // left arrow
  133. case 39:
  134. // right arrow
  135. if ($(e.target.parentNode).hasClass('dropdown-menuitem')) {
  136. var downKeyEvt = _.clone(e);
  137. downKeyEvt.keyCode = keyCode + 1; // left to up arrow: 38, right to down arrow:40;
  138. $(e.target).trigger(downKeyEvt); // delegate to bootstrap
  139. }
  140. break;
  141. default:
  142. break;
  143. }
  144. },
  145. _templateParams: function _templateParams() {
  146. return {
  147. id: this.viewId,
  148. items: this.items,
  149. label: this.label,
  150. icon: this.icon,
  151. hcLabel: this.hcLabel,
  152. baseClass: this.baseClass,
  153. labelOnly: this.labelOnly,
  154. showTitle: this.showTitle
  155. };
  156. },
  157. /**
  158. * Draws the MenuItem
  159. */
  160. render: function render() {
  161. var sHtml = this.dotTemplate(this._templateParams());
  162. this.$el.append(sHtml);
  163. this.$el.addClass(this.baseClass + 'ItemWrapper');
  164. this.$el.addClass('dropdown');
  165. this.$toggle = this.$el.children('.dropdown-toggle');
  166. this.$toggle.addClass(this.baseClass + 'Icon');
  167. this.$iconImage = $('<span class="' + this.icon + '"></span>');
  168. if (this.icon) {
  169. this.$toggle.prepend(this.$iconImage);
  170. }
  171. this.$toggle.attr('title', this.label);
  172. this.$toggle.addClass(this.baseClass + 'Item');
  173. this.$menu = this.$el.children('.dropdown-menu');
  174. if (this.trailing) {
  175. this.$menu.addClass('dropdown-menu-right');
  176. }
  177. var promise = this._buildDropDown();
  178. this.$menuLabel = this.$el.find('.menu-label');
  179. return promise;
  180. },
  181. _buildDropDown: function _buildDropDown() {
  182. var deferred = $.Deferred();
  183. require(['bsdropdown'], function () {
  184. this.$toggle.dropdown();
  185. /**
  186. * A callback can be passed to opening behavior
  187. */
  188. if (this.action) {
  189. this.$el.on('show.bs.dropdown', this.action.bind(this));
  190. }
  191. this._buildMenuItems();
  192. deferred.resolve(this.$el);
  193. }.bind(this));
  194. return deferred.promise();
  195. },
  196. _buildMenuItems: function _buildMenuItems() {
  197. _.each(this.items, function (item) {
  198. this._buildMenuItem(item);
  199. }.bind(this));
  200. },
  201. _getItemId: function _getItemId(name) {
  202. return this.viewId + '_' + name;
  203. },
  204. _buildMenuItem: function _buildMenuItem(item) {
  205. var itemID = this._getItemId(item.name);
  206. this.itemActions[itemID] = item.action;
  207. var itemNode = $('#' + itemID);
  208. var elem = itemNode.get(0);
  209. if (elem) {
  210. bidi.initElementForBidi(elem);
  211. }
  212. }
  213. });
  214. return MenuItem;
  215. });