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