1// ==ClosureCompiler== 2// @compilation_level ADVANCED_OPTIMIZATIONS 3// @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3.js 4// ==/ClosureCompiler== 5 6/** 7 * @name CSS3 InfoBubble with tabs for Google Maps API V3 8 * @version 0.8 9 * @author Luke Mahe 10 * @fileoverview 11 * This library is a CSS Infobubble with tabs. It uses css3 rounded corners and 12 * drop shadows and animations. It also allows tabs 13 */ 14 15/* 16 * Licensed under the Apache License, Version 2.0 (the "License"); 17 * you may not use this file except in compliance with the License. 18 * You may obtain a copy of the License at 19 * 20 * http://www.apache.org/licenses/LICENSE-2.0 21 * 22 * Unless required by applicable law or agreed to in writing, software 23 * distributed under the License is distributed on an "AS IS" BASIS, 24 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 * See the License for the specific language governing permissions and 26 * limitations under the License. 27 */ 28 29 30/** 31 * A CSS3 InfoBubble v0.8 32 * @param {Object.<string, *>=} opt_options Optional properties to set. 33 * @extends {google.maps.OverlayView} 34 * @constructor 35 */ 36function InfoBubble(opt_options) { 37 this.extend(InfoBubble, google.maps.OverlayView); 38 this.tabs_ = []; 39 this.activeTab_ = null; 40 this.baseZIndex_ = 100; 41 this.isOpen_ = false; 42 43 var options = opt_options || {}; 44 45 if (options['backgroundColor'] == undefined) { 46 options['backgroundColor'] = this.BACKGROUND_COLOR_; 47 } 48 49 if (options['borderColor'] == undefined) { 50 options['borderColor'] = this.BORDER_COLOR_; 51 } 52 53 if (options['borderRadius'] == undefined) { 54 options['borderRadius'] = this.BORDER_RADIUS_; 55 } 56 57 if (options['borderWidth'] == undefined) { 58 options['borderWidth'] = this.BORDER_WIDTH_; 59 } 60 61 if (options['padding'] == undefined) { 62 options['padding'] = this.PADDING_; 63 } 64 65 if (options['arrowPosition'] == undefined) { 66 options['arrowPosition'] = this.ARROW_POSITION_; 67 } 68 69 if (options['disableAutoPan'] == undefined) { 70 options['disableAutoPan'] = false; 71 } 72 73 if (options['disableAnimation'] == undefined) { 74 options['disableAnimation'] = false; 75 } 76 77 if (options['minWidth'] == undefined) { 78 options['minWidth'] = this.MIN_WIDTH_; 79 } 80 81 if (options['shadowStyle'] == undefined) { 82 options['shadowStyle'] = this.SHADOW_STYLE_; 83 } 84 85 if (options['arrowSize'] == undefined) { 86 options['arrowSize'] = this.ARROW_SIZE_; 87 } 88 89 if (options['arrowStyle'] == undefined) { 90 options['arrowStyle'] = this.ARROW_STYLE_; 91 } 92 93 this.buildDom_(); 94 95 this.setValues(options); 96} 97window['InfoBubble'] = InfoBubble; 98 99 100/** 101 * Default arrow size 102 * @const 103 * @private 104 */ 105InfoBubble.prototype.ARROW_SIZE_ = 15; 106 107 108/** 109 * Default arrow style 110 * @const 111 * @private 112 */ 113InfoBubble.prototype.ARROW_STYLE_ = 0; 114 115 116/** 117 * Default shadow style 118 * @const 119 * @private 120 */ 121InfoBubble.prototype.SHADOW_STYLE_ = 1; 122 123 124/** 125 * Default min width 126 * @const 127 * @private 128 */ 129InfoBubble.prototype.MIN_WIDTH_ = 50; 130 131 132/** 133 * Default arrow position 134 * @const 135 * @private 136 */ 137InfoBubble.prototype.ARROW_POSITION_ = 50; 138 139 140/** 141 * Default padding 142 * @const 143 * @private 144 */ 145InfoBubble.prototype.PADDING_ = 10; 146 147 148/** 149 * Default border width 150 * @const 151 * @private 152 */ 153InfoBubble.prototype.BORDER_WIDTH_ = 1; 154 155 156/** 157 * Default border color 158 * @const 159 * @private 160 */ 161InfoBubble.prototype.BORDER_COLOR_ = '#ccc'; 162 163 164/** 165 * Default border radius 166 * @const 167 * @private 168 */ 169InfoBubble.prototype.BORDER_RADIUS_ = 10; 170 171 172/** 173 * Default background color 174 * @const 175 * @private 176 */ 177InfoBubble.prototype.BACKGROUND_COLOR_ = '#fff'; 178 179 180/** 181 * Extends a objects prototype by anothers. 182 * 183 * @param {Object} obj1 The object to be extended. 184 * @param {Object} obj2 The object to extend with. 185 * @return {Object} The new extended object. 186 * @ignore 187 */ 188InfoBubble.prototype.extend = function(obj1, obj2) { 189 return (function(object) { 190 for (var property in object.prototype) { 191 this.prototype[property] = object.prototype[property]; 192 } 193 return this; 194 }).apply(obj1, [obj2]); 195}; 196 197 198/** 199 * Builds the InfoBubble dom 200 * @private 201 */ 202InfoBubble.prototype.buildDom_ = function() { 203 var bubble = this.bubble_ = document.createElement('DIV'); 204 bubble.style['position'] = 'absolute'; 205 bubble.style['zIndex'] = this.baseZIndex_; 206 207 var tabsContainer = this.tabsContainer_ = document.createElement('DIV'); 208 tabsContainer.style['position'] = 'relative'; 209 210 // Close button 211 var close = this.close_ = document.createElement('span'); 212 close.style['position'] = 'absolute'; 213 close.style['width'] = this.px(13); 214 close.style['height'] = this.px(13); 215 close.style['border'] = 0; 216 close.style['zIndex'] = this.baseZIndex_ + 1; 217 close.style['cursor'] = 'pointer'; 218 close.className = 'button-close-infobubble'; 219 220 221 var that = this; 222 google.maps.event.addDomListener(close, 'click', function() { 223 that.close(); 224 google.maps.event.trigger(that, 'closeclick'); 225 }); 226 227 // Content area 228 var contentContainer = this.contentContainer_ = document.createElement('DIV');
229 contentContainer.style['overflowX'] = 'auto'; 230 contentContainer.style['overflowY'] = 'auto'; 231 contentContainer.style['cursor'] = 'default'; 232 contentContainer.style['clear'] = 'both'; 233 contentContainer.style['position'] = 'relative'; 234 235 var content = this.content_ = document.createElement('DIV'); 236 contentContainer.appendChild(content); 237 238 // Arrow 239 var arrow = this.arrow_ = document.createElement('DIV'); 240 arrow.style['position'] = 'relative'; 241 242 var arrowOuter = this.arrowOuter_ = document.createElement('DIV'); 243 var arrowInner = this.arrowInner_ = document.createElement('DIV'); 244 245 var arrowSize = this.getArrowSize_(); 246 247 arrowOuter.style['position'] = arrowInner.style['position'] = 'absolute'; 248 arrowOuter.style['left'] = arrowInner.style['left'] = '50%'; 249 arrowOuter.style['height'] = arrowInner.style['height'] = '0'; 250 arrowOuter.style['width'] = arrowInner.style['width'] = '0'; 251 arrowOuter.style['marginLeft'] = this.px(-arrowSize); 252 arrowOuter.style['borderWidth'] = this.px(arrowSize); 253 arrowOuter.style['borderBottomWidth'] = 0; 254 255 // Shadow 256 var bubbleShadow = this.bubbleShadow_ = document.createElement('DIV'); 257 bubbleShadow.style['position'] = 'absolute'; 258 259 // Hide the InfoBubble by default 260 bubble.style['display'] = bubbleShadow.style['display'] = 'none'; 261 262 bubble.appendChild(this.tabsContainer_); 263 bubble.appendChild(close); 264 bubble.appendChild(contentContainer); 265 arrow.appendChild(arrowOuter); 266 arrow.appendChild(arrowInner); 267 bubble.appendChild(arrow); 268 269 var stylesheet = document.createElement('style'); 270 stylesheet.setAttribute('type', 'text/css'); 271 272 /** 273 * The animation for the infobubble 274 * @type {string} 275 */ 276 this.animationName_ = '_ibani_' + Math.round(Math.random() * 10000); 277 278 var css = '.' + this.animationName_ + '{-webkit-animation-name:' + 279 this.animationName_ + ';-webkit-animation-duration:0.5s;' + 280 '-webkit-animation-iteration-count:1;}' + 281 '@-webkit-keyframes ' + this.animationName_ + ' {from {' + 282 '-webkit-transform: scale(0)}50% {-webkit-transform: scale(1.2)}90% ' + 283 '{-webkit-transform: scale(0.95)}to {-webkit-transform: scale(1)}}'; 284 285 stylesheet.textContent = css; 286 document.getElementsByTagName('head')[0].appendChild(stylesheet); 287}; 288 289 290/** 291 * Sets the background class name 292 * 293 * @param {string} className The class name to set. 294 */ 295InfoBubble.prototype.setBackgroundClassName = function(className) { 296 this.set('backgroundClassName', className); 297}; 298InfoBubble.prototype['setBackgroundClassName'] = 299 InfoBubble.prototype.setBackgroundClassName; 300 301 302/** 303 * changed MVC callback 304 */ 305InfoBubble.prototype.backgroundClassName_changed = function() { 306 this.content_.className = this.get('backgroundClassName'); 307}; 308InfoBubble.prototype['backgroundClassName_changed'] = 309 InfoBubble.prototype.backgroundClassName_changed; 310 311 312/** 313 * Sets the class of the tab 314 * 315 * @param {string} className the class name to set. 316 */ 317InfoBubble.prototype.setTabClassName = function(className) { 318 this.set('tabClassName', className); 319}; 320InfoBubble.prototype['setTabClassName'] = 321 InfoBubble.prototype.setTabClassName; 322 323 324/** 325 * tabClassName changed MVC callback 326 */ 327InfoBubble.prototype.tabClassName_changed = function() { 328 this.updateTabStyles_(); 329}; 330InfoBubble.prototype['tabClassName_changed'] = 331 InfoBubble.prototype.tabClassName_changed; 332 333 334/** 335 * Gets the style of the arrow 336 * 337 * @private 338 * @return {number} The style of the arrow. 339 */ 340InfoBubble.prototype.getArrowStyle_ = function() { 341 return parseInt(this.get('arrowStyle'), 10) || 0; 342}; 343 344 345/** 346 * Sets the style of the arrow 347 * 348 * @param {number} style The style of the arrow. 349 */ 350InfoBubble.prototype.setArrowStyle = function(style) { 351 this.set('arrowStyle', style); 352}; 353InfoBubble.prototype['setArrowStyle'] = 354 InfoBubble.prototype.setArrowStyle; 355 356 357/** 358 * Arrow style changed MVC callback 359 */ 360InfoBubble.prototype.arrowStyle_changed = function() { 361 this.arrowSize_changed(); 362}; 363InfoBubble.prototype['arrowStyle_changed'] = 364 InfoBubble.prototype.arrowStyle_changed; 365 366 367/** 368 * Gets the size of the arrow 369 * 370 * @private 371 * @return {number} The size of the arrow. 372 */ 373InfoBubble.prototype.getArrowSize_ = function() { 374 return parseInt(this.get('arrowSize'), 10) || 0; 375}; 376 377 378/** 379 * Sets the size of the arrow 380 * 381 * @param {number} size The size of the arrow. 382 */ 383InfoBubble.prototype.setArrowSize = function(size) { 384 this.set('arrowSize', size); 385}; 386InfoBubble.prototype['setArrowSize'] = 387 InfoBubble.prototype.setArrowSize; 388 389 390/** 391 * Arrow size changed MVC callback 392 */ 393InfoBubble.prototype.arrowSize_changed = function() { 394 this.borderWidth_changed(); 395};
396InfoBubble.prototype['arrowSize_changed'] = 397 InfoBubble.prototype.arrowSize_changed; 398 399 400/** 401 * Set the position of the InfoBubble arrow 402 * 403 * @param {number} pos The position to set. 404 */ 405InfoBubble.prototype.setArrowPosition = function(pos) { 406 this.set('arrowPosition', pos); 407}; 408InfoBubble.prototype['setArrowPosition'] = 409 InfoBubble.prototype.setArrowPosition; 410 411 412/** 413 * Get the position of the InfoBubble arrow 414 * 415 * @private 416 * @return {number} The position.. 417 */ 418InfoBubble.prototype.getArrowPosition_ = function() { 419 return parseInt(this.get('arrowPosition'), 10) || 0; 420}; 421 422 423/** 424 * arrowPosition changed MVC callback 425 */ 426InfoBubble.prototype.arrowPosition_changed = function() { 427 var pos = this.getArrowPosition_(); 428 this.arrowOuter_.style['left'] = this.arrowInner_.style['left'] = pos + '%'; 429 430 this.redraw_(); 431}; 432InfoBubble.prototype['arrowPosition_changed'] = 433 InfoBubble.prototype.arrowPosition_changed; 434 435 436/** 437 * Set the zIndex of the InfoBubble 438 * 439 * @param {number} zIndex The zIndex to set. 440 */ 441InfoBubble.prototype.setZIndex = function(zIndex) { 442 this.set('zIndex', zIndex); 443}; 444InfoBubble.prototype['setZIndex'] = InfoBubble.prototype.setZIndex; 445 446
447/** 448 * Get the zIndex of the InfoBubble 449 * 450 * @return {number} The zIndex to set. 451 */ 452InfoBubble.prototype.getZIndex = function() { 453 return parseInt(this.get('zIndex'), 10) || this.baseZIndex_; 454}; 455 456 457/** 458 * zIndex changed MVC callback 459 */ 460InfoBubble.prototype.zIndex_changed = function() { 461 var zIndex = this.getZIndex(); 462 463 this.bubble_.style['zIndex'] = this.baseZIndex_ = zIndex; 464 this.close_.style['zIndex'] = zIndex + 1; 465}; 466InfoBubble.prototype['zIndex_changed'] = InfoBubble.prototype.zIndex_changed; 467 468 469/** 470 * Set the style of the shadow 471 * 472 * @param {number} shadowStyle The style of the shadow. 473 */ 474InfoBubble.prototype.setShadowStyle = function(shadowStyle) { 475 this.set('shadowStyle', shadowStyle); 476}; 477InfoBubble.prototype['setShadowStyle'] = InfoBubble.prototype.setShadowStyle; 478 479 480/** 481 * Get the style of the shadow 482 * 483 * @private 484 * @return {number} The style of the shadow. 485 */ 486InfoBubble.prototype.getShadowStyle_ = function() { 487 return parseInt(this.get('shadowStyle'), 10) || 0; 488}; 489 490 491/** 492 * shadowStyle changed MVC callback 493 */ 494InfoBubble.prototype.shadowStyle_changed = function() { 495 var shadowStyle = this.getShadowStyle_(); 496 497 var display = ''; 498 var shadow = ''; 499 var backgroundColor = ''; 500 switch (shadowStyle) { 501 case 0: 502 display = 'none'; 503 break; 504 case 1: 505 shadow = '40px 15px 10px rgba(33,33,33,0.3)'; 506 backgroundColor = 'transparent'; 507 break; 508 case 2: 509 shadow = '0 0 2px rgba(33,33,33,0.3)'; 510 backgroundColor = 'rgba(33,33,33,0.35)'; 511 break; 512 } 513 this.bubbleShadow_.style['boxShadow'] = 514 this.bubbleShadow_.style['webkitBoxShadow'] = 515 this.bubbleShadow_.style['MozBoxShadow'] = shadow; 516 this.bubbleShadow_.style['backgroundColor'] = backgroundColor; 517 if (this.isOpen_) { 518 this.bubbleShadow_.style['display'] = display; 519 this.draw(); 520 } 521}; 522InfoBubble.prototype['shadowStyle_changed'] = 523 InfoBubble.prototype.shadowStyle_changed; 524 525 526/** 527 * Show the close button 528 */ 529InfoBubble.prototype.showCloseButton = function() { 530 this.set('hideCloseButton', false); 531}; 532InfoBubble.prototype['showCloseButton'] = InfoBubble.prototype.showCloseButton; 533 534 535/** 536 * Hide the close button 537 */ 538InfoBubble.prototype.hideCloseButton = function() { 539 this.set('hideCloseButton', true); 540}; 541InfoBubble.prototype['hideCloseButton'] = InfoBubble.prototype.hideCloseButton; 542 543 544/** 545 * hideCloseButton changed MVC callback 546 */ 547InfoBubble.prototype.hideCloseButton_changed = function() { 548 this.close_.style['display'] = this.get('hideCloseButton') ? 'none' : ''; 549}; 550InfoBubble.prototype['hideCloseButton_changed'] = 551 InfoBubble.prototype.hideCloseButton_changed; 552 553 554/** 555 * Set the background color 556 * 557 * @param {string} color The color to set. 558 */ 559InfoBubble.prototype.setBackgroundColor = function(color) { 560 if (color) { 561 this.set('backgroundColor', color); 562 } 563}; 564InfoBubble.prototype['setBackgroundColor'] = 565 InfoBubble.prototype.setBackgroundColor; 566 567 568/** 569 * backgroundColor changed MVC callback 570 */ 571InfoBubble.prototype.backgroundColor_changed = function() { 572 var backgroundColor = this.get('backgroundColor'); 573 this.contentContainer_.style['backgroundColor'] = backgroundColor; 574 575 this.arrowInner_.style['borderColor'] = backgroundColor + 576 ' transparent transparent'; 577 this.updateTabStyles_(); 578}; 579InfoBubble.prototype['backgroundColor_changed'] = 580 InfoBubble.prototype.backgroundColor_changed; 581 582 583/** 584 * Set the border color 585 * 586 * @param {string} color The border color. 587 */ 588InfoBubble.prototype.setBorderColor = function(color) { 589 if (color) { 590 this.set('borderColor', color); 591 } 592}; 593InfoBubble.prototype['setBorderColor'] = InfoBubble.prototype.setBorderColor; 594 595 596/** 597 * borderColor changed MVC callback 598 */ 599InfoBubble.prototype.borderColor_changed = function() { 600 var borderColor = this.get('borderColor'); 601 602 var contentContainer = this.contentContainer_; 603 var arrowOuter = this.arrowOuter_; 604 contentContainer.style['borderColor'] = borderColor; 605 606 arrowOuter.style['borderColor'] = borderColor + 607 ' transparent transparent'; 608 609 contentContainer.style['borderStyle'] = 610 arrowOuter.style['borderStyle'] = 611 this.arrowInner_.style['borderStyle'] = 'solid'; 612 613 this.updateTabStyles_(); 614};
615InfoBubble.prototype['borderColor_changed'] = 616 InfoBubble.prototype.borderColor_changed; 617 618 619/** 620 * Set the radius of the border 621 * 622 * @param {number} radius The radius of the border. 623 */ 624InfoBubble.prototype.setBorderRadius = function(radius) { 625 this.set('borderRadius', radius); 626}; 627InfoBubble.prototype['setBorderRadius'] = InfoBubble.prototype.setBorderRadius; 628 629 630/** 631 * Get the radius of the border 632 * 633 * @private 634 * @return {number} The radius of the border. 635 */ 636InfoBubble.prototype.getBorderRadius_ = function() { 637 return parseInt(this.get('borderRadius'), 10) || 0; 638}; 639 640 641/** 642 * borderRadius changed MVC callback 643 */ 644InfoBubble.prototype.borderRadius_changed = function() { 645 var borderRadius = this.getBorderRadius_(); 646 var borderWidth = this.getBorderWidth_(); 647 648 this.contentContainer_.style['borderRadius'] = 649 this.contentContainer_.style['MozBorderRadius'] = 650 this.contentContainer_.style['webkitBorderRadius'] = 651 this.bubbleShadow_.style['borderRadius'] = 652 this.bubbleShadow_.style['MozBorderRadius'] = 653 this.bubbleShadow_.style['webkitBorderRadius'] = this.px(borderRadius); 654 655 this.tabsContainer_.style['paddingLeft'] = 656 this.tabsContainer_.style['paddingRight'] = 657 this.px(borderRadius + borderWidth); 658 659 this.redraw_(); 660}; 661InfoBubble.prototype['borderRadius_changed'] = 662 InfoBubble.prototype.borderRadius_changed; 663 664 665/** 666 * Get the width of the border 667 * 668 * @private 669 * @return {number} width The width of the border. 670 */ 671InfoBubble.prototype.getBorderWidth_ = function() { 672 return parseInt(this.get('borderWidth'), 10) || 0; 673}; 674 675 676/** 677 * Set the width of the border 678 * 679 * @param {number} width The width of the border. 680 */ 681InfoBubble.prototype.setBorderWidth = function(width) { 682 this.set('borderWidth', width); 683}; 684InfoBubble.prototype['setBorderWidth'] = InfoBubble.prototype.setBorderWidth; 685 686 687/** 688 * borderWidth change MVC callback 689 */ 690InfoBubble.prototype.borderWidth_changed = function() { 691 var borderWidth = this.getBorderWidth_(); 692 693 this.contentContainer_.style['borderWidth'] = this.px(borderWidth); 694 this.tabsContainer_.style['top'] = this.px(borderWidth); 695 696 this.updateArrowStyle_(); 697 this.updateTabStyles_(); 698 this.borderRadius_changed(); 699 this.redraw_(); 700}; 701InfoBubble.prototype['borderWidth_changed'] = 702 InfoBubble.prototype.borderWidth_changed; 703 704 705/** 706 * Update the arrow style 707 * @private 708 */ 709InfoBubble.prototype.updateArrowStyle_ = function() { 710 var borderWidth = this.getBorderWidth_(); 711 var arrowSize = this.getArrowSize_(); 712 var arrowStyle = this.getArrowStyle_(); 713 var arrowOuterSizePx = this.px(arrowSize); 714 var arrowInnerSizePx = this.px(Math.max(0, arrowSize - borderWidth)); 715 716 var outer = this.arrowOuter_; 717 var inner = this.arrowInner_; 718 719 this.arrow_.style['marginTop'] = this.px(-borderWidth); 720 outer.style['borderTopWidth'] = arrowOuterSizePx; 721 inner.style['borderTopWidth'] = arrowInnerSizePx; 722 723 // Full arrow or arrow pointing to the left 724 if (arrowStyle == 0 || arrowStyle == 1) { 725 outer.style['borderLeftWidth'] = arrowOuterSizePx; 726 inner.style['borderLeftWidth'] = arrowInnerSizePx; 727 } else { 728 outer.style['borderLeftWidth'] = inner.style['borderLeftWidth'] = 0; 729 } 730 731 // Full arrow or arrow pointing to the right 732 if (arrowStyle == 0 || arrowStyle == 2) { 733 outer.style['borderRightWidth'] = arrowOuterSizePx; 734 inner.style['borderRightWidth'] = arrowInnerSizePx; 735 } else { 736 outer.style['borderRightWidth'] = inner.style['borderRightWidth'] = 0; 737 } 738 739 if (arrowStyle < 2) { 740 outer.style['marginLeft'] = this.px(-(arrowSize)); 741 inner.style['marginLeft'] = this.px(-(arrowSize - borderWidth)); 742 } else { 743 outer.style['marginLeft'] = inner.style['marginLeft'] = 0; 744 } 745 746 // If there is no border then don't show thw outer arrow 747 if (borderWidth == 0) { 748 outer.style['display'] = 'none'; 749 } else { 750 outer.style['display'] = ''; 751 } 752}; 753 754 755/** 756 * Set the padding of the InfoBubble 757 * 758 * @param {number} padding The padding to apply. 759 */ 760InfoBubble.prototype.setPadding = function(padding) { 761 this.set('padding', padding); 762}; 763InfoBubble.prototype['setPadding'] = InfoBubble.prototype.setPadding; 764 765 766/** 767 * Set the padding of the InfoBubble 768 * 769 * @private 770 * @return {number} padding The padding to apply. 771 */ 772InfoBubble.prototype.getPadding_ = function() { 773 return parseInt(this.get('padding'), 10) || 0; 774}; 775 776 777/** 778 * padding changed MVC callback 779 */ 780InfoBubble.prototype.padding_changed = function() { 781 var padding = this.getPadding_(); 782 this.contentContainer_.style['padding'] = this.px(padding); 783 this.updateTabStyles_(); 784 785 this.redraw_(); 786};
787InfoBubble.prototype['padding_changed'] = InfoBubble.prototype.padding_changed; 788 789 790/** 791 * Add px extention to the number 792 * 793 * @param {number} num The number to wrap. 794 * @return {string|number} A wrapped number. 795 */ 796InfoBubble.prototype.px = function(num) { 797 if (num) { 798 // 0 doesn't need to be wrapped 799 return num + 'px'; 800 } 801 return num; 802}; 803 804 805/** 806 * Add events to stop propagation 807 * @private 808 */ 809InfoBubble.prototype.addEvents_ = function() { 810 // We want to cancel all the events so they do not go to the map 811 var events = ['mousedown', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 812 'mousewheel', 'DOMMouseScroll', 'touchstart', 'touchend', 'touchmove', 813 'dblclick', 'contextmenu', 'click']; 814 815 var bubble = this.bubble_; 816 this.listeners_ = []; 817 for (var i = 0, event; event = events[i]; i++) { 818 this.listeners_.push( 819 google.maps.event.addDomListener(bubble, event, function(e) { 820 e.cancelBubble = true; 821 if (e.stopPropagation) { 822 e.stopPropagation(); 823 } 824 }) 825 ); 826 } 827}; 828 829 830/** 831 * On Adding the InfoBubble to a map 832 * Implementing the OverlayView interface 833 */ 834InfoBubble.prototype.onAdd = function() { 835 if (!this.bubble_) { 836 this.buildDom_(); 837 } 838 839 this.addEvents_(); 840 841 var panes = this.getPanes(); 842 if (panes) { 843 panes.floatPane.appendChild(this.bubble_); 844 panes.floatShadow.appendChild(this.bubbleShadow_); 845 } 846}; 847InfoBubble.prototype['onAdd'] = InfoBubble.prototype.onAdd; 848 849 850/** 851 * Draw the InfoBubble 852 * Implementing the OverlayView interface 853 */ 854InfoBubble.prototype.draw = function() { 855 var projection = this.getProjection(); 856 857 if (!projection) { 858 // The map projection is not ready yet so do nothing 859 return; 860 } 861 862 var latLng = /** @type {google.maps.LatLng} */ (this.get('position')); 863 864 if (!latLng) { 865 this.close(); 866 return; 867 } 868 869 var tabHeight = 0; 870 871 if (this.activeTab_) { 872 tabHeight = this.activeTab_.offsetHeight; 873 } 874 875 var anchorHeight = this.getAnchorHeight_(); 876 var arrowSize = this.getArrowSize_(); 877 var arrowPosition = this.getArrowPosition_(); 878 879 arrowPosition = arrowPosition / 100; 880 881 var pos = projection.fromLatLngToDivPixel(latLng); 882 var width = this.contentContainer_.offsetWidth; 883 var height = this.bubble_.offsetHeight; 884 885 if (!width) { 886 return; 887 } 888 889 // Adjust for the height of the info bubble 890 var top = pos.y - (height + arrowSize); 891 892 if (anchorHeight) { 893 // If there is an anchor then include the height 894 top -= anchorHeight; 895 } 896 897 var left = pos.x - (width * arrowPosition); 898 899 this.bubble_.style['top'] = this.px(top); 900 this.bubble_.style['left'] = this.px(left); 901 902 var shadowStyle = parseInt(this.get('shadowStyle'), 10); 903 904 switch (shadowStyle) { 905 case 1: 906 // Shadow is behind 907 this.bubbleShadow_.style['top'] = this.px(top + tabHeight - 1); 908 this.bubbleShadow_.style['left'] = this.px(left); 909 this.bubbleShadow_.style['width'] = this.px(width); 910 this.bubbleShadow_.style['height'] = 911 this.px(this.contentContainer_.offsetHeight - arrowSize); 912 break; 913 case 2: 914 // Shadow is below 915 width = width * 0.8; 916 if (anchorHeight) { 917 this.bubbleShadow_.style['top'] = this.px(pos.y); 918 } else { 919 this.bubbleShadow_.style['top'] = this.px(pos.y + arrowSize); 920 } 921 this.bubbleShadow_.style['left'] = this.px(pos.x - width * arrowPosition); 922 923 this.bubbleShadow_.style['width'] = this.px(width); 924 this.bubbleShadow_.style['height'] = this.px(2); 925 break; 926 } 927}; 928InfoBubble.prototype['draw'] = InfoBubble.prototype.draw; 929 930 931/** 932 * Removing the InfoBubble from a map 933 */ 934InfoBubble.prototype.onRemove = function() { 935 if (this.bubble_ && this.bubble_.parentNode) { 936 this.bubble_.parentNode.removeChild(this.bubble_); 937 } 938 if (this.bubbleShadow_ && this.bubbleShadow_.parentNode) { 939 this.bubbleShadow_.parentNode.removeChild(this.bubbleShadow_); 940 } 941 942 for (var i = 0, listener; listener = this.listeners_[i]; i++) { 943 google.maps.event.removeListener(listener); 944 } 945}; 946InfoBubble.prototype['onRemove'] = InfoBubble.prototype.onRemove; 947 948 949/** 950 * Is the InfoBubble open 951 * 952 * @return {boolean} If the InfoBubble is open. 953 */ 954InfoBubble.prototype.isOpen = function() { 955 return this.isOpen_; 956}; 957InfoBubble.prototype['isOpen'] = InfoBubble.prototype.isOpen; 958 959 960/** 961 * Close the InfoBubble 962 */ 963InfoBubble.prototype.close = function() { 964 if (this.bubble_) { 965 this.bubble_.style['display'] = 'none'; 966 // Remove the animation so we next time it opens it will animate again 967 this.bubble_.className = 968 this.bubble_.className.replace(this.animationName_, ''); 969 } 970 971 if (this.bubbleShadow_) { 972 this.bubbleShadow_.style['display'] = 'none'; 973 this.bubbleShadow_.className = 974 this.bubbleShadow_.className.replace(this.animationName_, ''); 975 } 976 this.isOpen_ = false;
977}; 978InfoBubble.prototype['close'] = InfoBubble.prototype.close; 979 980 981/** 982 * Open the InfoBubble (asynchronous). 983 * 984 * @param {google.maps.Map=} opt_map Optional map to open on. 985 * @param {google.maps.MVCObject=} opt_anchor Optional anchor to position at. 986 */ 987InfoBubble.prototype.open = function(opt_map, opt_anchor) { 988 var that = this; 989 window.setTimeout(function() { 990 that.open_(opt_map, opt_anchor); 991 }, 0); 992}; 993 994/** 995 * Open the InfoBubble 996 * @private 997 * @param {google.maps.Map=} opt_map Optional map to open on. 998 * @param {google.maps.MVCObject=} opt_anchor Optional anchor to position at. 999 */ 1000InfoBubble.prototype.open_ = function(opt_map, opt_anchor) { 1001 this.updateContent_(); 1002 1003 if (opt_map) { 1004 this.setMap(opt_map); 1005 } 1006 1007 if (opt_anchor) { 1008 this.set('anchor', opt_anchor); 1009 this.bindTo('anchorPoint', opt_anchor); 1010 this.bindTo('position', opt_anchor); 1011 } 1012 1013 // Show the bubble and the show 1014 this.bubble_.style['display'] = this.bubbleShadow_.style['display'] = ''; 1015 var animation = !this.get('disableAnimation'); 1016 1017 if (animation) { 1018 // Add the animation 1019 this.bubble_.className += ' ' + this.animationName_; 1020 this.bubbleShadow_.className += ' ' + this.animationName_; 1021 } 1022 1023 this.redraw_(); 1024 this.isOpen_ = true; 1025 1026 var pan = !this.get('disableAutoPan'); 1027 if (pan) { 1028 var that = this; 1029 window.setTimeout(function() { 1030 // Pan into view, done in a time out to make it feel nicer :) 1031 that.panToView(); 1032 }, 200); 1033 } 1034}; 1035InfoBubble.prototype['open'] = InfoBubble.prototype.open; 1036 1037 1038/** 1039 * Set the position of the InfoBubble 1040 * 1041 * @param {google.maps.LatLng} position The position to set. 1042 */ 1043InfoBubble.prototype.setPosition = function(position) { 1044 if (position) { 1045 this.set('position', position); 1046 } 1047}; 1048InfoBubble.prototype['setPosition'] = InfoBubble.prototype.setPosition; 1049 1050 1051/** 1052 * Returns the position of the InfoBubble 1053 * 1054 * @return {google.maps.LatLng} the position. 1055 */ 1056InfoBubble.prototype.getPosition = function() { 1057 return /** @type {google.maps.LatLng} */ (this.get('position')); 1058}; 1059InfoBubble.prototype['getPosition'] = InfoBubble.prototype.getPosition; 1060 1061 1062/** 1063 * position changed MVC callback 1064 */ 1065InfoBubble.prototype.position_changed = function() { 1066 this.draw(); 1067}; 1068InfoBubble.prototype['position_changed'] = 1069 InfoBubble.prototype.position_changed; 1070 1071 1072/** 1073 * Pan the InfoBubble into view 1074 */ 1075InfoBubble.prototype.panToView = function() { 1076 var projection = this.getProjection(); 1077 1078 if (!projection) { 1079 // The map projection is not ready yet so do nothing 1080 return; 1081 } 1082 1083 if (!this.bubble_) { 1084 // No Bubble yet so do nothing 1085 return; 1086 } 1087 1088 var anchorHeight = this.getAnchorHeight_(); 1089 var height = this.bubble_.offsetHeight + anchorHeight; 1090 var map = this.get('map'); 1091 var mapDiv = map.getDiv(); 1092 var mapHeight = mapDiv.offsetHeight; 1093 1094 var latLng = this.getPosition(); 1095 var centerPos = projection.fromLatLngToContainerPixel(map.getCenter()); 1096 var pos = projection.fromLatLngToContainerPixel(latLng); 1097 1098 // Find out how much space at the top is free 1099 var spaceTop = centerPos.y - height; 1100 1101 // Fine out how much space at the bottom is free 1102 var spaceBottom = mapHeight - centerPos.y; 1103 1104 var needsTop = spaceTop < 0; 1105 var deltaY = 0; 1106 1107 if (needsTop) { 1108 spaceTop *= -1; 1109 deltaY = (spaceTop + spaceBottom) / 2; 1110 } 1111 1112 pos.y -= deltaY; 1113 latLng = projection.fromContainerPixelToLatLng(pos); 1114 1115 if (map.getCenter() != latLng) { 1116 map.panTo(latLng); 1117 } 1118}; 1119InfoBubble.prototype['panToView'] = InfoBubble.prototype.panToView; 1120 1121 1122/** 1123 * Converts a HTML string to a document fragment. 1124 * 1125 * @param {string} htmlString The HTML string to convert. 1126 * @return {Node} A HTML document fragment. 1127 * @private 1128 */ 1129InfoBubble.prototype.htmlToDocumentFragment_ = function(htmlString) { 1130 htmlString = htmlString.replace(/^\s*([\S\s]*)\b\s*$/, '$1'); 1131 var tempDiv = document.createElement('DIV'); 1132 tempDiv.innerHTML = htmlString; 1133 if (tempDiv.childNodes.length == 1) {
1134 return /** @type {!Node} */ (tempDiv.removeChild(tempDiv.firstChild)); 1135 } else { 1136 var fragment = document.createDocumentFragment(); 1137 while (tempDiv.firstChild) { 1138 fragment.appendChild(tempDiv.firstChild); 1139 } 1140 return fragment; 1141 } 1142}; 1143 1144 1145/** 1146 * Removes all children from the node. 1147 * 1148 * @param {Node} node The node to remove all children from. 1149 * @private 1150 */ 1151InfoBubble.prototype.removeChildren_ = function(node) { 1152 if (!node) { 1153 return; 1154 } 1155 1156 var child; 1157 while (child = node.firstChild) { 1158 node.removeChild(child); 1159 } 1160}; 1161 1162 1163/** 1164 * Sets the content of the infobubble. 1165 * 1166 * @param {string|Node} content The content to set. 1167 */ 1168InfoBubble.prototype.setContent = function(content) { 1169 this.set('content', content); 1170}; 1171InfoBubble.prototype['setContent'] = InfoBubble.prototype.setContent; 1172 1173 1174/** 1175 * Get the content of the infobubble. 1176 * 1177 * @return {string|Node} The marker content. 1178 */ 1179InfoBubble.prototype.getContent = function() { 1180 return /** @type {Node|string} */ (this.get('content')); 1181}; 1182InfoBubble.prototype['getContent'] = InfoBubble.prototype.getContent; 1183 1184 1185/** 1186 * Sets the marker content and adds loading events to images 1187 */ 1188InfoBubble.prototype.updateContent_ = function() { 1189 if (!this.content_) { 1190 // The Content area doesnt exist. 1191 return; 1192 } 1193 1194 this.removeChildren_(this.content_); 1195 var content = this.getContent(); 1196 if (content) { 1197 if (typeof content == 'string') { 1198 content = this.htmlToDocumentFragment_(content); 1199 } 1200 this.content_.appendChild(content); 1201 1202 var that = this; 1203 var images = this.content_.getElementsByTagName('IMG'); 1204 for (var i = 0, image; image = images[i]; i++) { 1205 // Because we don't know the size of an image till it loads, add a 1206 // listener to the image load so the marker can resize and reposition 1207 // itself to be the correct height. 1208 google.maps.event.addDomListener(image, 'load', function() { 1209 that.imageLoaded_(); 1210 }); 1211 } 1212 google.maps.event.trigger(this, 'domready'); 1213 } 1214 this.redraw_(); 1215}; 1216 1217/** 1218 * Image loaded 1219 * @private 1220 */ 1221InfoBubble.prototype.imageLoaded_ = function() { 1222 var pan = !this.get('disableAutoPan'); 1223 this.redraw_(); 1224 if (pan && (this.tabs_.length == 0 || this.activeTab_.index == 0)) { 1225 this.panToView(); 1226 } 1227}; 1228 1229/** 1230 * Updates the styles of the tabs 1231 * @private 1232 */ 1233InfoBubble.prototype.updateTabStyles_ = function() { 1234 if (this.tabs_ && this.tabs_.length) { 1235 for (var i = 0, tab; tab = this.tabs_[i]; i++) { 1236 this.setTabStyle_(tab.tab); 1237 } 1238 this.activeTab_.style['zIndex'] = this.baseZIndex_; 1239 var borderWidth = this.getBorderWidth_(); 1240 var padding = this.getPadding_() / 2; 1241 this.activeTab_.style['borderBottomWidth'] = 0; 1242 this.activeTab_.style['paddingBottom'] = this.px(padding + borderWidth); 1243 } 1244}; 1245 1246 1247/** 1248 * Sets the style of a tab 1249 * @private 1250 * @param {Element} tab The tab to style. 1251 */ 1252InfoBubble.prototype.setTabStyle_ = function(tab) { 1253 var backgroundColor = this.get('backgroundColor'); 1254 var borderColor = this.get('borderColor'); 1255 var borderRadius = this.getBorderRadius_(); 1256 var borderWidth = this.getBorderWidth_(); 1257 var padding = this.getPadding_(); 1258 1259 var marginRight = this.px(-(Math.max(padding, borderRadius))); 1260 var borderRadiusPx = this.px(borderRadius); 1261 1262 var index = this.baseZIndex_; 1263 if (tab.index) { 1264 index -= tab.index; 1265 } 1266 1267 // The styles for the tab 1268 var styles = { 1269 'cssFloat': 'left', 1270 'position': 'relative', 1271 'cursor': 'pointer', 1272 'backgroundColor': backgroundColor, 1273 'border': this.px(borderWidth) + ' solid ' + borderColor, 1274 'padding': this.px(padding / 2) + ' ' + this.px(padding), 1275 'marginRight': marginRight, 1276 'whiteSpace': 'nowrap', 1277 'borderRadiusTopLeft': borderRadiusPx, 1278 'MozBorderRadiusTopleft': borderRadiusPx, 1279 'webkitBorderTopLeftRadius': borderRadiusPx, 1280 'borderRadiusTopRight': borderRadiusPx, 1281 'MozBorderRadiusTopright': borderRadiusPx, 1282 'webkitBorderTopRightRadius': borderRadiusPx, 1283 'zIndex': index, 1284 'display': 'inline' 1285 }; 1286 1287 for (var style in styles) { 1288 tab.style[style] = styles[style]; 1289 } 1290 1291 var className = this.get('tabClassName'); 1292 if (className != undefined) { 1293 tab.className += ' ' + className; 1294 } 1295}; 1296 1297 1298/** 1299 * Add user actions to a tab 1300 * @private 1301 * @param {Object} tab The tab to add the actions to. 1302 */ 1303InfoBubble.prototype.addTabActions_ = function(tab) { 1304 var that = this; 1305 tab.listener_ = google.maps.event.addDomListener(tab, 'click', function() { 1306 that.setTabActive_(this); 1307 }); 1308}; 1309 1310 1311/** 1312 * Set a tab at a index to be active 1313 * 1314 * @param {number} index The index of the tab. 1315 */ 1316InfoBubble.prototype.setTabActive = function(index) { 1317 var tab = this.tabs_[index - 1]; 1318 1319 if (tab) { 1320 this.setTabActive_(tab.tab); 1321 } 1322}; 1323InfoBubble.prototype['setTabActive'] = InfoBubble.prototype.setTabActive; 1324 1325 1326/** 1327 * Set a tab to be active 1328 * @private 1329 * @param {Object} tab The tab to set active. 1330 */ 1331InfoBubble.prototype.setTabActive_ = function(tab) { 1332 if (!tab) { 1333 this.setContent(''); 1334 this.updateContent_(); 1335 return; 1336 } 1337 1338 var padding = this.getPadding_() / 2; 1339 var borderWidth = this.getBorderWidth_(); 1340 1341 if (this.activeTab_) { 1342 var activeTab = this.activeTab_; 1343 activeTab.style['zIndex'] = this.baseZIndex_ - activeTab.index; 1344 activeTab.style['paddingBottom'] = this.px(padding); 1345 activeTab.style['borderBottomWidth'] = this.px(borderWidth); 1346 } 1347 1348 tab.style['zIndex'] = this.baseZIndex_; 1349 tab.style['borderBottomWidth'] = 0; 1350 tab.style['marginBottomWidth'] = '-10px'; 1351 tab.style['paddingBottom'] = this.px(padding + borderWidth); 1352 1353 this.setContent(this.tabs_[tab.index].content); 1354 this.updateContent_(); 1355 1356 this.activeTab_ = tab; 1357 1358 this.redraw_(); 1359}; 1360 1361 1362/** 1363 * Set the max width of the InfoBubble 1364 * 1365 * @param {number} width The max width. 1366 */ 1367InfoBubble.prototype.setMaxWidth = function(width) { 1368 this.set('maxWidth', width); 1369}; 1370InfoBubble.prototype['setMaxWidth'] = InfoBubble.prototype.setMaxWidth; 1371 1372 1373/** 1374 * maxWidth changed MVC callback 1375 */ 1376InfoBubble.prototype.maxWidth_changed = function() { 1377 this.redraw_(); 1378};
1379InfoBubble.prototype['maxWidth_changed'] = 1380 InfoBubble.prototype.maxWidth_changed; 1381 1382 1383/** 1384 * Set the max height of the InfoBubble 1385 * 1386 * @param {number} height The max height. 1387 */ 1388InfoBubble.prototype.setMaxHeight = function(height) { 1389 this.set('maxHeight', height); 1390}; 1391InfoBubble.prototype['setMaxHeight'] = InfoBubble.prototype.setMaxHeight; 1392 1393 1394/** 1395 * maxHeight changed MVC callback 1396 */ 1397InfoBubble.prototype.maxHeight_changed = function() { 1398 this.redraw_(); 1399}; 1400InfoBubble.prototype['maxHeight_changed'] = 1401 InfoBubble.prototype.maxHeight_changed; 1402 1403 1404/** 1405 * Set the min width of the InfoBubble 1406 * 1407 * @param {number} width The min width. 1408 */ 1409InfoBubble.prototype.setMinWidth = function(width) { 1410 this.set('minWidth', width); 1411}; 1412InfoBubble.prototype['setMinWidth'] = InfoBubble.prototype.setMinWidth; 1413 1414 1415/** 1416 * minWidth changed MVC callback 1417 */ 1418InfoBubble.prototype.minWidth_changed = function() { 1419 this.redraw_(); 1420}; 1421InfoBubble.prototype['minWidth_changed'] = 1422 InfoBubble.prototype.minWidth_changed; 1423 1424 1425/** 1426 * Set the min height of the InfoBubble 1427 * 1428 * @param {number} height The min height. 1429 */ 1430InfoBubble.prototype.setMinHeight = function(height) { 1431 this.set('minHeight', height); 1432}; 1433InfoBubble.prototype['setMinHeight'] = InfoBubble.prototype.setMinHeight; 1434 1435 1436/** 1437 * minHeight changed MVC callback 1438 */ 1439InfoBubble.prototype.minHeight_changed = function() { 1440 this.redraw_(); 1441}; 1442InfoBubble.prototype['minHeight_changed'] = 1443 InfoBubble.prototype.minHeight_changed; 1444 1445 1446/** 1447 * Add a tab 1448 * 1449 * @param {string} label The label of the tab. 1450 * @param {string|Element} content The content of the tab. 1451 */ 1452InfoBubble.prototype.addTab = function(label, content) { 1453 var tab = document.createElement('DIV'); 1454 tab.innerHTML = label; 1455 1456 this.setTabStyle_(tab); 1457 this.addTabActions_(tab); 1458 1459 this.tabsContainer_.appendChild(tab); 1460 1461 this.tabs_.push({ 1462 label: label, 1463 content: content, 1464 tab: tab 1465 }); 1466 1467 tab.index = this.tabs_.length - 1; 1468 tab.style['zIndex'] = this.baseZIndex_ - tab.index; 1469 1470 if (!this.activeTab_) { 1471 this.setTabActive_(tab); 1472 } 1473 1474 tab.className = tab.className + ' ' + this.animationName_; 1475 1476 this.redraw_(); 1477}; 1478InfoBubble.prototype['addTab'] = InfoBubble.prototype.addTab; 1479 1480/** 1481 * Update a tab at a speicifc index 1482 * 1483 * @param {number} index The index of the tab. 1484 * @param {?string} opt_label The label to change to. 1485 * @param {?string} opt_content The content to update to. 1486 */ 1487InfoBubble.prototype.updateTab = function(index, opt_label, opt_content) { 1488 if (!this.tabs_.length || index < 0 || index >= this.tabs_.length) { 1489 return; 1490 } 1491 1492 var tab = this.tabs_[index]; 1493 if (opt_label != undefined) { 1494 tab.tab.innerHTML = tab.label = opt_label; 1495 } 1496 1497 if (opt_content != undefined) { 1498 tab.content = opt_content; 1499 } 1500 1501 if (this.activeTab_ == tab.tab) { 1502 this.setContent(tab.content); 1503 this.updateContent_(); 1504 } 1505 this.redraw_(); 1506}; 1507InfoBubble.prototype['updateTab'] = InfoBubble.prototype.updateTab; 1508 1509 1510/** 1511 * Remove a tab at a specific index 1512 * 1513 * @param {number} index The index of the tab to remove. 1514 */ 1515InfoBubble.prototype.removeTab = function(index) { 1516 if (!this.tabs_.length || index < 0 || index >= this.tabs_.length) { 1517 return; 1518 } 1519 1520 var tab = this.tabs_[index]; 1521 tab.tab.parentNode.removeChild(tab.tab); 1522 1523 google.maps.event.removeListener(tab.tab.listener_); 1524 1525 this.tabs_.splice(index, 1); 1526 1527 delete tab; 1528 1529 for (var i = 0, t; t = this.tabs_[i]; i++) { 1530 t.tab.index = i; 1531 } 1532 1533 if (tab.tab == this.activeTab_) { 1534 // Removing the current active tab 1535 if (this.tabs_[index]) { 1536 // Show the tab to the right 1537 this.activeTab_ = this.tabs_[index].tab; 1538 } else if (this.tabs_[index - 1]) { 1539 // Show a tab to the left 1540 this.activeTab_ = this.tabs_[index - 1].tab; 1541 } else { 1542 // No tabs left to sho 1543 this.activeTab_ = undefined; 1544 } 1545 1546 this.setTabActive_(this.activeTab_); 1547 } 1548 1549 this.redraw_(); 1550}; 1551InfoBubble.prototype['removeTab'] = InfoBubble.prototype.removeTab; 1552 1553 1554/** 1555 * Get the size of an element 1556 * @private 1557 * @param {Node|string} element The element to size. 1558 * @param {number=} opt_maxWidth Optional max width of the element. 1559 * @param {number=} opt_maxHeight Optional max height of the element. 1560 * @return {google.maps.Size} The size of the element. 1561 */ 1562InfoBubble.prototype.getElementSize_ = function(element, opt_maxWidth, 1563 opt_maxHeight) { 1564 var sizer = document.createElement('DIV'); 1565 sizer.style['display'] = 'inline'; 1566 sizer.style['position'] = 'absolute'; 1567 sizer.style['visibility'] = 'hidden'; 1568 1569 if (typeof element == 'string') { 1570 sizer.innerHTML = element; 1571 } else { 1572 sizer.appendChild(element.cloneNode(true)); 1573 } 1574 1575 document.body.appendChild(sizer); 1576 var size = new google.maps.Size(sizer.offsetWidth, sizer.offsetHeight); 1577 1578 // If the width is bigger than the max width then set the width and size again 1579 if (opt_maxWidth && size.width > opt_maxWidth) { 1580 sizer.style['width'] = this.px(opt_maxWidth); 1581 size = new google.maps.Size(sizer.offsetWidth, sizer.offsetHeight); 1582 } 1583 1584 // If the height is bigger than the max height then set the height and size 1585 // again 1586 if (opt_maxHeight && size.height > opt_maxHeight) { 1587 sizer.style['height'] = this.px(opt_maxHeight); 1588 size = new google.maps.Size(sizer.offsetWidth, sizer.offsetHeight); 1589 } 1590 1591 document.body.removeChild(sizer); 1592 delete sizer; 1593 return size; 1594}; 1595 1596 1597/** 1598 * Redraw the InfoBubble 1599 * @private 1600 */ 1601InfoBubble.prototype.redraw_ = function() { 1602 this.figureOutSize_(); 1603 this.positionCloseButton_(); 1604 this.draw(); 1605}; 1606 1607 1608/** 1609 * Figure out the optimum size of the InfoBubble 1610 * @private 1611 */ 1612InfoBubble.prototype.figureOutSize_ = function() { 1613 var map = this.get('map'); 1614 1615 if (!map) { 1616 return; 1617 } 1618 1619 var padding = this.getPadding_(); 1620 var borderWidth = this.getBorderWidth_(); 1621 var borderRadius = this.getBorderRadius_(); 1622 var arrowSize = this.getArrowSize_(); 1623 1624 var mapDiv = map.getDiv(); 1625 var gutter = arrowSize * 2; 1626 var mapWidth = mapDiv.offsetWidth - gutter; 1627 var mapHeight = mapDiv.offsetHeight - gutter - this.getAnchorHeight_(); 1628 var tabHeight = 0; 1629 var width = /** @type {number} */ (this.get('minWidth') || 0); 1630 var height = /** @type {number} */ (this.get('minHeight') || 0); 1631 var maxWidth = /** @type {number} */ (this.get('maxWidth') || 0); 1632 var maxHeight = /** @type {number} */ (this.get('maxHeight') || 0); 1633 1634 maxWidth = Math.min(mapWidth, maxWidth); 1635 maxHeight = Math.min(mapHeight, maxHeight); 1636 1637 var tabWidth = 0; 1638 if (this.tabs_.length) { 1639 // If there are tabs then you need to check the size of each tab's content 1640 for (var i = 0, tab; tab = this.tabs_[i]; i++) { 1641 var tabSize = this.getElementSize_(tab.tab, maxWidth, maxHeight); 1642 var contentSize = this.getElementSize_(tab.content, maxWidth, maxHeight); 1643 1644 if (width < tabSize.width) { 1645 width = tabSize.width; 1646 } 1647 1648 // Add up all the tab widths because they might end up being wider than 1649 // the content 1650 tabWidth += tabSize.width; 1651 1652 if (height < tabSize.height) { 1653 height = tabSize.height; 1654 } 1655 1656 if (tabSize.height > tabHeight) { 1657 tabHeight = tabSize.height; 1658 } 1659 1660 if (width < contentSize.width) { 1661 width = contentSize.width; 1662 } 1663 1664 if (height < contentSize.height) { 1665 height = contentSize.height; 1666 } 1667 } 1668 } else { 1669 var content = /** @type {string|Node} */ (this.get('content')); 1670 if (typeof content == 'string') { 1671 content = this.htmlToDocumentFragment_(content); 1672 } 1673 if (content) { 1674 var contentSize = this.getElementSize_(content, maxWidth, maxHeight); 1675 1676 if (width < contentSize.width) { 1677 width = contentSize.width; 1678 } 1679 1680 if (height < contentSize.height) { 1681 height = contentSize.height; 1682 } 1683 } 1684 } 1685 1686 if (maxWidth) { 1687 width = Math.min(width, maxWidth); 1688 } 1689 1690 if (maxHeight) { 1691 height = Math.min(height, maxHeight); 1692 } 1693 1694 width = Math.max(width, tabWidth); 1695 1696 if (width == tabWidth) { 1697 width = width + 2 * padding; 1698 } 1699 1700 arrowSize = arrowSize * 2; 1701 width = Math.max(width, arrowSize); 1702 1703 // Maybe add this as a option so they can go bigger than the map if the user 1704 // wants 1705 if (width > mapWidth) { 1706 width = mapWidth; 1707 } 1708 1709 if (height > mapHeight) { 1710 height = mapHeight - tabHeight; 1711 } 1712 1713 if (this.tabsContainer_) { 1714 this.tabHeight_ = tabHeight; 1715 this.tabsContainer_.style['width'] = this.px(tabWidth); 1716 } 1717 1718 this.contentContainer_.style['width'] = this.px(width); 1719 this.contentContainer_.style['height'] = this.px(height); 1720}; 1721 1722 1723/**
1724 * Get the height of the anchor 1725 * 1726 * This function is a hack for now and doesn't really work that good, need to 1727 * wait for pixelBounds to be correctly exposed. 1728 * @private 1729 * @return {number} The height of the anchor. 1730 */ 1731InfoBubble.prototype.getAnchorHeight_ = function() { 1732 var anchor = this.get('anchor'); 1733 if (anchor) { 1734 var anchorPoint = /** @type google.maps.Point */(this.get('anchorPoint')); 1735 1736 if (anchorPoint) { 1737 return -1 * anchorPoint.y; 1738 } 1739 } 1740 return 0; 1741}; 1742 1743InfoBubble.prototype.anchorPoint_changed = function() { 1744 this.draw(); 1745}; 1746InfoBubble.prototype['anchorPoint_changed'] = InfoBubble.prototype.anchorPoint_changed; 1747 1748 1749/** 1750 * Position the close button in the right spot. 1751 * @private 1752 */ 1753InfoBubble.prototype.positionCloseButton_ = function() { 1754 var br = this.getBorderRadius_(); 1755 var bw = this.getBorderWidth_(); 1756 1757 var right = 2; 1758 var top = 2; 1759 1760 if (this.tabs_.length && this.tabHeight_) { 1761 top += this.tabHeight_; 1762 } 1763 1764 top += bw; 1765 right += bw; 1766 1767 var c = this.contentContainer_; 1768 if (c && c.clientHeight < c.scrollHeight) { 1769 // If there are scrollbars then move the cross in so it is not over 1770 // scrollbar 1771 right += 15; 1772 } 1773 1774 this.close_.style['right'] = this.px(right); 1775 this.close_.style['top'] = this.px(top); 1776};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.