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.
6 * @fileoverview Bubble implementation.
9 // TODO(xiyuan): Move this into shared.
10 cr.define('cr.ui', function() {
12 * Creates a bubble div.
14 * @extends {HTMLDivElement}
16 var Bubble = cr.ui.define('div');
19 * Bubble attachment side.
23 RIGHT: 'bubble-right',
26 BOTTOM: 'bubble-bottom'
30 __proto__: HTMLDivElement.prototype,
32 // Anchor element for this bubble.
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,
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);
58 * Element that should be focused on hide.
61 set elementToFocusOnHide(value) {
62 this.elementToFocusOnHide_ = value;
66 * Whether to hide bubble when key is pressed.
69 set hideOnKeyPress(value) {
70 this.hideOnKeyPress_ = value;
74 * Whether to hide bubble when clicked inside bubble element.
78 set hideOnSelfClick(value) {
80 this.removeEventListener('click', this.selfClickHandler_);
82 this.addEventListener('click', this.selfClickHandler_);
86 * Handler for click event which prevents bubble auto hide.
89 handleSelfClick_: function(e) {
90 // Allow clicking on [x] button.
91 if (e.target && e.target.classList.contains('close-button'))
98 * Sets the attachment of the bubble.
99 * @param {!Attachment} attachment Bubble attachment.
101 setAttachment_: function(attachment) {
102 for (var k in Bubble.Attachment) {
103 var v = Bubble.Attachment[k];
104 this.classList.toggle(v, v == attachment);
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.
117 showContentAt_: function(pos, attachment, opt_content) {
118 this.style.top = this.style.left = this.style.right = this.style.bottom =
121 if (typeof pos[k] == 'number')
122 this.style[k] = pos[k] + 'px';
124 if (opt_content !== undefined) {
126 this.appendChild(opt_content);
128 this.setAttachment_(attachment);
130 this.classList.remove('faded');
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.
141 showForElement: function(el, attachment, opt_offset, opt_padding) {
142 this.showContentForElement(
143 el, attachment, undefined, opt_offset, opt_padding);
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.
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];
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;
181 case Bubble.Attachment.RIGHT:
182 pos.top = origin.top + offset[1] - ARROW_OFFSET;
183 pos.right = origin.right + el.offsetWidth + opt_padding;
185 case Bubble.Attachment.BOTTOM:
186 pos.right = origin.right + offset[0] - ARROW_OFFSET;
187 pos.top = origin.top + el.offsetHeight + opt_padding;
189 case Bubble.Attachment.LEFT:
190 pos.top = origin.top + offset[1] - ARROW_OFFSET;
191 pos.left = origin.left + el.offsetWidth + opt_padding;
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;
200 case Bubble.Attachment.RIGHT:
201 pos.top = origin.top + offset[1] - ARROW_OFFSET;
202 pos.left = origin.left + el.offsetWidth + opt_padding;
204 case Bubble.Attachment.BOTTOM:
205 pos.left = origin.left + offset[0] - ARROW_OFFSET;
206 pos.top = origin.top + el.offsetHeight + opt_padding;
208 case Bubble.Attachment.LEFT:
209 pos.top = origin.top + offset[1] - ARROW_OFFSET;
210 pos.right = origin.right + el.offsetWidth + opt_padding;
216 this.showContentAt_(pos, attachment, opt_content);
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.
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);
243 if (!this.classList.contains('faded'))
244 this.classList.add('faded');
248 * Hides the bubble anchored to the given element (if any).
249 * @param {!Object} el Anchor element.
251 hideForElement: function(el) {
252 if (!this.hidden && this.anchor_ == el)
257 * Handler for faded transition end.
260 handleTransitionEnd_: function(e) {
261 if (this.classList.contains('faded')) {
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();
273 * Handler of document click event.
276 handleDocClick_: function(e) {
277 // Ignore clicks on anchor element.
278 if (e.target == this.anchor_)
286 * Handle of document keydown event.
289 handleDocKeyDown_: function(e) {
290 if (this.hideOnKeyPress_ && !this.hidden) {
295 if (e.keyCode == 27 && !this.hidden) {
296 if (this.elementToFocusOnHide_)
297 this.elementToFocusOnHide_.focus();
303 * Handler of window blur event.
306 handleWindowBlur_: function(e) {