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 7 /** @const */ var MenuItem = cr.ui.MenuItem; 8 9 /** 10 * Creates a new menu element. 11 * @param {Object=} opt_propertyBag Optional properties. 12 * @constructor 13 * @extends {HTMLMenuElement} 14 */ 15 var Menu = cr.ui.define('menu'); 16 17 Menu.prototype = { 18 __proto__: HTMLMenuElement.prototype, 19 20 selectedIndex_: -1, 21 22 /** 23 * Initializes the menu element. 24 */ 25 decorate: function() { 26 this.addEventListener('mouseover', this.handleMouseOver_); 27 this.addEventListener('mouseout', this.handleMouseOut_); 28 29 // Decorate the children as menu items. 30 var children = this.children; 31 for (var i = 0, child; child = children[i]; i++) { 32 cr.ui.decorate(child, MenuItem); 33 } 34 }, 35 36 /** 37 * Walks up the ancestors of |el| until a menu item belonging to this menu 38 * is found. 39 * @param {Element} el The element to start searching from. 40 * @return {cr.ui.MenuItem} The found menu item or null. 41 * @private 42 */ 43 findMenuItem_: function(el) { 44 while (el && el.parentNode != this) { 45 el = el.parentNode; 46 } 47 return el; 48 }, 49 50 /** 51 * Handles mouseover events and selects the hovered item. 52 * @param {Event} e The mouseover event. 53 * @private 54 */ 55 handleMouseOver_: function(e) { 56 var overItem = this.findMenuItem_(e.target); 57 this.selectedItem = overItem; 58 }, 59 60 /** 61 * Handles mouseout events and deselects any selected item. 62 * @param {Event} e The mouseout event. 63 * @private 64 */ 65 handleMouseOut_: function(e) { 66 this.selectedItem = null; 67 }, 68 69 /** 70 * The selected menu item or null if none. 71 * @type {cr.ui.MenuItem} 72 */ 73 get selectedItem() { 74 return this.children[this.selectedIndex]; 75 }, 76 set selectedItem(item) { 77 var index = Array.prototype.indexOf.call(this.children, item); 78 this.selectedIndex = index; 79 }, 80 81 /** 82 * This is the function that handles keyboard navigation. This is usually 83 * called by the element responsible for managing the menu. 84 * @param {Event} e The keydown event object. 85 * @return {boolean} Whether the event was handled be the menu. 86 */ 87 handleKeyDown: function(e) { 88 var item = this.selectedItem; 89 90 var self = this; 91 function selectNextVisible(m) { 92 var children = self.children; 93 var len = children.length; 94 var i = self.selectedIndex; 95 if (i == -1 && m == -1) { 96 // Edge case when we need to go the last item fisrt. 97 i = 0; 98 } 99 while (true) { 100 i = (i + m + len) % len; 101 item = children[i]; 102 if (item && !item.isSeparator() && !item.hidden) 103 break; 104 } 105 if (item) 106 self.selectedIndex = i; 107 } 108 109 switch (e.keyIdentifier) { 110 case 'Down': 111 selectNextVisible(1); 112 return true; 113 case 'Up': 114 selectNextVisible(-1); 115 return true; 116 case 'Enter': 117 case 'U+0020': // Space 118 if (item) { 119 if (cr.dispatchSimpleEvent(item, 'activate', true, true)) { 120 if (item.command) 121 item.command.execute(); 122 } 123 } 124 return true; 125 } 126 127 return false; 128 } 129 }; 130 131 function selectedIndexChanged(selectedIndex, oldSelectedIndex) { 132 var oldSelectedItem = this.children[oldSelectedIndex]; 133 if (oldSelectedItem) 134 oldSelectedItem.selected = false; 135 var item = this.selectedItem; 136 if (item) 137 item.selected = true; 138 } 139 /** 140 * The selected menu item. 141 * @type {number} 142 */ 143 cr.defineProperty(Menu, 'selectedIndex', cr.PropertyKind.JS, 144 selectedIndexChanged); 145 146 // Export 147 return { 148 Menu: Menu 149 }; 150}); 151