• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5cr.define('cr.ui', function() {
6
7  const MenuItem = cr.ui.MenuItem;
8
9  /**
10   * Creates a new menu element.
11   * @param {Object=} opt_propertyBag Optional properties.
12   * @constructor
13   * @extends {HTMLMenuElement}
14   */
15  var Menu = cr.ui.define('menu');
16
17  Menu.prototype = {
18    __proto__: HTMLMenuElement.prototype,
19
20    selectedIndex_: -1,
21
22    /**
23     * Initializes the menu element.
24     */
25    decorate: function() {
26      this.addEventListener('mouseover', this.handleMouseOver_);
27      this.addEventListener('mouseout', this.handleMouseOut_);
28
29      // Decorate the children as menu items.
30      var children = this.children;
31      for (var i = 0, child; child = children[i]; i++) {
32        cr.ui.decorate(child, MenuItem);
33      }
34    },
35
36    /**
37     * Walks up the ancestors until a menu item belonging to this menu is found.
38     * @param {Element} el
39     * @return {cr.ui.MenuItem} The found menu item or null.
40     * @private
41     */
42    findMenuItem_: function(el) {
43      while (el && el.parentNode != this) {
44        el = el.parentNode;
45      }
46      return el;
47    },
48
49    /**
50     * Handles mouseover events and selects the hovered item.
51     * @param {Event} e The mouseover event.
52     * @private
53     */
54    handleMouseOver_: function(e) {
55      var overItem = this.findMenuItem_(e.target);
56      this.selectedItem = overItem;
57    },
58
59    /**
60     * Handles mouseout events and deselects any selected item.
61     * @param {Event} e The mouseout event.
62     * @private
63     */
64    handleMouseOut_: function(e) {
65      this.selectedItem = null;
66    },
67
68    /**
69     * The selected menu item or null if none.
70     * @type {cr.ui.MenuItem}
71     */
72    get selectedItem() {
73      return this.children[this.selectedIndex];
74    },
75    set selectedItem(item) {
76      var index = Array.prototype.indexOf.call(this.children, item);
77      this.selectedIndex = index;
78    },
79
80    /**
81     * This is the function that handles keyboard navigation. This is usually
82     * called by the element responsible for managing the menu.
83     * @param {Event} e The keydown event object.
84     * @return {boolean} Whether the event was handled be the menu.
85     */
86    handleKeyDown: function(e) {
87      var item = this.selectedItem;
88
89      var self = this;
90      function selectNextVisible(m) {
91        var children = self.children;
92        var len = children.length;
93        var i = self.selectedIndex;
94        if (i == -1 && m == -1) {
95          // Edge case when we need to go the last item fisrt.
96          i = 0;
97        }
98        while (true) {
99          i = (i + m + len) % len;
100          item = children[i];
101          if (item && !item.isSeparator() && !item.hidden)
102            break;
103        }
104        if (item)
105          self.selectedIndex = i;
106      }
107
108      switch (e.keyIdentifier) {
109        case 'Down':
110          selectNextVisible(1);
111          return true;
112        case 'Up':
113          selectNextVisible(-1);
114          return true;
115        case 'Enter':
116        case 'U+0020': // Space
117          if (item) {
118            if (cr.dispatchSimpleEvent(item, 'activate', true, true)) {
119              if (item.command)
120                item.command.execute();
121            }
122          }
123          return true;
124      }
125
126      return false;
127    }
128  };
129
130  function selectedIndexChanged(selectedIndex, oldSelectedIndex) {
131    var oldSelectedItem = this.children[oldSelectedIndex];
132    if (oldSelectedItem)
133      oldSelectedItem.selected = false;
134    var item = this.selectedItem;
135    if (item)
136      item.selected = true;
137  }
138  /**
139   * The selected menu item.
140   * @type {number}
141   */
142  cr.defineProperty(Menu, 'selectedIndex', cr.PropertyKind.JS,
143      selectedIndexChanged);
144
145  // Export
146  return {
147    Menu: Menu
148  };
149});
150