1// Copyright (c) 2012 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 */ var 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 of |el| until a menu item belonging to this menu
38     * is found.
39     * @param {Element} el The element to start searching from.
40     * @return {cr.ui.MenuItem} The found menu item or null.
41     * @private
42     */
43    findMenuItem_: function(el) {
44      while (el && el.parentNode != this) {
45        el = el.parentNode;
46      }
47      return el;
48    },
49
50    /**
51     * Handles mouseover events and selects the hovered item.
52     * @param {Event} e The mouseover event.
53     * @private
54     */
55    handleMouseOver_: function(e) {
56      var overItem = this.findMenuItem_(e.target);
57      this.selectedItem = overItem;
58    },
59
60    /**
61     * Handles mouseout events and deselects any selected item.
62     * @param {Event} e The mouseout event.
63     * @private
64     */
65    handleMouseOut_: function(e) {
66      this.selectedItem = null;
67    },
68
69    /**
70     * The selected menu item or null if none.
71     * @type {cr.ui.MenuItem}
72     */
73    get selectedItem() {
74      return this.children[this.selectedIndex];
75    },
76    set selectedItem(item) {
77      var index = Array.prototype.indexOf.call(this.children, item);
78      this.selectedIndex = index;
79    },
80
81    /**
82     * This is the function that handles keyboard navigation. This is usually
83     * called by the element responsible for managing the menu.
84     * @param {Event} e The keydown event object.
85     * @return {boolean} Whether the event was handled be the menu.
86     */
87    handleKeyDown: function(e) {
88      var item = this.selectedItem;
89
90      var self = this;
91      function selectNextVisible(m) {
92        var children = self.children;
93        var len = children.length;
94        var i = self.selectedIndex;
95        if (i == -1 && m == -1) {
96          // Edge case when we need to go the last item fisrt.
97          i = 0;
98        }
99        while (true) {
100          i = (i + m + len) % len;
101          item = children[i];
102          if (item && !item.isSeparator() && !item.hidden)
103            break;
104        }
105        if (item)
106          self.selectedIndex = i;
107      }
108
109      switch (e.keyIdentifier) {
110        case 'Down':
111          selectNextVisible(1);
112          return true;
113        case 'Up':
114          selectNextVisible(-1);
115          return true;
116        case 'Enter':
117        case 'U+0020': // Space
118          if (item) {
119            if (cr.dispatchSimpleEvent(item, 'activate', true, true)) {
120              if (item.command)
121                item.command.execute();
122            }
123          }
124          return true;
125      }
126
127      return false;
128    }
129  };
130
131  function selectedIndexChanged(selectedIndex, oldSelectedIndex) {
132    var oldSelectedItem = this.children[oldSelectedIndex];
133    if (oldSelectedItem)
134      oldSelectedItem.selected = false;
135    var item = this.selectedItem;
136    if (item)
137      item.selected = true;
138  }
139  /**
140   * The selected menu item.
141   * @type {number}
142   */
143  cr.defineProperty(Menu, 'selectedIndex', cr.PropertyKind.JS,
144      selectedIndexChanged);
145
146  // Export
147  return {
148    Menu: Menu
149  };
150});
151