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