|
OLD | NEW |
---|---|
(Empty) | |
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 // require: event_tracker.js | |
6 | |
7 cr.define('cr.ui', function() { | |
8 'use strict'; | |
9 | |
10 /** | |
11 * ExpandableBubble is a free-floating compact informational bubble with an | |
12 * arrow that points at a place of interest on the page. When clicked, the | |
13 * bubble expands to show more of its content. Width of the bubble is the | |
14 * width of the node it is overlapping when unexpanded. Expanded, it is of a | |
15 * fixed width, but variable height. Currently the arrow is always positioned | |
16 * at the bottom right and points down. | |
17 * @constructor | |
18 * @extends {cr.ui.div} | |
19 */ | |
20 var ExpandableBubble = cr.ui.define('div'); | |
21 | |
22 ExpandableBubble.prototype = { | |
23 __proto__: HTMLDivElement.prototype, | |
24 | |
25 /** @inheritDoc */ | |
26 decorate: function() { | |
27 this.className = 'expandable-bubble'; | |
28 this.innerHTML = | |
29 '<div class="expandable-bubble-contents">' + | |
30 '<div class="expandable-bubble-title"></div>' + | |
31 '<div class="expandable-bubble-main" hidden></div>' + | |
32 '</div>' + | |
33 '<div class="expandable-bubble-close" hidden></div>' + | |
34 '<div class="expandable-bubble-shadow"></div>' + | |
35 '<div class="expandable-bubble-arrow"></div>'; | |
36 | |
37 this.hidden = true; | |
38 }, | |
39 | |
40 /** | |
41 * Sets the title of the bubble. The title is always visible when the | |
42 * bubble is visible. | |
43 * @type {Node} An HTML element to set as the title. | |
44 */ | |
45 set contentTitle(node) { | |
46 var bubbleTitle = this.querySelector('.expandable-bubble-title'); | |
47 bubbleTitle.textContent = ''; | |
48 bubbleTitle.appendChild(node); | |
49 }, | |
50 | |
51 /** | |
52 * Sets the content node of the bubble. The content node is only visible | |
53 * when the bubble is expanded. | |
54 * @param {Node} An HTML element. | |
55 */ | |
56 set content(node) { | |
57 var bubbleMain = this.querySelector('.expandable-bubble-main'); | |
58 bubbleMain.textContent = ''; | |
59 bubbleMain.appendChild(node); | |
60 }, | |
61 | |
62 /** | |
63 * Sets the anchor node, i.e. the node that this bubble points at and | |
64 * partially overlaps. | |
65 * @param {HTMLElement} node The new anchor node. | |
66 */ | |
67 set anchorNode(node) { | |
68 this.anchorNode_ = node; | |
69 | |
70 if (!this.hidden) | |
71 this.resizeAndReposition_(); | |
72 }, | |
73 | |
74 /** | |
75 * Updates the position of the bubble. | |
76 * @private | |
77 */ | |
78 reposition_: function() { | |
79 var clientRect = this.anchorNode_.getBoundingClientRect(); | |
80 this.style.left = this.style.right = clientRect.left + 'px'; | |
81 | |
82 var top = clientRect.top - 1; | |
83 if (this.expanded) { | |
84 this.style.top = | |
85 (top - this.offsetHeight + this.unexpandedHeight) + 'px'; | |
86 } else { | |
87 this.style.top = top + 'px'; | |
88 } | |
89 }, | |
90 | |
91 /** | |
92 * Resizes the bubble and then repositions it. | |
93 * @private | |
94 */ | |
95 resizeAndReposition_: function() { | |
96 var clientRect = this.anchorNode_.getBoundingClientRect(); | |
97 var width = clientRect.width; | |
98 if (this.expanded) { | |
99 var expandedWidth = 250; | |
100 this.style.marginLeft = (width - expandedWidth) + 'px'; | |
101 width = expandedWidth; | |
102 } else { | |
103 this.style.marginLeft = '0'; | |
104 } | |
105 | |
106 // Width is dynamic (when not expanded) based on the width of the anchor | |
107 // node, and the title and shadow need to follow suit. | |
108 this.style.width = width + 'px'; | |
109 var bubbleTitle = this.querySelector('.expandable-bubble-title'); | |
110 bubbleTitle.style.width = width - 2 + 'px'; | |
111 var bubbleContent = this.querySelector('.expandable-bubble-main'); | |
112 bubbleContent.style.width = width - 12 + 'px'; | |
113 var bubbleShadow = this.querySelector('.expandable-bubble-shadow'); | |
114 bubbleShadow.style.width = width + 2 + 'px'; | |
115 | |
116 // Also reposition the bubble -- dimensions have potentially changed. | |
117 this.reposition_(); | |
118 }, | |
119 | |
120 /* | |
121 * Expand the bubble (bringing the full content into view). | |
122 * @private | |
123 */ | |
124 expandBubble_: function() { | |
125 this.querySelector('.expandable-bubble-main').hidden = false; | |
126 this.querySelector('.expandable-bubble-close').hidden = false; | |
127 this.expanded = true; | |
128 this.resizeAndReposition_(); | |
129 }, | |
130 | |
131 /** | |
132 * Collapse the bubble, hiding the main content and the close button. | |
133 * This is automatically called when the window is resized. | |
134 * @private | |
135 */ | |
136 collapseBubble_: function() { | |
137 this.querySelector('.expandable-bubble-main').hidden = true; | |
138 this.querySelector('.expandable-bubble-close').hidden = true; | |
139 this.expanded = false; | |
140 this.resizeAndReposition_(); | |
141 }, | |
142 | |
143 /** | |
144 * The onclick handler for the notification (expands the bubble). | |
145 * @param {Event} e The event. | |
146 * @private | |
147 */ | |
148 onNotificationClick_ : function(e) { | |
149 if (!this.contains(e.target)) | |
150 return; | |
151 | |
152 if (!this.expanded) { | |
153 // Save the height of the unexpanded bubble, so we can make sure to | |
154 // position it correctly (arrow points in the same location) after | |
155 // we expand it. | |
156 this.unexpandedHeight = this.offsetHeight; | |
157 } | |
158 | |
159 this.expandBubble_(); | |
160 }, | |
161 | |
162 /** | |
163 * Starts showing the bubble. The bubble will grab input and show until the | |
164 * user clicks away. | |
165 */ | |
166 show: function() { | |
167 if (!this.hidden) | |
168 return; | |
169 | |
170 document.body.appendChild(this); | |
171 this.hidden = false; | |
172 this.resizeAndReposition_(); | |
173 | |
174 this.eventTracker_ = new EventTracker; | |
175 this.eventTracker_.add(window, | |
176 'load', this.resizeAndReposition_.bind(this)); | |
177 this.eventTracker_.add(window, | |
178 'resize', this.collapseBubble_.bind(this)); | |
179 this.eventTracker_.add(window, | |
Evan Stade
2011/10/27 20:51:48
don't connect on |window|
| |
180 'click', this.onNotificationClick_.bind(this)); | |
181 | |
182 var doc = this.ownerDocument; | |
183 this.eventTracker_.add(doc, 'keydown', this, true); | |
184 this.eventTracker_.add(doc, 'mousedown', this, true); | |
185 }, | |
186 | |
187 /** | |
188 * Hides the bubble from view. | |
189 */ | |
190 hide: function() { | |
191 this.hidden = true; | |
192 this.eventTracker_.removeAll(); | |
193 this.parentNode.removeChild(this); | |
194 }, | |
195 | |
196 /** | |
197 * Handles keydown and mousedown events, dismissing the bubble if | |
198 * necessary. | |
199 * @param {Event} e The event. | |
200 * @private | |
201 */ | |
202 handleEvent: function(e) { | |
203 switch (e.type) { | |
204 case 'keydown': | |
205 if (e.keyCode == 27) { // Esc. | |
206 if (this.expanded) | |
207 this.collapseBubble_(); | |
208 } | |
209 break; | |
210 | |
211 case 'mousedown': | |
212 if (e.target == this.querySelector('.expandable-bubble-close')) { | |
213 this.hide(); | |
214 } else if (!this.contains(e.target)) { | |
215 if (this.expanded) | |
216 this.collapseBubble_(); | |
217 } | |
218 break; | |
219 } | |
220 | |
221 e.stopPropagation(); | |
arv (Not doing code reviews)
2011/10/27 16:50:01
I still like comments in the code for these
Evan Stade
2011/10/27 20:51:48
you only want a grab when expanded. This is blocki
| |
222 e.preventDefault(); | |
223 return; | |
arv (Not doing code reviews)
2011/10/27 16:50:01
useless return
| |
224 }, | |
225 }; | |
226 | |
227 /** | |
228 * Whether the bubble is expanded or not. | |
229 * @type {boolean} | |
230 */ | |
231 cr.defineProperty(ExpandableBubble, 'expanded', cr.PropertyKind.BOOL_ATTR); | |
232 | |
233 return { | |
234 ExpandableBubble: ExpandableBubble | |
235 }; | |
236 }); | |
OLD | NEW |