Javascript  |  150行  |  3.81 KB

// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('cr.ui', function() {

  const MenuItem = cr.ui.MenuItem;

  /**
   * Creates a new menu element.
   * @param {Object=} opt_propertyBag Optional properties.
   * @constructor
   * @extends {HTMLMenuElement}
   */
  var Menu = cr.ui.define('menu');

  Menu.prototype = {
    __proto__: HTMLMenuElement.prototype,

    selectedIndex_: -1,

    /**
     * Initializes the menu element.
     */
    decorate: function() {
      this.addEventListener('mouseover', this.handleMouseOver_);
      this.addEventListener('mouseout', this.handleMouseOut_);

      // Decorate the children as menu items.
      var children = this.children;
      for (var i = 0, child; child = children[i]; i++) {
        cr.ui.decorate(child, MenuItem);
      }
    },

    /**
     * Walks up the ancestors until a menu item belonging to this menu is found.
     * @param {Element} el
     * @return {cr.ui.MenuItem} The found menu item or null.
     * @private
     */
    findMenuItem_: function(el) {
      while (el && el.parentNode != this) {
        el = el.parentNode;
      }
      return el;
    },

    /**
     * Handles mouseover events and selects the hovered item.
     * @param {Event} e The mouseover event.
     * @private
     */
    handleMouseOver_: function(e) {
      var overItem = this.findMenuItem_(e.target);
      this.selectedItem = overItem;
    },

    /**
     * Handles mouseout events and deselects any selected item.
     * @param {Event} e The mouseout event.
     * @private
     */
    handleMouseOut_: function(e) {
      this.selectedItem = null;
    },

    /**
     * The selected menu item or null if none.
     * @type {cr.ui.MenuItem}
     */
    get selectedItem() {
      return this.children[this.selectedIndex];
    },
    set selectedItem(item) {
      var index = Array.prototype.indexOf.call(this.children, item);
      this.selectedIndex = index;
    },

    /**
     * This is the function that handles keyboard navigation. This is usually
     * called by the element responsible for managing the menu.
     * @param {Event} e The keydown event object.
     * @return {boolean} Whether the event was handled be the menu.
     */
    handleKeyDown: function(e) {
      var item = this.selectedItem;

      var self = this;
      function selectNextVisible(m) {
        var children = self.children;
        var len = children.length;
        var i = self.selectedIndex;
        if (i == -1 && m == -1) {
          // Edge case when we need to go the last item fisrt.
          i = 0;
        }
        while (true) {
          i = (i + m + len) % len;
          item = children[i];
          if (item && !item.isSeparator() && !item.hidden)
            break;
        }
        if (item)
          self.selectedIndex = i;
      }

      switch (e.keyIdentifier) {
        case 'Down':
          selectNextVisible(1);
          return true;
        case 'Up':
          selectNextVisible(-1);
          return true;
        case 'Enter':
        case 'U+0020': // Space
          if (item) {
            if (cr.dispatchSimpleEvent(item, 'activate', true, true)) {
              if (item.command)
                item.command.execute();
            }
          }
          return true;
      }

      return false;
    }
  };

  function selectedIndexChanged(selectedIndex, oldSelectedIndex) {
    var oldSelectedItem = this.children[oldSelectedIndex];
    if (oldSelectedItem)
      oldSelectedItem.selected = false;
    var item = this.selectedItem;
    if (item)
      item.selected = true;
  }
  /**
   * The selected menu item.
   * @type {number}
   */
  cr.defineProperty(Menu, 'selectedIndex', cr.PropertyKind.JS,
      selectedIndexChanged);

  // Export
  return {
    Menu: Menu
  };
});