PageSourceSearch

https://www.hotelelyseeprague.cz/js/infobubble.js

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

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.