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