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