Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ui / webui / resources / js / cr / ui / menu_button.js
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
5 // <include src="../../assert.js">
6
7 cr.exportPath('cr.ui');
8
9 /**
10  * Enum for type of hide. Delayed is used when called by clicking on a
11  * checkable menu item.
12  * @enum {number}
13  */
14 cr.ui.HideType = {
15   INSTANT: 0,
16   DELAYED: 1
17 };
18
19 cr.define('cr.ui', function() {
20   /** @const */
21   var Menu = cr.ui.Menu;
22
23   /** @const */
24   var HideType = cr.ui.HideType;
25
26   /** @const */
27   var positionPopupAroundElement = cr.ui.positionPopupAroundElement;
28
29   /**
30    * Creates a new menu button element.
31    * @param {Object=} opt_propertyBag Optional properties.
32    * @constructor
33    * @extends {HTMLButtonElement}
34    * @implements {EventListener}
35    */
36   var MenuButton = cr.ui.define('button');
37
38   MenuButton.prototype = {
39     __proto__: HTMLButtonElement.prototype,
40
41     /**
42      * Initializes the menu button.
43      */
44     decorate: function() {
45       this.addEventListener('mousedown', this);
46       this.addEventListener('keydown', this);
47
48       // Adding the 'custom-appearance' class prevents widgets.css from changing
49       // the appearance of this element.
50       this.classList.add('custom-appearance');
51       this.classList.add('menu-button');  // For styles in menu_button.css.
52
53       var menu;
54       if ((menu = this.getAttribute('menu')))
55         this.menu = menu;
56
57       // An event tracker for events we only connect to while the menu is
58       // displayed.
59       this.showingEvents_ = new EventTracker();
60
61       this.anchorType = cr.ui.AnchorType.BELOW;
62       this.invertLeftRight = false;
63     },
64
65     /**
66      * The menu associated with the menu button.
67      * @type {cr.ui.Menu}
68      */
69     get menu() {
70       return this.menu_;
71     },
72     set menu(menu) {
73       if (typeof menu == 'string' && menu[0] == '#') {
74         menu = assert(this.ownerDocument.getElementById(menu.slice(1)));
75         cr.ui.decorate(menu, Menu);
76       }
77
78       this.menu_ = menu;
79       if (menu) {
80         if (menu.id)
81           this.setAttribute('menu', '#' + menu.id);
82       }
83     },
84
85     /**
86      * Whether to show the menu on press of the Up or Down arrow keys.
87      */
88     respondToArrowKeys: true,
89
90     /**
91      * Handles event callbacks.
92      * @param {Event} e The event object.
93      */
94     handleEvent: function(e) {
95       if (!this.menu)
96         return;
97
98       switch (e.type) {
99         case 'mousedown':
100           if (e.currentTarget == this.ownerDocument) {
101             if (e.target instanceof Node && !this.contains(e.target) &&
102                 !this.menu.contains(e.target)) {
103               this.hideMenu();
104             } else {
105               e.preventDefault();
106             }
107           } else {
108             if (this.isMenuShown()) {
109               this.hideMenu();
110             } else if (e.button == 0) {  // Only show the menu when using left
111                                          // mouse button.
112               this.showMenu(false);
113
114               // Prevent the button from stealing focus on mousedown.
115               e.preventDefault();
116             }
117           }
118
119           // Hide the focus ring on mouse click.
120           this.classList.add('using-mouse');
121           break;
122         case 'keydown':
123           this.handleKeyDown(e);
124           // If the menu is visible we let it handle all the keyboard events.
125           if (this.isMenuShown() && e.currentTarget == this.ownerDocument) {
126             if (this.menu.handleKeyDown(e)) {
127               e.preventDefault();
128               e.stopPropagation();
129             }
130           }
131
132           // Show the focus ring on keypress.
133           this.classList.remove('using-mouse');
134           break;
135         case 'focus':
136           if (e.target instanceof Node && !this.contains(e.target) &&
137               !this.menu.contains(e.target)) {
138             this.hideMenu();
139             // Show the focus ring on focus - if it's come from a mouse event,
140             // the focus ring will be hidden in the mousedown event handler,
141             // executed after this.
142             this.classList.remove('using-mouse');
143           }
144           break;
145         case 'activate':
146           var hideDelayed = e.target instanceof cr.ui.MenuItem &&
147               e.target.checkable;
148           this.hideMenu(hideDelayed ? HideType.DELAYED : HideType.INSTANT);
149           break;
150         case 'scroll':
151           if (!(e.target == this.menu || this.menu.contains(e.target)))
152             this.hideMenu();
153           break;
154         case 'popstate':
155         case 'resize':
156           this.hideMenu();
157           break;
158       }
159     },
160
161     /**
162      * Shows the menu.
163      * @param {boolean} shouldSetFocus Whether to set focus on the
164      *     selected menu item.
165      */
166     showMenu: function(shouldSetFocus) {
167       this.hideMenu();
168
169       this.menu.updateCommands(this);
170
171       var event = document.createEvent('UIEvents');
172       event.initUIEvent('menushow', true, true, window, null);
173
174       if (!this.dispatchEvent(event))
175         return;
176
177       this.menu.hidden = false;
178
179       this.setAttribute('menu-shown', '');
180
181       // When the menu is shown we steal all keyboard events.
182       var doc = this.ownerDocument;
183       var win = doc.defaultView;
184       this.showingEvents_.add(doc, 'keydown', this, true);
185       this.showingEvents_.add(doc, 'mousedown', this, true);
186       this.showingEvents_.add(doc, 'focus', this, true);
187       this.showingEvents_.add(doc, 'scroll', this, true);
188       this.showingEvents_.add(win, 'popstate', this);
189       this.showingEvents_.add(win, 'resize', this);
190       this.showingEvents_.add(this.menu, 'activate', this);
191       this.positionMenu_();
192
193       if (shouldSetFocus)
194         this.menu.focusSelectedItem();
195     },
196
197     /**
198      * Hides the menu. If your menu can go out of scope, make sure to call this
199      * first.
200      * @param {cr.ui.HideType=} opt_hideType Type of hide.
201      *     default: cr.ui.HideType.INSTANT.
202      */
203     hideMenu: function(opt_hideType) {
204       if (!this.isMenuShown())
205         return;
206
207       this.removeAttribute('menu-shown');
208       if (opt_hideType == HideType.DELAYED)
209         this.menu.classList.add('hide-delayed');
210       else
211         this.menu.classList.remove('hide-delayed');
212       this.menu.hidden = true;
213
214       this.showingEvents_.removeAll();
215       this.focus();
216     },
217
218     /**
219      * Whether the menu is shown.
220      */
221     isMenuShown: function() {
222       return this.hasAttribute('menu-shown');
223     },
224
225     /**
226      * Positions the menu below the menu button. At this point we do not use any
227      * advanced positioning logic to ensure the menu fits in the viewport.
228      * @private
229      */
230     positionMenu_: function() {
231       positionPopupAroundElement(this, this.menu, this.anchorType,
232                                  this.invertLeftRight);
233     },
234
235     /**
236      * Handles the keydown event for the menu button.
237      */
238     handleKeyDown: function(e) {
239       switch (e.keyIdentifier) {
240         case 'Down':
241         case 'Up':
242           if (!this.respondToArrowKeys)
243             break;
244         case 'Enter':
245         case 'U+0020': // Space
246           if (!this.isMenuShown())
247             this.showMenu(true);
248           e.preventDefault();
249           break;
250         case 'Esc':
251         case 'U+001B': // Maybe this is remote desktop playing a prank?
252         case 'U+0009': // Tab
253           this.hideMenu();
254           break;
255       }
256     }
257   };
258
259   /**
260    * Helper for styling a menu button with a drop-down arrow indicator.
261    * Creates a new 2D canvas context and draws a downward-facing arrow into it.
262    * @param {string} canvasName The name of the canvas. The canvas can be
263    *     addressed from CSS using -webkit-canvas(<canvasName>).
264    * @param {number} width The width of the canvas and the arrow.
265    * @param {number} height The height of the canvas and the arrow.
266    * @param {string} colorSpec The CSS color to use when drawing the arrow.
267    */
268   function createDropDownArrowCanvas(canvasName, width, height, colorSpec) {
269     var ctx = document.getCSSCanvasContext('2d', canvasName, width, height);
270     ctx.fillStyle = ctx.strokeStyle = colorSpec;
271     ctx.beginPath();
272     ctx.moveTo(0, 0);
273     ctx.lineTo(width, 0);
274     ctx.lineTo(height, height);
275     ctx.closePath();
276     ctx.fill();
277     ctx.stroke();
278   };
279
280   /** @const */ var ARROW_WIDTH = 6;
281   /** @const */ var ARROW_HEIGHT = 3;
282
283   /**
284    * Create the images used to style drop-down-style MenuButtons.
285    * This should be called before creating any MenuButtons that will have the
286    * CSS class 'drop-down'. If no colors are specified, defaults will be used.
287    * @param {string=} opt_normalColor CSS color for the default button state.
288    * @param {string=} opt_hoverColor CSS color for the hover button state.
289    * @param {string=} opt_activeColor CSS color for the active button state.
290    */
291   MenuButton.createDropDownArrows = function(
292       opt_normalColor, opt_hoverColor, opt_activeColor) {
293     opt_normalColor = opt_normalColor || 'rgb(192, 195, 198)';
294     opt_hoverColor = opt_hoverColor || 'rgb(48, 57, 66)';
295     opt_activeColor = opt_activeColor || 'white';
296
297     createDropDownArrowCanvas(
298         'drop-down-arrow', ARROW_WIDTH, ARROW_HEIGHT, opt_normalColor);
299     createDropDownArrowCanvas(
300         'drop-down-arrow-hover', ARROW_WIDTH, ARROW_HEIGHT, opt_hoverColor);
301     createDropDownArrowCanvas(
302         'drop-down-arrow-active', ARROW_WIDTH, ARROW_HEIGHT, opt_activeColor);
303   };
304
305   // Export
306   return {
307     MenuButton: MenuButton,
308   };
309 });