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