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