PageSourceSearch

https://ahilyabythesea.com/js/infobubble.js?ver=7.1.1

js ahilyabythesea.com collected 2026-10-06 16:57:56 UTC 45,129 bytes, 1,790 lines download raw bytes

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.