1ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// Use of this source code is governed by a BSD-style license that can be
3c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch// found in the LICENSE file.
4c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
5c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdochcr.define('cr.ui', function() {
6c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  const Menu = cr.ui.Menu;
7c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  const positionPopupAroundElement = cr.ui.positionPopupAroundElement;
8c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
9c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  /**
10c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * Creates a new menu button element.
11c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @param {Object=} opt_propertyBag Optional properties.
12c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @constructor
13c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   * @extends {HTMLButtonElement}
14c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch   */
15c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  var MenuButton = cr.ui.define('button');
16c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
17c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  MenuButton.prototype = {
18c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    __proto__: HTMLButtonElement.prototype,
19c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
20c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
21c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Initializes the menu button.
22c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
23c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    decorate: function() {
24c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.addEventListener('mousedown', this);
25c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.addEventListener('keydown', this);
26c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
27c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var menu;
28c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if ((menu = this.getAttribute('menu')))
29c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        this.menu = menu;
30ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
31ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // An event tracker for events we only connect to while the menu is
32ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      // displayed.
33ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.showingEvents_ = new EventTracker();
34c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
35c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
36c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
37c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * The menu associated with the menu button.
38c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @type {cr.ui.Menu}
39c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
40c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    get menu() {
41c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      return this.menu_;
42c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
43c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    set menu(menu) {
44c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (typeof menu == 'string' && menu[0] == '#') {
45c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        menu = this.ownerDocument.getElementById(menu.slice(1));
46c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        cr.ui.decorate(menu, Menu);
47c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
48c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
49c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.menu_ = menu;
50c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (menu) {
51c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        if (menu.id)
52c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          this.setAttribute('menu', '#' + menu.id);
53c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
54c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
55c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
56c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
57c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Handles event callbacks.
58c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @param {Event} e The event object.
59c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
60c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    handleEvent: function(e) {
61c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      if (!this.menu)
62c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        return;
63c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
64c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      switch (e.type) {
65c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'mousedown':
66c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          if (e.currentTarget == this.ownerDocument) {
67c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            if (!this.contains(e.target) && !this.menu.contains(e.target))
68c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              this.hideMenu();
69c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            else
70c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              e.preventDefault();
71c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          } else {
72c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            if (this.isMenuShown()) {
73c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              this.hideMenu();
74c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            } else if (e.button == 0) {  // Only show the menu when using left
75c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch                                         // mouse button.
76c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              this.showMenu();
77c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              // Prevent the button from stealing focus on mousedown.
78c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch              e.preventDefault();
79c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            }
80c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          }
81c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          break;
82c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'keydown':
83c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          this.handleKeyDown(e);
84c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          // If the menu is visible we let it handle all the keyboard events.
85c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          if (this.isMenuShown() && e.currentTarget == this.ownerDocument) {
86c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            this.menu.handleKeyDown(e);
87c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            e.preventDefault();
88c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            e.stopPropagation();
89c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          }
90c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          break;
91c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
92c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'activate':
93c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'blur':
94c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'resize':
95c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          this.hideMenu();
96c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          break;
97c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
98c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
99c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
100c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
101c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Shows the menu.
102c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
103c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    showMenu: function() {
104ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.hideMenu();
105ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
106c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.menu.style.display = 'block';
107c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.setAttribute('menu-shown', '');
108c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
109c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      // when the menu is shown we steal all keyboard events.
110c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var doc = this.ownerDocument;
111c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      var win = doc.defaultView;
112ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.showingEvents_.add(doc, 'keydown', this, true);
113ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.showingEvents_.add(doc, 'mousedown', this, true);
114ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.showingEvents_.add(doc, 'blur', this, true);
115ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.showingEvents_.add(win, 'resize', this);
116ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.showingEvents_.add(this.menu, 'activate', this);
117c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.positionMenu_();
118c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
119c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
120c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
121ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * Hides the menu. If your menu can go out of scope, make sure to call this
122ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen     * first.
123c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
124c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    hideMenu: function() {
125ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      if (!this.isMenuShown())
126ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen        return;
127ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
128c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.removeAttribute('menu-shown');
129c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.menu.style.display = 'none';
130ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen
131ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      this.showingEvents_.removeAll();
132c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      this.menu.selectedIndex = -1;
133c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
134c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
135c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
136c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Whether the menu is shown.
137c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
138c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    isMenuShown: function() {
139ddb351dbec246cf1fab5ec20d2d5520909041de1Kristian Monsen      return this.hasAttribute('menu-shown');
140c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
141c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
142c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
143c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Positions the menu below the menu button. At this point we do not use any
144c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * advanced positioning logic to ensure the menu fits in the viewport.
145c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * @private
146c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
147c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    positionMenu_: function() {
148c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      positionPopupAroundElement(this, this.menu, cr.ui.AnchorType.BELOW);
149c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    },
150c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
151c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    /**
152c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     * Handles the keydown event for the menu button.
153c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch     */
154c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    handleKeyDown: function(e) {
155c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      switch (e.keyIdentifier) {
156c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'Down':
157c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'Up':
158c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'Enter':
159c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'U+0020': // Space
160c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          if (!this.isMenuShown())
161c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch            this.showMenu();
162c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          e.preventDefault();
163c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          break;
164c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'Esc':
165c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch        case 'U+001B': // Maybe this is remote desktop playing a prank?
166c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          this.hideMenu();
167c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch          break;
168c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch      }
169c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    }
170c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  };
171c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch
172c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  // Export
173c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  return {
174c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch    MenuButton: MenuButton
175c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch  };
176c407dc5cd9bdc5668497f21b26b09d988ab439deBen Murdoch});
177