PageSourceSearch

https://floridaautosappraisal.com/plugins/gmaps/system/layout/js/infobubble.js

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

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.