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 /** @const */ 7 var Menu = cr.ui.Menu; 8 /** @const */ 9 var positionPopupAroundElement = cr.ui.positionPopupAroundElement; 10 11 /** 12 * Creates a new menu button element. 13 * @param {Object=} opt_propertyBag Optional properties. 14 * @constructor 15 * @extends {HTMLButtonElement} 16 */ 17 var MenuButton = cr.ui.define('button'); 18 19 MenuButton.prototype = { 20 __proto__: HTMLButtonElement.prototype, 21 22 /** 23 * Initializes the menu button. 24 */ 25 decorate: function() { 26 this.addEventListener('mousedown', this); 27 this.addEventListener('keydown', this); 28 29 // Adding the 'custom-appearance' class prevents widgets.css from changing 30 // the appearance of this element. 31 this.classList.add('custom-appearance'); 32 this.classList.add('menu-button'); // For styles in menu_button.css. 33 34 var menu; 35 if ((menu = this.getAttribute('menu'))) 36 this.menu = menu; 37 38 // An event tracker for events we only connect to while the menu is 39 // displayed. 40 this.showingEvents_ = new EventTracker(); 41 42 this.anchorType = cr.ui.AnchorType.BELOW; 43 this.invertLeftRight = false; 44 }, 45 46 /** 47 * The menu associated with the menu button. 48 * @type {cr.ui.Menu} 49 */ 50 get menu() { 51 return this.menu_; 52 }, 53 set menu(menu) { 54 if (typeof menu == 'string' && menu[0] == '#') { 55 menu = this.ownerDocument.getElementById(menu.slice(1)); 56 cr.ui.decorate(menu, Menu); 57 } 58 59 this.menu_ = menu; 60 if (menu) { 61 if (menu.id) 62 this.setAttribute('menu', '#' + menu.id); 63 } 64 }, 65 66 /** 67 * Handles event callbacks. 68 * @param {Event} e The event object. 69 */ 70 handleEvent: function(e) { 71 if (!this.menu) 72 return; 73 74 switch (e.type) { 75 case 'mousedown': 76 if (e.currentTarget == this.ownerDocument) { 77 if (!this.contains(e.target) && !this.menu.contains(e.target)) 78 this.hideMenu(); 79 else 80 e.preventDefault(); 81 } else { 82 if (this.isMenuShown()) { 83 this.hideMenu(); 84 } else if (e.button == 0) { // Only show the menu when using left 85 // mouse button. 86 this.showMenu(); 87 // Prevent the button from stealing focus on mousedown. 88 e.preventDefault(); 89 } 90 } 91 break; 92 case 'keydown': 93 this.handleKeyDown(e); 94 // If the menu is visible we let it handle all the keyboard events. 95 if (this.isMenuShown() && e.currentTarget == this.ownerDocument) { 96 this.menu.handleKeyDown(e); 97 e.preventDefault(); 98 e.stopPropagation(); 99 } 100 break; 101 102 case 'activate': 103 case 'blur': 104 case 'resize': 105 this.hideMenu(); 106 break; 107 } 108 }, 109 110 /** 111 * Shows the menu. 112 */ 113 showMenu: function() { 114 this.hideMenu(); 115 116 this.menu.style.display = 'block'; 117 this.setAttribute('menu-shown', ''); 118 119 // when the menu is shown we steal all keyboard events. 120 var doc = this.ownerDocument; 121 var win = doc.defaultView; 122 this.showingEvents_.add(doc, 'keydown', this, true); 123 this.showingEvents_.add(doc, 'mousedown', this, true); 124 this.showingEvents_.add(doc, 'blur', this, true); 125 this.showingEvents_.add(win, 'resize', this); 126 this.showingEvents_.add(this.menu, 'activate', this); 127 this.positionMenu_(); 128 }, 129 130 /** 131 * Hides the menu. If your menu can go out of scope, make sure to call this 132 * first. 133 */ 134 hideMenu: function() { 135 if (!this.isMenuShown()) 136 return; 137 138 this.removeAttribute('menu-shown'); 139 this.menu.style.display = 'none'; 140 141 this.showingEvents_.removeAll(); 142 this.menu.selectedIndex = -1; 143 }, 144 145 /** 146 * Whether the menu is shown. 147 */ 148 isMenuShown: function() { 149 return this.hasAttribute('menu-shown'); 150 }, 151 152 /** 153 * Positions the menu below the menu button. At this point we do not use any 154 * advanced positioning logic to ensure the menu fits in the viewport. 155 * @private 156 */ 157 positionMenu_: function() { 158 positionPopupAroundElement(this, this.menu, this.anchorType, 159 this.invertLeftRight); 160 }, 161 162 /** 163 * Handles the keydown event for the menu button. 164 */ 165 handleKeyDown: function(e) { 166 switch (e.keyIdentifier) { 167 case 'Down': 168 case 'Up': 169 case 'Enter': 170 case 'U+0020': // Space 171 if (!this.isMenuShown()) 172 this.showMenu(); 173 e.preventDefault(); 174 break; 175 case 'Esc': 176 case 'U+001B': // Maybe this is remote desktop playing a prank? 177 this.hideMenu(); 178 break; 179 } 180 } 181 }; 182 183 /** 184 * Helper for styling a menu button with a drop-down arrow indicator. 185 * Creates a new 2D canvas context and draws a downward-facing arrow into it. 186 * @param {string} canvasName The name of the canvas. The canvas can be 187 * addressed from CSS using -webkit-canvas(<canvasName>). 188 * @param {number} width The width of the canvas and the arrow. 189 * @param {number} height The height of the canvas and the arrow. 190 * @param {string} colorSpec The CSS color to use when drawing the arrow. 191 */ 192 function createDropDownArrowCanvas(canvasName, width, height, colorSpec) { 193 var ctx = document.getCSSCanvasContext('2d', canvasName, width, height); 194 ctx.fillStyle = ctx.strokeStyle = colorSpec; 195 ctx.beginPath(); 196 ctx.moveTo(0, 0); 197 ctx.lineTo(width, 0); 198 ctx.lineTo(height, height); 199 ctx.closePath(); 200 ctx.fill(); 201 ctx.stroke(); 202 }; 203 204 /** @const */ var ARROW_WIDTH = 6; 205 /** @const */ var ARROW_HEIGHT = 3; 206 207 /** 208 * Create the images used to style drop-down-style MenuButtons. 209 * This should be called before creating any MenuButtons that will have the 210 * CSS class 'drop-down'. If no colors are specified, defaults will be used. 211 * @param {=string} normalColor CSS color for the default button state. 212 * @param {=string} hoverColor CSS color for the hover button state. 213 * @param {=string} activeColor CSS color for the active button state. 214 */ 215 MenuButton.createDropDownArrows = function( 216 normalColor, hoverColor, activeColor) { 217 normalColor = normalColor || 'rgb(192, 195, 198)'; 218 hoverColor = hoverColor || 'rgb(48, 57, 66)'; 219 activeColor = activeColor || 'white'; 220 221 createDropDownArrowCanvas( 222 'drop-down-arrow', ARROW_WIDTH, ARROW_HEIGHT, normalColor); 223 createDropDownArrowCanvas( 224 'drop-down-arrow-hover', ARROW_WIDTH, ARROW_HEIGHT, hoverColor); 225 createDropDownArrowCanvas( 226 'drop-down-arrow-active', ARROW_WIDTH, ARROW_HEIGHT, activeColor); 227 } 228 229 // Export 230 return { 231 MenuButton: MenuButton 232 }; 233}); 234