PageSourceSearch

https://www.gouesnou.bzh/wp-content/plugins/creasit-plan-interactif/js/infobubble.js

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

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.