[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/admin/tool/lp/amd/src/ -> menubar.js (source)

   1  // This file is part of Moodle - http://moodle.org/
   2  //
   3  // Moodle is free software: you can redistribute it and/or modify
   4  // it under the terms of the GNU General Public License as published by
   5  // the Free Software Foundation, either version 3 of the License, or
   6  // (at your option) any later version.
   7  //
   8  // Moodle is distributed in the hope that it will be useful,
   9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11  // GNU General Public License for more details.
  12  //
  13  // You should have received a copy of the GNU General Public License
  14  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  15  
  16  /**
  17   * Aria menubar functionality. Enhances a simple nested list structure into a full aria widget.
  18   * Based on the open ajax example: http://oaa-accessibility.org/example/26/
  19   *
  20   * @module     tool_lp/menubar
  21   * @package    tool_lp
  22   * @copyright  2015 Damyon Wiese <damyon@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  define(['jquery'], function($) {
  26  
  27      /** @property {boolean}  Flag to indicate if we have already registered a click event handler for the document. */
  28      var documentClickHandlerRegistered = false;
  29  
  30      /** @property {boolean} Flag to indicate whether there's an active, open menu. */
  31      var menuActive = false;
  32  
  33      /**
  34       * Close all open submenus anywhere in the page (there should only ever be one open at a time).
  35       *
  36       * @method closeAllSubMenus
  37       */
  38      var closeAllSubMenus = function() {
  39          $('.tool-lp-menu .tool-lp-sub-menu').attr('aria-hidden', 'true');
  40          // Every menu's closed at this point, so set the menu active flag to false.
  41          menuActive = false;
  42      };
  43  
  44      /**
  45       * Constructor
  46       *
  47       * @param {$} menuRoot Jquery collection matching the root of the menu.
  48       * @param {Function[]} handlers, called when a menu item is chosen.
  49       */
  50      var Menubar = function(menuRoot, handlers) {
  51          // Setup private class variables.
  52          this.menuRoot = menuRoot;
  53          this.handlers = handlers;
  54          this.rootMenus = this.menuRoot.children('li');
  55          this.subMenus = this.rootMenus.children('ul');
  56          this.subMenuItems = this.subMenus.children('li');
  57          this.allItems = this.rootMenus.add(this.subMenuItems);
  58          this.activeItem = null;
  59          this.isChildOpen = false;
  60  
  61          this.keys = {
  62              tab:    9,
  63              enter:  13,
  64              esc:    27,
  65              space:  32,
  66              left:   37,
  67              up:     38,
  68              right:  39,
  69              down:   40
  70          };
  71  
  72          this.addAriaAttributes();
  73          // Add the event listeners.
  74          this.addEventListeners();
  75      };
  76  
  77      /**
  78       * Open a submenu, first it closes all other sub-menus and sets the open direction.
  79       * @method openSubMenu
  80       * @param {Node} menu
  81       */
  82      Menubar.prototype.openSubMenu = function(menu) {
  83          this.setOpenDirection();
  84          closeAllSubMenus();
  85          menu.attr('aria-hidden', 'false');
  86          // Set menu active flag to true when a menu is opened.
  87          menuActive = true;
  88      };
  89  
  90  
  91      /**
  92       * Bind the event listeners to the DOM
  93       * @method addEventListeners
  94       */
  95      Menubar.prototype.addEventListeners = function() {
  96          var currentThis = this;
  97  
  98          // When clicking outside the menubar.
  99          if (documentClickHandlerRegistered === false) {
 100              $(document).click(function() {
 101                  // Check if a menu is opened.
 102                  if (menuActive) {
 103                      // Close menu.
 104                      closeAllSubMenus();
 105                  }
 106              });
 107              // Set this flag to true so that we won't need to add a document click handler for the other Menubar instances.
 108              documentClickHandlerRegistered = true;
 109          }
 110  
 111          // Hovers.
 112          this.subMenuItems.mouseenter(function() {
 113              $(this).addClass('menu-hover');
 114              return true;
 115          });
 116  
 117          this.subMenuItems.mouseout(function() {
 118              $(this).removeClass('menu-hover');
 119              return true;
 120          });
 121  
 122          // Mouse listeners.
 123          this.allItems.click(function(e) {
 124              return currentThis.handleClick($(this), e);
 125          });
 126  
 127          // Key listeners.
 128          this.allItems.keydown(function(e) {
 129              return currentThis.handleKeyDown($(this), e);
 130          });
 131  
 132          this.allItems.focus(function() {
 133              return currentThis.handleFocus($(this));
 134          });
 135  
 136          this.allItems.blur(function() {
 137              return currentThis.handleBlur($(this));
 138          });
 139      };
 140  
 141      /**
 142       * Process click events for the top menus.
 143       *
 144       * @method handleClick
 145       * @param {Object} item is the jquery object of the item firing the event
 146       * @param {Event} e is the associated event object
 147       * @return {boolean} Returns false
 148       */
 149      Menubar.prototype.handleClick = function(item, e) {
 150          e.stopPropagation();
 151  
 152          var parentUL = item.parent();
 153  
 154          if (parentUL.is('.tool-lp-menu')) {
 155              // Toggle the child menu open/closed.
 156              if (item.children('ul').first().attr('aria-hidden') == 'true') {
 157                  this.openSubMenu(item.children('ul').first());
 158              } else {
 159                  item.children('ul').first().attr('aria-hidden', 'true');
 160              }
 161          } else {
 162              // Remove hover and focus styling.
 163              this.allItems.removeClass('menu-hover menu-focus');
 164  
 165              // Clear the active item.
 166              this.activeItem = null;
 167  
 168              // Close the menu.
 169              this.menuRoot.find('ul').not('.root-level').attr('aria-hidden', 'true');
 170              // Follow any link, or call the click handlers.
 171              var anchor = item.find('a').first();
 172              var clickEvent = new $.Event('click');
 173              clickEvent.target = anchor;
 174              var eventHandled = false;
 175              if (this.handlers) {
 176                  $.each(this.handlers, function(selector, handler) {
 177                      if (eventHandled) {
 178                          return;
 179                      }
 180                      if (item.find(selector).length > 0) {
 181                          var callable = $.proxy(handler, anchor);
 182                          // False means stop propogatting events.
 183                          eventHandled = (callable(clickEvent) === false) || clickEvent.isDefaultPrevented();
 184                      }
 185                  });
 186              }
 187              // If we didn't find a handler, and the HREF is # that probably means that
 188              // we are handling it from somewhere else. Let's just do nothing in that case.
 189              if (!eventHandled && anchor.attr('href') !== '#') {
 190                  window.location.href = anchor.attr('href');
 191              }
 192          }
 193          return false;
 194      };
 195  
 196      /*
 197       * Process focus events for the menu.
 198       *
 199       * @method handleFocus
 200       * @param {Object} item is the jquery object of the item firing the event
 201       * @return boolean Returns false
 202       */
 203      Menubar.prototype.handleFocus = function(item) {
 204  
 205          // If activeItem is null, we are getting focus from outside the menu. Store
 206          // the item that triggered the event.
 207          if (this.activeItem === null) {
 208              this.activeItem = item;
 209          } else if (item[0] != this.activeItem[0]) {
 210              return true;
 211          }
 212  
 213          // Get the set of jquery objects for all the parent items of the active item.
 214          var parentItems = this.activeItem.parentsUntil('ul.tool-lp-menu').filter('li');
 215  
 216          // Remove focus styling from all other menu items.
 217          this.allItems.removeClass('menu-focus');
 218  
 219          // Add focus styling to the active item.
 220          this.activeItem.addClass('menu-focus');
 221  
 222          // Add focus styling to all parent items.
 223          parentItems.addClass('menu-focus');
 224  
 225          // If the bChildOpen flag has been set, open the active item's child menu (if applicable).
 226          if (this.isChildOpen === true) {
 227  
 228              var itemUL = item.parent();
 229  
 230              // If the itemUL is a root-level menu and item is a parent item,
 231              // show the child menu.
 232              if (itemUL.is('.tool-lp-menu') && (item.attr('aria-haspopup') == 'true')) {
 233                  this.openSubMenu(item.children('ul').first());
 234              }
 235          }
 236  
 237          return true;
 238      };
 239  
 240      /*
 241       * Process blur events for the menu.
 242       *
 243       * @method handleBlur
 244       * @param {Object} item is the jquery object of the item firing the event
 245       * @return boolean Returns false
 246       */
 247      Menubar.prototype.handleBlur = function(item) {
 248          item.removeClass('menu-focus');
 249  
 250          return true;
 251      };
 252  
 253      /*
 254       * Determine if the menu should open to the left, or the right,
 255       * based on the screen size and menu position.
 256       * @method setOpenDirection
 257       */
 258      Menubar.prototype.setOpenDirection = function() {
 259          var pos = this.menuRoot.offset();
 260          var isRTL = $(document.body).hasClass('dir-rtl');
 261          var openLeft = false;
 262          var heightmenuRoot = this.rootMenus.outerHeight();
 263          var widthmenuRoot = this.rootMenus.outerWidth();
 264          // Sometimes the menuMinWidth is not enough to figure out if menu exceeds the window width.
 265          // So we have to calculate the real menu width.
 266          var subMenuContainer = this.rootMenus.find('ul.tool-lp-sub-menu');
 267  
 268          // Reset margins.
 269          subMenuContainer.css('margin-right', '');
 270          subMenuContainer.css('margin-left', '');
 271          subMenuContainer.css('margin-top', '');
 272  
 273          subMenuContainer.attr('aria-hidden', false);
 274          var menuRealWidth = subMenuContainer.outerWidth(),
 275              menuRealHeight = subMenuContainer.outerHeight();
 276  
 277          var margintop = null,
 278              marginright = null,
 279              marginleft = null;
 280          var top = pos.top - $(window).scrollTop();
 281          // Top is the same for RTL and LTR.
 282          if (top + menuRealHeight > $(window).height()) {
 283              margintop = menuRealHeight + heightmenuRoot;
 284              subMenuContainer.css('margin-top', '-' + margintop + 'px');
 285          }
 286  
 287          if (isRTL) {
 288              if (pos.left - menuRealWidth < 0) {
 289                  marginright = menuRealWidth - widthmenuRoot;
 290                  subMenuContainer.css('margin-right', '-' + marginright + 'px');
 291              }
 292          } else {
 293              if (pos.left + menuRealWidth > $(window).width()) {
 294                  marginleft = menuRealWidth - widthmenuRoot;
 295                  subMenuContainer.css('margin-left', '-' + marginleft + 'px');
 296              }
 297          }
 298  
 299          if (openLeft) {
 300              this.menuRoot.addClass('tool-lp-menu-open-left');
 301          } else {
 302              this.menuRoot.removeClass('tool-lp-menu-open-left');
 303          }
 304  
 305      };
 306  
 307      /*
 308       * Process keyDown events for the menu.
 309       *
 310       * @method handleKeyDown
 311       * @param {Object} item is the jquery object of the item firing the event
 312       * @param {Event} e is the associated event object
 313       * @return boolean Returns false if consuming the event
 314       */
 315      Menubar.prototype.handleKeyDown = function(item, e) {
 316  
 317          if (e.altKey || e.ctrlKey) {
 318              // Modifier key pressed: Do not process.
 319              return true;
 320          }
 321  
 322          switch (e.keyCode) {
 323              case this.keys.tab: {
 324  
 325                  // Hide all menu items and update their aria attributes.
 326                  this.menuRoot.find('ul').attr('aria-hidden', 'true');
 327  
 328                  // Remove focus styling from all menu items.
 329                  this.allItems.removeClass('menu-focus');
 330  
 331                  this.activeItem = null;
 332  
 333                  this.isChildOpen = false;
 334  
 335                  break;
 336              }
 337              case this.keys.esc: {
 338                  var itemUL = item.parent();
 339  
 340                  if (itemUL.is('.tool-lp-menu')) {
 341                      // Hide the child menu and update the aria attributes.
 342                      item.children('ul').first().attr('aria-hidden', 'true');
 343                  } else {
 344  
 345                      // Move up one level.
 346                      this.activeItem = itemUL.parent();
 347  
 348                      // Reset the isChildOpen flag.
 349                      this.isChildOpen = false;
 350  
 351                      // Set focus on the new item.
 352                      this.activeItem.focus();
 353  
 354                      // Hide the active menu and update the aria attributes.
 355                      itemUL.attr('aria-hidden', 'true');
 356                  }
 357  
 358                  e.stopPropagation();
 359                  return false;
 360              }
 361              case this.keys.enter:
 362              case this.keys.space: {
 363                  // Trigger click handler.
 364                  return this.handleClick(item, e);
 365              }
 366  
 367              case this.keys.left: {
 368  
 369                  this.activeItem = this.moveToPrevious(item);
 370  
 371                  this.activeItem.focus();
 372  
 373                  e.stopPropagation();
 374                  return false;
 375              }
 376              case this.keys.right: {
 377  
 378                  this.activeItem = this.moveToNext(item);
 379  
 380                  this.activeItem.focus();
 381  
 382                  e.stopPropagation();
 383                  return false;
 384              }
 385              case this.keys.up: {
 386  
 387                  this.activeItem = this.moveUp(item);
 388  
 389                  this.activeItem.focus();
 390  
 391                  e.stopPropagation();
 392                  return false;
 393              }
 394              case this.keys.down: {
 395  
 396                  this.activeItem = this.moveDown(item);
 397  
 398                  this.activeItem.focus();
 399  
 400                  e.stopPropagation();
 401                  return false;
 402              }
 403          }
 404  
 405          return true;
 406  
 407      };
 408  
 409  
 410      /**
 411       * Move to the next menu level.
 412       * This will be either the next root-level menu or the child of a menu parent. If
 413       * at the root level and the active item is the last in the menu, this function will loop
 414       * to the first menu item.
 415       *
 416       * If the menu is a horizontal menu, the first child element of the newly selected menu will
 417       * be selected
 418       *
 419       * @method moveToNext
 420       * @param {Object} item is the active menu item
 421       * @return {Object} Returns the item to move to. Returns item is no move is possible
 422       */
 423      Menubar.prototype.moveToNext = function(item) {
 424          // Item's containing menu.
 425          var itemUL = item.parent();
 426  
 427          // The items in the currently active menu.
 428          var menuItems = itemUL.children('li');
 429  
 430          // The number of items in the active menu.
 431          var menuNum = menuItems.length;
 432          // The items index in its menu.
 433          var menuIndex = menuItems.index(item);
 434          var newItem = null;
 435          var childMenu = null;
 436  
 437          if (itemUL.is('.tool-lp-menu')) {
 438              // This is the root level move to next sibling. This will require closing
 439              // the current child menu and opening the new one.
 440  
 441              if (menuIndex < menuNum - 1) {
 442                  // Not the last root menu.
 443                  newItem = item.next();
 444              } else { // Wrap to first item.
 445                  newItem = menuItems.first();
 446              }
 447  
 448              // Close the current child menu (if applicable).
 449              if (item.attr('aria-haspopup') == 'true') {
 450  
 451                  childMenu = item.children('ul').first();
 452  
 453                  if (childMenu.attr('aria-hidden') == 'false') {
 454                      // Update the child menu's aria-hidden attribute.
 455                      childMenu.attr('aria-hidden', 'true');
 456                      this.isChildOpen = true;
 457                  }
 458              }
 459  
 460              // Remove the focus styling from the current menu.
 461              item.removeClass('menu-focus');
 462  
 463              // Open the new child menu (if applicable).
 464              if ((newItem.attr('aria-haspopup') === 'true') && (this.isChildOpen === true)) {
 465  
 466                  childMenu = newItem.children('ul').first();
 467  
 468                  // Update the child's aria-hidden attribute.
 469                  this.openSubMenu(childMenu);
 470              }
 471          } else {
 472              // This is not the root level. If there is a child menu to be moved into, do that;
 473              // otherwise, move to the next root-level menu if there is one.
 474              if (item.attr('aria-haspopup') == 'true') {
 475  
 476                  childMenu = item.children('ul').first();
 477  
 478                  newItem = childMenu.children('li').first();
 479  
 480                  // Show the child menu and update its aria attributes.
 481                  this.openSubMenu(childMenu);
 482              } else {
 483                  // At deepest level, move to the next root-level menu.
 484  
 485                  var parentMenus = null;
 486                  var rootItem = null;
 487  
 488                  // Get list of all parent menus for item, up to the root level.
 489                  parentMenus = item.parentsUntil('ul.tool-lp-menu').filter('ul').not('.tool-lp-menu');
 490  
 491                  // Hide the current menu and update its aria attributes accordingly.
 492                  parentMenus.attr('aria-hidden', 'true');
 493  
 494                  // Remove the focus styling from the active menu.
 495                  parentMenus.find('li').removeClass('menu-focus');
 496                  parentMenus.last().parent().removeClass('menu-focus');
 497  
 498                  // The containing root for the menu.
 499                  rootItem = parentMenus.last().parent();
 500  
 501                  menuIndex = this.rootMenus.index(rootItem);
 502  
 503                  // If this is not the last root menu item, move to the next one.
 504                  if (menuIndex < this.rootMenus.length - 1) {
 505                      newItem = rootItem.next();
 506                  } else {
 507                      // Loop.
 508                      newItem = this.rootMenus.first();
 509                  }
 510  
 511                  // Add the focus styling to the new menu.
 512                  newItem.addClass('menu-focus');
 513  
 514                  if (newItem.attr('aria-haspopup') == 'true') {
 515                      childMenu = newItem.children('ul').first();
 516  
 517                      newItem = childMenu.children('li').first();
 518  
 519                      // Show the child menu and update it's aria attributes.
 520                      this.openSubMenu(childMenu);
 521                      this.isChildOpen = true;
 522                  }
 523              }
 524          }
 525  
 526          return newItem;
 527      };
 528  
 529      /**
 530       * Member function to move to the previous menu level.
 531       * This will be either the previous root-level menu or the child of a menu parent. If
 532       * at the root level and the active item is the first in the menu, this function will loop
 533       * to the last menu item.
 534       *
 535       * If the menu is a horizontal menu, the first child element of the newly selected menu will
 536       * be selected
 537       *
 538       * @method moveToPrevious
 539       * @param {Object} item is the active menu item
 540       * @return {Object} Returns the item to move to. Returns item is no move is possible
 541       */
 542      Menubar.prototype.moveToPrevious = function(item) {
 543          // Item's containing menu.
 544          var itemUL = item.parent();
 545          // The items in the currently active menu.
 546          var menuItems = itemUL.children('li');
 547          // The items index in its menu.
 548          var menuIndex = menuItems.index(item);
 549          var newItem = null;
 550          var childMenu = null;
 551  
 552          if (itemUL.is('.tool-lp-menu')) {
 553              // This is the root level move to previous sibling. This will require closing
 554              // the current child menu and opening the new one.
 555  
 556              if (menuIndex > 0) {
 557                  // Not the first root menu.
 558                  newItem = item.prev();
 559              } else {
 560                  // Wrap to last item.
 561                  newItem = menuItems.last();
 562              }
 563  
 564              // Close the current child menu (if applicable).
 565              if (item.attr('aria-haspopup') == 'true') {
 566                  childMenu = item.children('ul').first();
 567  
 568                  if (childMenu.attr('aria-hidden') == 'false') {
 569                      // Update the child menu's aria-hidden attribute.
 570                      childMenu.attr('aria-hidden', 'true');
 571                      this.isChildOpen = true;
 572                  }
 573              }
 574  
 575              // Remove the focus styling from the current menu.
 576              item.removeClass('menu-focus');
 577  
 578              // Open the new child menu (if applicable).
 579              if ((newItem.attr('aria-haspopup') === 'true') && (this.isChildOpen === true)) {
 580  
 581                  childMenu = newItem.children('ul').first();
 582  
 583                  // Update the child's aria-hidden attribute.
 584                  this.openSubMenu(childMenu);
 585  
 586              }
 587          } else {
 588              // This is not the root level. If there is a parent menu that is not the
 589              // root menu, move up one level; otherwise, move to first item of the previous
 590              // root menu.
 591  
 592              var parentLI = itemUL.parent();
 593              var parentUL = parentLI.parent();
 594  
 595              // If this is a vertical menu or is not the first child menu
 596              // of the root-level menu, move up one level.
 597              if (!parentUL.is('.tool-lp-menu')) {
 598  
 599                  newItem = itemUL.parent();
 600  
 601                  // Hide the active menu and update aria-hidden.
 602                  itemUL.attr('aria-hidden', 'true');
 603  
 604                  // Remove the focus highlight from the item.
 605                  item.removeClass('menu-focus');
 606  
 607              } else {
 608                  // Move to previous root-level menu.
 609  
 610                  // Hide the current menu and update the aria attributes accordingly.
 611                  itemUL.attr('aria-hidden', 'true');
 612  
 613                  // Remove the focus styling from the active menu.
 614                  item.removeClass('menu-focus');
 615                  parentLI.removeClass('menu-focus');
 616  
 617                  menuIndex = this.rootMenus.index(parentLI);
 618  
 619                  if (menuIndex > 0) {
 620                      // Move to the previous root-level menu.
 621                      newItem = parentLI.prev();
 622                  } else {
 623                      // Loop to last root-level menu.
 624                      newItem = this.rootMenus.last();
 625                  }
 626  
 627                  // Add the focus styling to the new menu.
 628                  newItem.addClass('menu-focus');
 629  
 630                  if (newItem.attr('aria-haspopup') == 'true') {
 631                      childMenu = newItem.children('ul').first();
 632  
 633                      // Show the child menu and update it's aria attributes.
 634                      this.openSubMenu(childMenu);
 635                      this.isChildOpen = true;
 636  
 637                      newItem = childMenu.children('li').first();
 638                  }
 639              }
 640          }
 641  
 642          return newItem;
 643      };
 644  
 645      /**
 646       * Member function to select the next item in a menu.
 647       * If the active item is the last in the menu, this function will loop to the
 648       * first menu item.
 649       *
 650       * @method moveDown
 651       * @param {Object} item is the active menu item
 652       * @param {String} startChr is the character to attempt to match against the beginning of the
 653       *                          menu item titles. If found, focus moves to the next menu item beginning with that character.
 654       * @return {Object} Returns the item to move to. Returns item is no move is possible
 655       */
 656      Menubar.prototype.moveDown = function(item, startChr) {
 657          // Item's containing menu.
 658          var itemUL = item.parent();
 659          // The items in the currently active menu.
 660          var menuItems = itemUL.children('li').not('.separator');
 661          // The number of items in the active menu.
 662          var menuNum = menuItems.length;
 663          // The items index in its menu.
 664          var menuIndex = menuItems.index(item);
 665          var newItem = null;
 666          var newItemUL = null;
 667  
 668          if (itemUL.is('.tool-lp-menu')) {
 669              // This is the root level menu.
 670  
 671              if (item.attr('aria-haspopup') != 'true') {
 672                  // No child menu to move to.
 673                  return item;
 674              }
 675  
 676              // Move to the first item in the child menu.
 677              newItemUL = item.children('ul').first();
 678              newItem = newItemUL.children('li').first();
 679  
 680              // Make sure the child menu is visible.
 681              this.openSubMenu(newItemUL);
 682  
 683              return newItem;
 684          }
 685  
 686          // If $item is not the last item in its menu, move to the next item. If startChr is specified, move
 687          // to the next item with a title that begins with that character.
 688          if (startChr) {
 689              var match = false;
 690              var curNdx = menuIndex + 1;
 691  
 692              // Check if the active item was the last one on the list.
 693              if (curNdx == menuNum) {
 694                  curNdx = 0;
 695              }
 696  
 697              // Iterate through the menu items (starting from the current item and wrapping) until a match is found
 698              // or the loop returns to the current menu item.
 699              while (curNdx != menuIndex) {
 700  
 701                  var titleChr = menuItems.eq(curNdx).html().charAt(0);
 702  
 703                  if (titleChr.toLowerCase() == startChr) {
 704                      match = true;
 705                      break;
 706                  }
 707  
 708                  curNdx = curNdx + 1;
 709  
 710                  if (curNdx == menuNum) {
 711                      // Reached the end of the list, start again at the beginning.
 712                      curNdx = 0;
 713                  }
 714              }
 715  
 716              if (match === true) {
 717                  newItem = menuItems.eq(curNdx);
 718  
 719                  // Remove the focus styling from the current item.
 720                  item.removeClass('menu-focus');
 721  
 722                  return newItem;
 723              } else {
 724                  return item;
 725              }
 726          } else {
 727              if (menuIndex < menuNum - 1) {
 728                  newItem = menuItems.eq(menuIndex + 1);
 729              } else {
 730                  newItem = menuItems.first();
 731              }
 732          }
 733  
 734          // Remove the focus styling from the current item.
 735          item.removeClass('menu-focus');
 736  
 737          return newItem;
 738      };
 739  
 740      /**
 741       * Function moveUp() is a member function to select the previous item in a menu.
 742       * If the active item is the first in the menu, this function will loop to the
 743       * last menu item.
 744       *
 745       * @method moveUp
 746       * @param {Object} item is the active menu item
 747       * @return {Object} Returns the item to move to. Returns item is no move is possible
 748       */
 749      Menubar.prototype.moveUp = function(item) {
 750          // Item's containing menu.
 751          var itemUL = item.parent();
 752          // The items in the currently active menu.
 753          var menuItems = itemUL.children('li').not('.separator');
 754          // The items index in its menu.
 755          var menuIndex = menuItems.index(item);
 756          var newItem = null;
 757  
 758          if (itemUL.is('.tool-lp-menu')) {
 759              // This is the root level menu.
 760              // Nothing to do.
 761              return item;
 762          }
 763  
 764          // If item is not the first item in its menu, move to the previous item.
 765          if (menuIndex > 0) {
 766              newItem = menuItems.eq(menuIndex - 1);
 767          } else {
 768              // Loop to top of menu.
 769              newItem = menuItems.last();
 770          }
 771  
 772          // Remove the focus styling from the current item.
 773          item.removeClass('menu-focus');
 774  
 775          return newItem;
 776      };
 777  
 778      /**
 779       * Enhance the dom with aria attributes.
 780       * @method addAriaAttributes
 781       */
 782      Menubar.prototype.addAriaAttributes = function() {
 783          this.menuRoot.attr('role', 'menubar');
 784          this.rootMenus.attr('role', 'menuitem');
 785          this.rootMenus.attr('tabindex', '0');
 786          this.rootMenus.attr('aria-haspopup', 'true');
 787          this.subMenus.attr('role', 'menu');
 788          this.subMenus.attr('aria-hidden', 'true');
 789          this.subMenuItems.attr('role', 'menuitem');
 790          this.subMenuItems.attr('tabindex', '-1');
 791  
 792          // For CSS styling and effects.
 793          this.menuRoot.addClass('tool-lp-menu');
 794          this.allItems.addClass('tool-lp-menu-item');
 795          this.rootMenus.addClass('tool-lp-root-menu');
 796          this.subMenus.addClass('tool-lp-sub-menu');
 797      };
 798  
 799      return /** @alias module:tool_lp/menubar */ {
 800          /**
 801           * Create a menu bar object for every node matching the selector.
 802           *
 803           * The expected DOM structure is shown below.
 804           * <ul> <- This is the target of the selector parameter.
 805           *   <li> <- This is repeated for each top level menu.
 806           *      Text <- This is the text for the top level menu.
 807           *      <ul> <- This is a list of the entries in this top level menu.
 808           *         <li> <- This is repeated for each menu entry.
 809           *            <a href="someurl">Choice 1</a> <- The anchor for the menu.
 810           *         </li>
 811           *      </ul>
 812           *   </li>
 813           * </ul>
 814           *
 815           * @method enhance
 816           * @param {String} selector - The selector for the outer most menu node.
 817           * @param {Function} handler - Javascript handler for when a menu item was chosen. If the
 818           *                             handler returns true (or does not exist), the
 819           *                             menu will look for an anchor with a link to follow.
 820           *                             For example, if the menu entry has a "data-action" attribute
 821           *                             and we want to call a javascript function when that entry is chosen,
 822           *                             we could pass a list of handlers like this:
 823           *                             { "[data-action='add']" : callAddFunction }
 824           */
 825          enhance: function(selector, handler) {
 826              $(selector).each(function(index, element) {
 827                  var menuRoot = $(element);
 828                  // Don't enhance the same menu twice.
 829                  if (menuRoot.data("menubarEnhanced") !== true) {
 830                      (new Menubar(menuRoot, handler));
 831                      menuRoot.data("menubarEnhanced", true);
 832                  }
 833              });
 834          },
 835  
 836          /**
 837           * Handy function to close all open menus anywhere on the page.
 838           * @method closeAll
 839           */
 840          closeAll: closeAllSubMenus
 841      };
 842  });


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