Home | History | Annotate | Download | only in ui
      1 // Copyright (c) 2011 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   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       // An event tracker for events we only connect to while the menu is
     32       // displayed.
     33       this.showingEvents_ = new EventTracker();
     34     },
     35 
     36     /**
     37      * The menu associated with the menu button.
     38      * @type {cr.ui.Menu}
     39      */
     40     get menu() {
     41       return this.menu_;
     42     },
     43     set menu(menu) {
     44       if (typeof menu == 'string' && menu[0] == '#') {
     45         menu = this.ownerDocument.getElementById(menu.slice(1));
     46         cr.ui.decorate(menu, Menu);
     47       }
     48 
     49       this.menu_ = menu;
     50       if (menu) {
     51         if (menu.id)
     52           this.setAttribute('menu', '#' + menu.id);
     53       }
     54     },
     55 
     56     /**
     57      * Handles event callbacks.
     58      * @param {Event} e The event object.
     59      */
     60     handleEvent: function(e) {
     61       if (!this.menu)
     62         return;
     63 
     64       switch (e.type) {
     65         case 'mousedown':
     66           if (e.currentTarget == this.ownerDocument) {
     67             if (!this.contains(e.target) && !this.menu.contains(e.target))
     68               this.hideMenu();
     69             else
     70               e.preventDefault();
     71           } else {
     72             if (this.isMenuShown()) {
     73               this.hideMenu();
     74             } else if (e.button == 0) {  // Only show the menu when using left
     75                                          // mouse button.
     76               this.showMenu();
     77               // Prevent the button from stealing focus on mousedown.
     78               e.preventDefault();
     79             }
     80           }
     81           break;
     82         case 'keydown':
     83           this.handleKeyDown(e);
     84           // If the menu is visible we let it handle all the keyboard events.
     85           if (this.isMenuShown() && e.currentTarget == this.ownerDocument) {
     86             this.menu.handleKeyDown(e);
     87             e.preventDefault();
     88             e.stopPropagation();
     89           }
     90           break;
     91 
     92         case 'activate':
     93         case 'blur':
     94         case 'resize':
     95           this.hideMenu();
     96           break;
     97       }
     98     },
     99 
    100     /**
    101      * Shows the menu.
    102      */
    103     showMenu: function() {
    104       this.hideMenu();
    105 
    106       this.menu.style.display = 'block';
    107       this.setAttribute('menu-shown', '');
    108 
    109       // when the menu is shown we steal all keyboard events.
    110       var doc = this.ownerDocument;
    111       var win = doc.defaultView;
    112       this.showingEvents_.add(doc, 'keydown', this, true);
    113       this.showingEvents_.add(doc, 'mousedown', this, true);
    114       this.showingEvents_.add(doc, 'blur', this, true);
    115       this.showingEvents_.add(win, 'resize', this);
    116       this.showingEvents_.add(this.menu, 'activate', this);
    117       this.positionMenu_();
    118     },
    119 
    120     /**
    121      * Hides the menu. If your menu can go out of scope, make sure to call this
    122      * first.
    123      */
    124     hideMenu: function() {
    125       if (!this.isMenuShown())
    126         return;
    127 
    128       this.removeAttribute('menu-shown');
    129       this.menu.style.display = 'none';
    130 
    131       this.showingEvents_.removeAll();
    132       this.menu.selectedIndex = -1;
    133     },
    134 
    135     /**
    136      * Whether the menu is shown.
    137      */
    138     isMenuShown: function() {
    139       return this.hasAttribute('menu-shown');
    140     },
    141 
    142     /**
    143      * Positions the menu below the menu button. At this point we do not use any
    144      * advanced positioning logic to ensure the menu fits in the viewport.
    145      * @private
    146      */
    147     positionMenu_: function() {
    148       positionPopupAroundElement(this, this.menu, cr.ui.AnchorType.BELOW);
    149     },
    150 
    151     /**
    152      * Handles the keydown event for the menu button.
    153      */
    154     handleKeyDown: function(e) {
    155       switch (e.keyIdentifier) {
    156         case 'Down':
    157         case 'Up':
    158         case 'Enter':
    159         case 'U+0020': // Space
    160           if (!this.isMenuShown())
    161             this.showMenu();
    162           e.preventDefault();
    163           break;
    164         case 'Esc':
    165         case 'U+001B': // Maybe this is remote desktop playing a prank?
    166           this.hideMenu();
    167           break;
    168       }
    169     }
    170   };
    171 
    172   // Export
    173   return {
    174     MenuButton: MenuButton
    175   };
    176 });
    177