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