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