Chromium Code Reviews| Index: chrome/browser/resources/shared/js/cr/ui/expandable_bubble.js |
| =================================================================== |
| --- chrome/browser/resources/shared/js/cr/ui/expandable_bubble.js (revision 0) |
| +++ chrome/browser/resources/shared/js/cr/ui/expandable_bubble.js (revision 0) |
| @@ -0,0 +1,236 @@ |
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +// require: event_tracker.js |
| + |
| +cr.define('cr.ui', function() { |
| + 'use strict'; |
| + |
| + /** |
| + * ExpandableBubble is a free-floating compact informational bubble with an |
| + * arrow that points at a place of interest on the page. When clicked, the |
| + * bubble expands to show more of its content. Width of the bubble is the |
| + * width of the node it is overlapping when unexpanded. Expanded, it is of a |
| + * fixed width, but variable height. Currently the arrow is always positioned |
| + * at the bottom right and points down. |
| + * @constructor |
| + * @extends {cr.ui.div} |
| + */ |
| + var ExpandableBubble = cr.ui.define('div'); |
| + |
| + ExpandableBubble.prototype = { |
| + __proto__: HTMLDivElement.prototype, |
| + |
| + /** @inheritDoc */ |
| + decorate: function() { |
| + this.className = 'expandable-bubble'; |
| + this.innerHTML = |
| + '<div class="expandable-bubble-contents">' + |
| + '<div class="expandable-bubble-title"></div>' + |
| + '<div class="expandable-bubble-main" hidden></div>' + |
| + '</div>' + |
| + '<div class="expandable-bubble-close" hidden></div>' + |
| + '<div class="expandable-bubble-shadow"></div>' + |
| + '<div class="expandable-bubble-arrow"></div>'; |
| + |
| + this.hidden = true; |
| + }, |
| + |
| + /** |
| + * Sets the title of the bubble. The title is always visible when the |
| + * bubble is visible. |
| + * @type {Node} An HTML element to set as the title. |
| + */ |
| + set contentTitle(node) { |
| + var bubbleTitle = this.querySelector('.expandable-bubble-title'); |
| + bubbleTitle.textContent = ''; |
| + bubbleTitle.appendChild(node); |
| + }, |
| + |
| + /** |
| + * Sets the content node of the bubble. The content node is only visible |
| + * when the bubble is expanded. |
| + * @param {Node} An HTML element. |
| + */ |
| + set content(node) { |
| + var bubbleMain = this.querySelector('.expandable-bubble-main'); |
| + bubbleMain.textContent = ''; |
| + bubbleMain.appendChild(node); |
| + }, |
| + |
| + /** |
| + * Sets the anchor node, i.e. the node that this bubble points at and |
| + * partially overlaps. |
| + * @param {HTMLElement} node The new anchor node. |
| + */ |
| + set anchorNode(node) { |
| + this.anchorNode_ = node; |
| + |
| + if (!this.hidden) |
| + this.resizeAndReposition_(); |
| + }, |
| + |
| + /** |
| + * Updates the position of the bubble. |
| + * @private |
| + */ |
| + reposition_: function() { |
| + var clientRect = this.anchorNode_.getBoundingClientRect(); |
| + this.style.left = this.style.right = clientRect.left + 'px'; |
| + |
| + var top = clientRect.top - 1; |
| + if (this.expanded) { |
| + this.style.top = |
| + (top - this.offsetHeight + this.unexpandedHeight) + 'px'; |
| + } else { |
| + this.style.top = top + 'px'; |
| + } |
| + }, |
| + |
| + /** |
| + * Resizes the bubble and then repositions it. |
| + * @private |
| + */ |
| + resizeAndReposition_: function() { |
| + var clientRect = this.anchorNode_.getBoundingClientRect(); |
| + var width = clientRect.width; |
| + if (this.expanded) { |
| + var expandedWidth = 250; |
| + this.style.marginLeft = (width - expandedWidth) + 'px'; |
| + width = expandedWidth; |
| + } else { |
| + this.style.marginLeft = '0'; |
| + } |
| + |
| + // Width is dynamic (when not expanded) based on the width of the anchor |
| + // node, and the title and shadow need to follow suit. |
| + this.style.width = width + 'px'; |
| + var bubbleTitle = this.querySelector('.expandable-bubble-title'); |
| + bubbleTitle.style.width = width - 2 + 'px'; |
| + var bubbleContent = this.querySelector('.expandable-bubble-main'); |
| + bubbleContent.style.width = width - 12 + 'px'; |
| + var bubbleShadow = this.querySelector('.expandable-bubble-shadow'); |
| + bubbleShadow.style.width = width + 2 + 'px'; |
| + |
| + // Also reposition the bubble -- dimensions have potentially changed. |
| + this.reposition_(); |
| + }, |
| + |
| + /* |
| + * Expand the bubble (bringing the full content into view). |
| + * @private |
| + */ |
| + expandBubble_: function() { |
| + this.querySelector('.expandable-bubble-main').hidden = false; |
| + this.querySelector('.expandable-bubble-close').hidden = false; |
| + this.expanded = true; |
| + this.resizeAndReposition_(); |
| + }, |
| + |
| + /** |
| + * Collapse the bubble, hiding the main content and the close button. |
| + * This is automatically called when the window is resized. |
| + * @private |
| + */ |
| + collapseBubble_: function() { |
| + this.querySelector('.expandable-bubble-main').hidden = true; |
| + this.querySelector('.expandable-bubble-close').hidden = true; |
| + this.expanded = false; |
| + this.resizeAndReposition_(); |
| + }, |
| + |
| + /** |
| + * The onclick handler for the notification (expands the bubble). |
| + * @param {Event} e The event. |
| + * @private |
| + */ |
| + onNotificationClick_ : function(e) { |
| + if (!this.contains(e.target)) |
| + return; |
| + |
| + if (!this.expanded) { |
| + // Save the height of the unexpanded bubble, so we can make sure to |
| + // position it correctly (arrow points in the same location) after |
| + // we expand it. |
| + this.unexpandedHeight = this.offsetHeight; |
| + } |
| + |
| + this.expandBubble_(); |
| + }, |
| + |
| + /** |
| + * Starts showing the bubble. The bubble will grab input and show until the |
| + * user clicks away. |
| + */ |
| + show: function() { |
| + if (!this.hidden) |
| + return; |
| + |
| + document.body.appendChild(this); |
| + this.hidden = false; |
| + this.resizeAndReposition_(); |
| + |
| + this.eventTracker_ = new EventTracker; |
| + this.eventTracker_.add(window, |
| + 'load', this.resizeAndReposition_.bind(this)); |
| + this.eventTracker_.add(window, |
| + 'resize', this.collapseBubble_.bind(this)); |
| + this.eventTracker_.add(window, |
|
Evan Stade
2011/10/27 20:51:48
don't connect on |window|
|
| + 'click', this.onNotificationClick_.bind(this)); |
| + |
| + var doc = this.ownerDocument; |
| + this.eventTracker_.add(doc, 'keydown', this, true); |
| + this.eventTracker_.add(doc, 'mousedown', this, true); |
| + }, |
| + |
| + /** |
| + * Hides the bubble from view. |
| + */ |
| + hide: function() { |
| + this.hidden = true; |
| + this.eventTracker_.removeAll(); |
| + this.parentNode.removeChild(this); |
| + }, |
| + |
| + /** |
| + * Handles keydown and mousedown events, dismissing the bubble if |
| + * necessary. |
| + * @param {Event} e The event. |
| + * @private |
| + */ |
| + handleEvent: function(e) { |
| + switch (e.type) { |
| + case 'keydown': |
| + if (e.keyCode == 27) { // Esc. |
| + if (this.expanded) |
| + this.collapseBubble_(); |
| + } |
| + break; |
| + |
| + case 'mousedown': |
| + if (e.target == this.querySelector('.expandable-bubble-close')) { |
| + this.hide(); |
| + } else if (!this.contains(e.target)) { |
| + if (this.expanded) |
| + this.collapseBubble_(); |
| + } |
| + break; |
| + } |
| + |
| + 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
|
| + e.preventDefault(); |
| + return; |
|
arv (Not doing code reviews)
2011/10/27 16:50:01
useless return
|
| + }, |
| + }; |
| + |
| + /** |
| + * Whether the bubble is expanded or not. |
| + * @type {boolean} |
| + */ |
| + cr.defineProperty(ExpandableBubble, 'expanded', cr.PropertyKind.BOOL_ATTR); |
| + |
| + return { |
| + ExpandableBubble: ExpandableBubble |
| + }; |
| +}); |
| Property changes on: chrome\browser\resources\shared\js\cr\ui\expandable_bubble.js |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| + LF |