Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / login / bubble.js
blobcce6f8d23e4ee303cf5dabe9dd9b0c9eaa5926fd
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.
5 /**
6  * @fileoverview Bubble implementation.
7  */
9 // TODO(xiyuan): Move this into shared.
10 cr.define('cr.ui', function() {
11   /**
12    * Creates a bubble div.
13    * @constructor
14    * @extends {HTMLDivElement}
15    */
16   var Bubble = cr.ui.define('div');
18   /**
19    * Bubble attachment side.
20    * @enum {string}
21    */
22   Bubble.Attachment = {
23     RIGHT: 'bubble-right',
24     LEFT: 'bubble-left',
25     TOP: 'bubble-top',
26     BOTTOM: 'bubble-bottom'
27   };
29   Bubble.prototype = {
30     __proto__: HTMLDivElement.prototype,
32     // Anchor element for this bubble.
33     anchor_: undefined,
35     // If defined, sets focus to this element once bubble is closed. Focus is
36     // set to this element only if there's no any other focused element.
37     elementToFocusOnHide_: undefined,
39     // Whether to hide bubble when key is pressed.
40     hideOnKeyPress_: true,
42     /** @override */
43     decorate: function() {
44       this.docKeyDownHandler_ = this.handleDocKeyDown_.bind(this);
45       this.selfClickHandler_ = this.handleSelfClick_.bind(this);
46       this.ownerDocument.addEventListener('click',
47                                           this.handleDocClick_.bind(this));
48       this.ownerDocument.addEventListener('keydown',
49                                           this.docKeyDownHandler_);
50       window.addEventListener('blur', this.handleWindowBlur_.bind(this));
51       this.addEventListener('webkitTransitionEnd',
52                             this.handleTransitionEnd_.bind(this));
53       // Guard timer for 200ms + epsilon.
54       ensureTransitionEndEvent(this, 250);
55     },
57     /**
58      * Element that should be focused on hide.
59      * @type {HTMLElement}
60      */
61     set elementToFocusOnHide(value) {
62       this.elementToFocusOnHide_ = value;
63     },
65     /**
66      * Whether to hide bubble when key is pressed.
67      * @type {boolean}
68      */
69     set hideOnKeyPress(value) {
70       this.hideOnKeyPress_ = value;
71     },
73     /**
74      * Whether to hide bubble when clicked inside bubble element.
75      * Default is true.
76      * @type {boolean}
77      */
78     set hideOnSelfClick(value) {
79       if (value)
80         this.removeEventListener('click', this.selfClickHandler_);
81       else
82         this.addEventListener('click', this.selfClickHandler_);
83     },
85     /**
86      * Handler for click event which prevents bubble auto hide.
87      * @private
88      */
89     handleSelfClick_: function(e) {
90       // Allow clicking on [x] button.
91       if (e.target && e.target.classList.contains('close-button'))
92         return;
94       e.stopPropagation();
95     },
97     /**
98      * Sets the attachment of the bubble.
99      * @param {!Attachment} attachment Bubble attachment.
100      */
101     setAttachment_: function(attachment) {
102       for (var k in Bubble.Attachment) {
103         var v = Bubble.Attachment[k];
104         this.classList.toggle(v, v == attachment);
105       }
106     },
108     /**
109      * Shows the bubble for given anchor element.
110      * @param {!Object} pos Bubble position (left, top, right, bottom in px).
111      * @param {!Attachment} attachment Bubble attachment (on which side of the
112      *     specified position it should be displayed).
113      * @param {HTMLElement} opt_content Content to show in bubble.
114      *     If not specified, bubble element content is shown.
115      * @private
116      */
117     showContentAt_: function(pos, attachment, opt_content) {
118       this.style.top = this.style.left = this.style.right = this.style.bottom =
119           'auto';
120       for (var k in pos) {
121         if (typeof pos[k] == 'number')
122           this.style[k] = pos[k] + 'px';
123       }
124       if (opt_content !== undefined) {
125         this.innerHTML = '';
126         this.appendChild(opt_content);
127       }
128       this.setAttachment_(attachment);
129       this.hidden = false;
130       this.classList.remove('faded');
131     },
133     /**
134      * Shows the bubble for given anchor element. Bubble content is not cleared.
135      * @param {!HTMLElement} el Anchor element of the bubble.
136      * @param {!Attachment} attachment Bubble attachment (on which side of the
137      *     element it should be displayed).
138      * @param {number=} opt_offset Offset of the bubble.
139      * @param {number=} opt_padding Optional padding of the bubble.
140      */
141     showForElement: function(el, attachment, opt_offset, opt_padding) {
142       this.showContentForElement(
143           el, attachment, undefined, opt_offset, opt_padding);
144     },
146     /**
147      * Shows the bubble for given anchor element.
148      * @param {!HTMLElement} el Anchor element of the bubble.
149      * @param {!Attachment} attachment Bubble attachment (on which side of the
150      *     element it should be displayed).
151      * @param {HTMLElement} opt_content Content to show in bubble.
152      *     If not specified, bubble element content is shown.
153      * @param {number=} opt_offset Offset of the bubble attachment point from
154      *     left (for vertical attachment) or top (for horizontal attachment)
155      *     side of the element. If not specified, the bubble is positioned to
156      *     be aligned with the left/top side of the element but not farther than
157      *     half of its width/height.
158      * @param {number=} opt_padding Optional padding of the bubble.
159      */
160     showContentForElement: function(el, attachment, opt_content,
161                                     opt_offset, opt_padding) {
162       /** @const */ var ARROW_OFFSET = 25;
163       /** @const */ var DEFAULT_PADDING = 18;
165       if (opt_padding == undefined)
166         opt_padding = DEFAULT_PADDING;
168       var origin = cr.ui.login.DisplayManager.getPosition(el);
169       var offset = opt_offset == undefined ?
170           [Math.min(ARROW_OFFSET, el.offsetWidth / 2),
171            Math.min(ARROW_OFFSET, el.offsetHeight / 2)] :
172           [opt_offset, opt_offset];
174       var pos = {};
175       if (isRTL()) {
176         switch (attachment) {
177           case Bubble.Attachment.TOP:
178             pos.right = origin.right + offset[0] - ARROW_OFFSET;
179             pos.bottom = origin.bottom + el.offsetHeight + opt_padding;
180             break;
181           case Bubble.Attachment.RIGHT:
182             pos.top = origin.top + offset[1] - ARROW_OFFSET;
183             pos.right = origin.right + el.offsetWidth + opt_padding;
184             break;
185           case Bubble.Attachment.BOTTOM:
186             pos.right = origin.right + offset[0] - ARROW_OFFSET;
187             pos.top = origin.top + el.offsetHeight + opt_padding;
188             break;
189           case Bubble.Attachment.LEFT:
190             pos.top = origin.top + offset[1] - ARROW_OFFSET;
191             pos.left = origin.left + el.offsetWidth + opt_padding;
192             break;
193         }
194       } else {
195         switch (attachment) {
196           case Bubble.Attachment.TOP:
197             pos.left = origin.left + offset[0] - ARROW_OFFSET;
198             pos.bottom = origin.bottom + el.offsetHeight + opt_padding;
199             break;
200           case Bubble.Attachment.RIGHT:
201             pos.top = origin.top + offset[1] - ARROW_OFFSET;
202             pos.left = origin.left + el.offsetWidth + opt_padding;
203             break;
204           case Bubble.Attachment.BOTTOM:
205             pos.left = origin.left + offset[0] - ARROW_OFFSET;
206             pos.top = origin.top + el.offsetHeight + opt_padding;
207             break;
208           case Bubble.Attachment.LEFT:
209             pos.top = origin.top + offset[1] - ARROW_OFFSET;
210             pos.right = origin.right + el.offsetWidth + opt_padding;
211             break;
212         }
213       }
215       this.anchor_ = el;
216       this.showContentAt_(pos, attachment, opt_content);
217     },
219     /**
220      * Shows the bubble for given anchor element.
221      * @param {!HTMLElement} el Anchor element of the bubble.
222      * @param {string} text Text content to show in bubble.
223      * @param {!Attachment} attachment Bubble attachment (on which side of the
224      *     element it should be displayed).
225      * @param {number=} opt_offset Offset of the bubble attachment point from
226      *     left (for vertical attachment) or top (for horizontal attachment)
227      *     side of the element. If not specified, the bubble is positioned to
228      *     be aligned with the left/top side of the element but not farther than
229      *     half of its weight/height.
230      * @param {number=} opt_padding Optional padding of the bubble.
231      */
232     showTextForElement: function(el, text, attachment,
233                                  opt_offset, opt_padding) {
234       var span = this.ownerDocument.createElement('span');
235       span.textContent = text;
236       this.showContentForElement(el, attachment, span, opt_offset, opt_padding);
237     },
239     /**
240      * Hides the bubble.
241      */
242     hide: function() {
243       if (!this.classList.contains('faded'))
244         this.classList.add('faded');
245     },
247     /**
248      * Hides the bubble anchored to the given element (if any).
249      * @param {!Object} el Anchor element.
250      */
251     hideForElement: function(el) {
252       if (!this.hidden && this.anchor_ == el)
253         this.hide();
254     },
256     /**
257      * Handler for faded transition end.
258      * @private
259      */
260     handleTransitionEnd_: function(e) {
261       if (this.classList.contains('faded')) {
262         this.hidden = true;
263         if (this.elementToFocusOnHide_ &&
264             document.activeElement == document.body) {
265           // Restore focus to default element only if there's no other
266           // element that is focused.
267           this.elementToFocusOnHide_.focus();
268         }
269       }
270     },
272     /**
273      * Handler of document click event.
274      * @private
275      */
276     handleDocClick_: function(e) {
277       // Ignore clicks on anchor element.
278       if (e.target == this.anchor_)
279         return;
281       if (!this.hidden)
282         this.hide();
283     },
285     /**
286      * Handle of document keydown event.
287      * @private
288      */
289     handleDocKeyDown_: function(e) {
290       if (this.hideOnKeyPress_ && !this.hidden) {
291         this.hide();
292         return;
293       }
295       if (e.keyCode == 27 && !this.hidden) {
296         if (this.elementToFocusOnHide_)
297           this.elementToFocusOnHide_.focus();
298         this.hide();
299       }
300     },
302     /**
303      * Handler of window blur event.
304      * @private
305      */
306     handleWindowBlur_: function(e) {
307       if (!this.hidden)
308         this.hide();
309     }
310   };
312   return {
313     Bubble: Bubble
314   };