Chromium Code Reviews
|
| 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 |