PageSourceSearch

https://www.fondazionehospicetn.it/wp-content/plugins/tracks2map/infoBubble.js

js fondazionehospicetn.it collected 2026-10-07 16:03:34 UTC 44,052 bytes, 1,760 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.