PageSourceSearch

https://www.labor-limbach-lehrte.de/typo3temp/assets/compressed/me…ff723ceee43ee4de37a78a3ebe5ee.1772536452.js

js labor-limbach-lehrte.de collected 2026-10-08 12:43:32 UTC 103,730 bytes, 3,157 lines download raw bytes

1
2var TRIO = TRIO || {};
3
4TRIO.LocationSlider = {
5	element: null,
6
7	create: function(elem) {
8		this.index = 0;
9		this.element = jQuery(elem);
10
11		if (this.element.length > 0) {
12			this.map = jQuery('#locationMap');
13			this.scroller = this.element.find('.laboratory-finder__scroller');
14			this.scrollUpHandle = this.element.find('a.laboratory-finder__scroll---up');
15			this.scrollDownHandle = this.element.find('a.laboratory-finder__scroll---down');
16
17			this.init();
18		}
19	},
20
21	init: function() {
22		var that = this;
23
24		this.scrollUpHandle.on('click', function() {
25			that.slide('up');
26		});
27
28		this.scrollDownHandle.on('click', function() {
29			that.slide('down');
30		});
31
32		this.setMapMarkers();
33
34		this.updateButtons();
35	},
36
37	setMapMarkers: function() {
38		var that = this;
39
40		this.scroller.find('.laboratory-finder__location-list-item').each(function(n) {
41			var location = jQuery(this);
42			var coords = location.data('coords').split(',');
43			var isActive = (n == 0) ? ' laboratory-finder__marker--active' : '';
44			var labelActive = (n == 0) ? ' style="display: block;"' : ' style="display: none;"';
45			var marker = jQuery('<span class="laboratory-finder__marker" style="left: ' + coords[0] + '%; top: ' + coords[1] + '%;"><span class="laboratory-finder__pointer' + isActive + '"></span><span class="laboratory-finder__label ' + ((isActive) ? 'laboratory-finder__label--opened' : '') + '"' + labelActive + '>' + location.data('title') + '</span></span>');
46
47			marker.on('click', function() {
48				that.scrollToLocation(n);
49			});
50
51			marker.on('mouseenter', function() {
52				if (marker.find('.laboratory-finder__label--opened').length === 0) {
53					$('.laboratory-finder__label--opened').removeClass('laboratory-finder__label--opened').fadeOut(100);
54					that.showLabel(marker);
55				}
56			});
57
58			that.map.append(marker);
59		});
60	},
61
62	showLabel: function(marker) {
63		marker.find('.laboratory-finder__label').addClass('laboratory-finder__label--opened').fadeIn(100);
64	},
65
66	slide: function(direction) {
67		var slideTo,
68			that = this,
69			maxIndex = this.scroller.find(">.laboratory-finder__location-list-item").length - 1;
70
71		switch(direction) {
72			case 'up':
73				if (this.index > 0) {
74					this.index--;
75				}
76				break;
77			case 'down':
78				if (this.index < maxIndex) {
79					this.index++;
80				}
81				break;
82		}
83
84		slideTo = -this.index * 480;
85		this.scroller.stop(true, true).animate({top: slideTo}, function() {
86			that.update();
87		});
88	},
89
90	scrollToLocation: function(index) {
91		// Only scroll if new index
92		if (index === this.index) {
93			return;
94		}
95
96		this.index = index;
97		this.scroller.stop(true, true).animate({top: -(index * 480)});
98		this.update();
99	},
100
101	updateButtons: function() {
102		var numElements = $('.laboratory-finder__location-list-item').length;
103
104		if (this.index === 0) {
105			this.scrollUpHandle.addClass('laboratory-finder__scroll---disabled');
106		} else if (this.index > 0 && this.index < numElements - 1) {
107			this.scrollUpHandle.add(this.scrollDownHandle).removeClass('laboratory-finder__scroll---disabled');
108		} else {
109			this.scrollDownHandle.addClass('laboratory-finder__scroll---disabled');
110		}
111	},
112
113	update: function() {
114		this.map.find('.laboratory-finder__label').hide();
115		this.map.find('.laboratory-finder__pointer').removeClass('laboratory-finder__marker--active');
116		jQuery(this.map.find('.laboratory-finder__pointer')[this.index]).addClass('laboratory-finder__marker--active');
117		jQuery(this.map.find('.laboratory-finder__marker')[this.index]).find('.laboratory-finder__label').fadeIn();
118
119		this.updateButtons();
120	}
121};
122
123jQuery(function($) {
124	TRIO.LocationSlider.create('#location_slider');
125});
126
127// "Alle Srechstunden" hat ein reload?  Soll das so?
128
129var TRIO = TRIO || {};
130
131TRIO.ConsultationFinder = {
132    element: null,
133
134    create: function (elem) {
135        this.index = 0;
136        this.element = jQuery(elem);
137
138        if (this.element.length > 0) {
139            this.map = jQuery('#locationMap');
140            this.scroller = this.element.find('.consultation-hour-finder__scroller');
141            this.scrollUpHandle = this.element.find('a.consultation-hour-finder__scroll---up');
142            this.scrollDownHandle = this.element.find('a.consultation-hour-finder__scroll---down');
143
144            this.init();
145        }
146
147        me = this;
148    },
149    slideCardHeight: 480,
150    detectSlideHeight: function() {
151        var me = this;
152        jQuery('.consultation-hour-finder__location-content').each(function(i, el) {
153            var height = jQuery(el).outerHeight()
154            if (me.slideCardHeight < height) {
155                me.slideCardHeight = height + 20; // 20 is some extra space
156            }
157        });
158        jQuery('#consultation-hour_finder')
159          .css('height', this.slideCardHeight + 'px');
160        jQuery('.consultation-hour-finder__location-list-item, .consultation-hour-finder__location-list')
161          .css('height', this.slideCardHeight + 'px');
162    },
163
164    init: function () {
165        var that = this;
166
167        this.detectSlideHeight();
168
169        this.scrollUpHandle.on('click', function () {
170            that.slide('up');
171        });
172
173        this.scrollDownHandle.on('click', function () {
174            that.slide('down');
175        });
176
177        this.setMapMarkers();
178
179        this.updateButtons();
180    },
181    slide: function (direction) {
182
183        var slideTo,
184          that = this,
185          maxIndex = this.scroller.find(">.consultation-hour-finder__location-list-item").length - 1;
186
187        switch (direction) {
188            case 'up':
189                if (that.index > 0) {
190                    that.index--;
191                    that.currentSlide--;
192                }
193                break;
194            case 'down':
195                if (that.index < maxIndex) {
196                    that.index++;
197                    that.currentSlide++;
198                }
199                break;
200        }
201
202        slideTo = -that.index * this.slideCardHeight;
203        this.scroller.stop(true, true).animate({top: slideTo}, function () {
204            var elementId;
205            that.update();
206            $('.consultation-hour-finder__location-list-item').not('.consultation-hour-finder__location-list-item--hidden').each(function (i) {
207                if (i === that.index) {
208                    elementId = $(this).attr('id');
209                }
210            });
211
212            $(TRIO.ConsultationMap.markers).each(function () {
213                if (this.id === elementId) {
214                    this.setIcon('/typo3conf/ext/address_map/Resources/Public/Icons/marker.png');
215                    TRIO.ConsultationMap.map.setCenter(this.position);
216                    TRIO.ConsultationMap.map.setZoom(12);
217                } else {
218                    this.setIcon('/typo3conf/ext/address_map/Resources/Public/Icons/marker_gray.png');
219                }
220            });
221        });
222    },
223
224    updateButtons: function () {
225        var numElements = $('.consultation-hour-finder__location-list-item').not('.consultation-hour-finder__location-list-item--hidden').length;
226
227        if (this.index <= 0) {
228            this.scrollUpHandle.addClass('consultation-hour-finder__scroll---disabled');
229            this.scrollDownHandle.removeClass('consultation-hour-finder__scroll---disabled');
230        }
231
232        if (this.index >= numElements - 1) {
233            this.scrollUpHandle.removeClass('consultation-hour-finder__scroll---disabled');
234            this.scrollDownHandle.addClass('consultation-hour-finder__scroll---disabled');
235        }
236
237        if (this.index > 0 && this.index < numElements - 1) {
238            this.scrollUpHandle.removeClass('consultation-hour-finder__scroll---disabled');
239            this.scrollDownHandle.removeClass('consultation-hour-finder__scroll---disabled');
240        }
241
242        if (this.index <= 0 && this.index >= numElements - 1) {
243            this.scrollUpHandle.addClass('consultation-hour-finder__scroll---disabled');
244            this.scrollDownHandle.addClass('consultation-hour-finder__scroll---disabled');
245        }
246    },
247
248    update: function () {
249        this.updateButtons();
250    },
251
252    slideTo: function (id) {
253        var that = this;
254        var ix = 0;
255        var slideTo = 0;
256
257        $('.consultation-hour-finder__location-list-item').not('.consultation-hour-finder__location-list-item--hidden').each(function (i) {
258            if (id === $(this).attr('id')) {
259                slideTo = -ix * that.slideCardHeight;
260                that.index = ix;
261            }
262            ix++;
263        });
264
265        this.scroller.stop(true, true).animate({top: slideTo}, function () {
266            that.updateButtons();
267        });
268
269    },
270
271    setMapMarkers: function () {
272        var that = this;
273
274        this.scroller.find('.consultation-hour-finder__location-list-item').each(function (n) {
275            /*var location = jQuery(this);
276             var coords = location.data('coords').split(',');
277             var isActive = (n == 0) ? ' consultation-hour-finder__marker--active' : '';
278             var labelActive = (n == 0) ? ' style="display: block;"' : ' style="display: none;"';
279             var marker = jQuery('<span class="consultation-hour-finder__marker" style="left: ' + coords[0] + '%; top: ' + coords[1] + '%;"><span class="laboratory-finder__pointer' + isActive + '"></span><span class="laboratory-finder__label ' + ((isActive) ? 'laboratory-finder__label--opened' : '') + '"' + labelActive + '>' + location.data('title') + '</span></span>');
280
281             marker.on('click', function() {
282             that.scrollToLocation(n);
283             });
284
285             marker.on('mouseenter', function() {
286             if (marker.find('.consultation-hour-finder__label--opened').length === 0) {
287             $('.consultation-hour-finder__label--opened').removeClass('consultation-hour-finder__label--opened').fadeOut(100);
288             that.showLabel(marker);
289             }
290             });
291
292             that.map.append(marker);*/
293        });
294    },
295
296    setFirstSlideAsActive: function (setInactive) {
297        var elementId;
298
299        var firstIteration = false;
300        $('.consultation-hour-finder__location-list-item').not('.consultation-hour-finder__location-list-item--hidden').each(function (i) {
301
302            if (!firstIteration) {
303                elementId = $(this).attr('id');
304                firstIteration = true;
305            }
306        });
307
308        if (setInactive) {
309            $(TRIO.ConsultationMap.markers).each(function () {
310                this.setIcon('/typo3conf/ext/address_map/Resources/Public/
310Icons/marker_gray.png');
311            });
312            return;
313        }
314
315        $(TRIO.ConsultationMap.markers).each(function () {
316            if (this.id === elementId) {
317                this.setIcon('/typo3conf/ext/address_map/Resources/Public/Icons/marker.png');
318            } else {
319                this.setIcon('/typo3conf/ext/address_map/Resources/Public/Icons/marker_gray.png');
320            }
321        });
322    },
323};
324
325var MY_MAPTYPE_ID = 'map_style';
326
327TRIO.ConsultationMap = {
328    element: null,
329    addresses: null,
330    zoomDisabled: null,
331    center: null,
332    activemarker: null,
333    address: null,
334    settings: null,
335    styles: null,
336    map: null,
337    layer: null,
338    geocoder: null,
339    last: null,
340    url: null,
341    markers: [],
342
343    filter: function (category) {
344        var that = this;
345        var latlngbounds = new google.maps.LatLngBounds();
346        var activeMarkerCount = 0;
347        var activeMarker = null;
348
349        // Watch for boundaries and force min zoom level
350        google.maps.event.addListenerOnce(this.map, 'bounds_changed', function() {
351            this.setZoom(Math.min(12, this.getZoom()));
352        });
353
354        if (category === undefined) {
355            this.addresses.each(function () {
356                var $markers = $(that.markers);
357                $markers.each(function () {
358                    latlngbounds.extend(this.position);
359                    activeMarkerCount++;
360                    activeMarker = null;
361                    this.setVisible(true);
362                });
363
364                activeMarker = $markers.first();
365                $(this).removeClass('consultation-hour-finder__location-list-item--hidden');
366            });
367        } else {
368            this.addresses.each(function () {
369                var id = $(this).attr('id');
370                var categories = $(this).data('category');
371                var cats = [];
372                if(typeof categories == 'string' && categories.includes(",")) {
373                    cats = categories.split(",");
374                }
375
376                if (category == $(this).data('category') || cats.includes("" + category +"")) {
377                    $(this).removeClass('consultation-hour-finder__location-list-item--hidden');
378                    $(that.markers).each(function () {
379                        if (this.id == id) {
380                            latlngbounds.extend(this.position);
381                            activeMarker = this;
382                            activeMarkerCount++;
383                            this.setVisible(true);
384                        }
385                    });
386                } else {
387                    $(this).addClass('consultation-hour-finder__location-list-item--hidden');
388                    $(that.markers).each(function () {
389                        if (this.id == id) {
390                            this.setVisible(false);
391                        }
392                    });
393                }
394            });
395        }
396
397        if (activeMarkerCount > 1) {
398            this.map.fitBounds(latlngbounds);
399        } else if (activeMarkerCount == 1) {
400            this.map.setZoom(12);
401            this.map.panTo(activeMarker.position);
402        }
403        TRIO.ConsultationFinder.index = 0;
404        TRIO.ConsultationFinder.update();
405        $('.consultation-hour-finder__scroller').css('top', '0px');
406
407        if (category === undefined) {
408            TRIO.ConsultationFinder.setFirstSlideAsActive(true);
409        } else {
410            TRIO.ConsultationFinder.setFirstSlideAsActive();
411        }
412
413        if (!Limbach.isMedium()) {
414            $('html, body').animate({scrollTop: $('.consultation-hour-finder__location-slider').offset().top});
415        }
416
417    },
418
419    create: function (elem) {
420        var consultationMap = this;
421        this.element = jQuery(elem);
422        this.zoomLevel = this.element.data('zoomlevel');
423        this.addresses = this.element.find('.consultation-hour-finder__location-list-item');
424        this.zoomDisabled = (this.element.data('zoom') == 0) ? true : false;
425        this.center = [50.51168, 8.920441];
426        this.activemarker = [];
427
428        var filterButtons = $('.consultation-hour-finder__filter-button');
429        var activeButtonClass = 'consultation-hour-finder__filter-button--active';
430
431        /**
432         * Show all labs on selected filter button
433         */
434        filterButtons.on('click', function () {
435            filterButtons.removeClass(activeButtonClass);
436            $(this).addClass(activeButtonClass);
437            consultationMap.filter($(this).data('uid'));
438        });
439
440        if (this.element.length > 0) {
441            this.settings = {
442                zoom: TRIO.ConsultationMap.zoomLevel,
443                scrollwheel: false,
444                center: new google.maps.LatLng(TRIO.ConsultationMap.center[0], TRIO.ConsultationMap.center[1]),
445                mapTypeId: google.maps.MapTypeId.ROADMAP,
446                mapTypeControlOptions: {
447                    mapTypeIds: [google.maps.MapTypeId.ROADMAP, MY_MAPTYPE_ID]
448                },
449                streetViewControl: false
450            };
451
452            this.styles = [
453                {
454                    stylers: [
455                        {hue: "#dddddd"},
456                        {saturation: -100}
457                    ]
458                }, {
459                    "elementType": "labels.text.fill",
460                    "stylers": [
461                        {"color": "#991355"},
462                        {"visibility": "on"}
463                    ]
464                }, {
465                    featureType: "administrative.country",
466                    elementType: "labels",
467                    stylers: [
468                        {visibility: "off"}
469                    ]
470                }, {
471                    featureType: "road.highway",
472                    stylers: [
473                        {lightness: 50}
474                    ]
475                }, {
476                    featureType: "poi",
477                    stylers: [
478                        {visibility: "off"}
479                    ]
480                }
481            ];
482
483            this.init();
484        }
485    },
486
487    init: function () {
488        this.map = new google.maps.Map(document.getElementById('locationMap'), this.settings);
489        this.setMarkerByGeocoder();
490    },
491
492    update: function () {
493        this.geocoder = null;
494
495        for (var i = 0; i < this.activemarker.length; i++) {
496            this.activemarker[i].setMap(null);
497        }
498
499        this.activemarker = [];
500        this.addresses = null;
501        this.addresses = this.element.find('.consultation-hour-finder__location-list-item');
502        this.setMarkerByGeocoder();
503    },
504
505    setMarkerByGeocoder: function () {
506        var that = this;
507        var marker;
508        var latlng_pos = [];
509
510        this.geocoder = new google.maps.Geocoder();
511        if (this.addresses.length > 0) {
512            this.addresses.each(function (i) {
513                var address = jQuery(this);
514
515                if (address.data('geodata').length > 0) {
516                    var geodata = address.data('geodata').split(',');
517
518                    that.markers.push(marker = new google.maps.Marker({
519                        position: new google.maps.LatLng(geodata[0], geodata[1]),
520                        map: TRIO.ConsultationMap.map,
521                        title: address.data('title'),
522                        id: address.attr('id'),
523                        icon: '/typo3conf/ext/address_map/Resources/Public/
523Icons/marker_gray.png'
524                    }));
525                } else {
526                    /*that.geocoder.geocode({'address': address.data('address')}, function(results, status) {
527                     if (status == google.maps.GeocoderStatus.OK) {
528                     var location = results[0].geometry.location;
529                     marker = new google.maps.Marker({
530                     position: location,
531                     map: that.map,
532                     title: address.data('title'),
533                     link: address.data('link'),
534                     icon: '/typo3conf/ext/address_map/Resources/Public/Icons/marker.png'
535                     });
536                     } else {
537                     alert('Geocode was not successful for the following reason: ' + status);
538                     }
539                     });*/
540                }
541
542                that.activemarker[i] = marker;
543                latlng_pos[i] = marker.position;
544
545                google.maps.event.addListener(marker, 'click', function () {
546                    var id = $(this).attr('id');
547                    $(that.markers).each(function () {
548                        if (this.id == id) {
549                            this.setIcon('/typo3conf/ext/address_map/Resources/Public/Icons/marker.png');
550                            TRIO.ConsultationFinder.slideTo(id);
551                            that.map.setCenter(this.position);
552                            that.map.setZoom(12);
553
554                            //var center = new google.maps.LatLng(lat, lng);
555                            //that.map.panTo(center);
556                        } else {
557                            this.setIcon('/typo3conf/ext/address_map/Resources/Public/Icons/marker_gray.png');
558                        }
559                    });
560                    //TODO: scroll to element with id this.id
561                });
562            });
563
564            if (this.addresses.length == 1) {
565                this.map.setZoom(12);
566                this.map.panTo(marker.position);
567            }
568
569            if (this.addresses.length > 1) {
570                var latlngbounds = new google.maps.LatLngBounds();
571
572                for (var j = 0; j < latlng_pos.length; j++) {
573                    latlngbounds.extend(latlng_pos[j]);
574                }
575
576                this.map.fitBounds(latlngbounds);
577            }
578        }
579
580        this.map.setOptions({
581            styles: this.styles
582        });
583
584        var mcStyles = [
585            {
586                textColor: 'white',
587                url: '/typo3conf/ext/address_map/Resources/Public/Icons/marker_gray.png',
588                height: 47,
589                width: 34,
590                anchorText: [-8, 0],
591                anchorIcon: [47, 34/2],
592                textSize: 15
593            },
594            {
595                textColor: 'white',
596                url: '/typo3conf/ext/address_map/Resources/Public/Icons/marker_gray.png',
597                height: 47,
598                width: 34,
599                anchorText: [-8, 0],
600                anchorIcon: [47, 34/2],
601                textSize: 15
602            },
603            {
604                textColor: 'white',
605                url: '/typo3conf/ext/address_map/Resources/Public/Icons/marker_gray.png',
606                height: 47,
607                width: 34,
608                anchorText: [-8, 0],
609                anchorIcon: [47, 34/2],
610                textSize: 15
611            }
612        ];
613
614        var mcOptions = {
615            gridSize: 50,
616            styles: mcStyles,
617            averageCenter: true,
618            minimumClusterSize: 2,
619            ignoreHidden: true
620        };
621        this.markerClusterer = new MarkerClusterer(this.map, this.markers, mcOptions);
622    }
623};
624
625jQuery(document).ready(function () {
626    TRIO.ConsultationFinder.create('#consultation-hour_finder');
627    TRIO.ConsultationMap.create('#consultation-hour_finder');
628});
629
630/**
631 * @name MarkerClustererPlus for Google Maps V3
632 * @version 2.1.1 [November 4, 2013]
633 * @author Gary Little
634 * @fileoverview
635 * The library creates and manages per-zoom-level clusters for large amounts of markers.
636 * <p>
637 * This is an enhanced V3 implementation of the
638 * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
639 * >V2 MarkerClusterer</a> by Xiaoxi Wu. It is based on the
640 * <a href="http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclusterer/"
641 * >V3 MarkerClusterer</a> port by Luke Mahe. MarkerClustererPlus was created by Gary Little.
642 * <p>
643 * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It
644 *  adds support for the <code>ignoreHidden</code>, <code>title</code>, <code>batchSizeIE</code>,
645 *  and <code>calculator</code> properties as well as support for four more events. It also allows
646 *  greater control over the styling of the text that appears on the cluster marker. The
647 *  documentation has been significantly improved and the overall code has been simplified and
648 *  polished. Very large numbers of markers can now be managed without causing Javascript timeout
649 *  errors on Internet Explorer. Note that the name of the <code>clusterclick</code> event has been
650 *  deprecated. The new name is <code>click</code>, so please change your application code now.
651 */
652
653/**
654 * Licensed under the Apache License, Version 2.0 (the "License");
655 * you may not use this file except in compliance with the License.
656 * You may obtain a copy of the License at
657 *
658 *     http://www.apache.org/licenses/LICENSE-2.0
659 *
660 * Unless required by applicable law or agreed to in writing, software
661 * distributed under the License is distributed on an "AS IS" BASIS,
662 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
663 * See the License for the specific language governing permissions and
664 * limitations under the License.
665 */
666
667
668/**
669 * @name ClusterIconStyle
670 * @class This class represents the object for values in the <code>styles</code> array passed
671 *  to the {@link MarkerClusterer} constructor. The element in this array that is used to
672 *  style the cluster icon is determined by calling the <code>calculator</code> function.
673 *
674 * @property {string} url The URL of the cluster icon image file. Required.
675 * @property {number} height The display height (in pixels) of the cluster icon. Required.
676 * @property {number} width The display width (in pixels) of the cluster icon. Required.
677 * @property {Array} [anchorText] The position (in pixels) from the center of the cluster icon to
678 *  where the text label is to be centered and drawn. The format is <code>[yoffset, xoffset]</code>
679 *  where <code>yoffset</code> increases as you go down from center and <code>xoffset</code>
680 *  increases to the right of center. The default is <code>[0, 0]</code>.
681 * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the
682 *  spot on the cluster icon that is to be aligned with the cluster position. The format is
683 *  <code>[yoffset, xoffset]</code> where <code>yoffset</code> increases as you go down and
684 *  <code>xoffset</code> increases to the right of the top-left corner of the icon. The default
685 *  anchor position is the center of the cluster icon.
686 * @property {string} [textColor="black"] The color of the label text shown on the
687 *  cluster icon.
688 * @property {number} [textSize=11] The size (in pixels) of the label text shown on the
689 *  cluster icon.
690 * @property {string} [textDecoration="none"] The value of the CSS <code>text-decoration</code>
691 *  property for the label text shown on the cluster icon.
692 * @property {string} [fontWeight="bold"] The value of the CSS <code>font-weight</code>
693 *  property for the label text shown on the cluster icon.
694 * @property {string} [fontStyle="normal"] The value of the CSS <code>font-style</code>
695 *  property for the label text shown on the cluster icon.
696 * @property {string} [fontFamily="Arial,sans-serif"] The value of the CSS <code>font-family</code>
697 *  property for the label text shown on the cluster icon.
698 * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image
699 *  within the image defined by <code>url</code>. The format is <code>"xpos ypos"</code>
700 *  (the same format as for the CSS <code>background-position</code> property). You must set
701 *  this property appropriately when the image defined by <code>url</code> represents a sprite
702 *  containing multiple images. Note that the position <i>must</i> be specified in px units.
703 */
704/**
705 * @name ClusterIconInfo
706 * @class This class is an object containing general information about a cluster icon. This is
707 *  the object that a <code>calculator</code> function returns.
708 *
709 * @property {string} text The text of the label to be shown on the cluster icon.
710 * @property {number} index The index plus 1 of the element in the <code>styles</code>
711 *  array to be used to style the cluster icon.
712 * @property {string} title The tooltip to display when the mouse moves over the cluster icon.
713 *  If this value is <code>undefined</code> or <code>""</code>, <code>title</code> is set to the
714 *  value of the <code>title</code> property passed to the MarkerClusterer.
715 */
716/**
717 * A cluster icon.
718 *
719 * @constructor
720 * @extends google.maps.OverlayView
721 * @param {Cluster} cluster The cluster with which the icon is to be associated.
722 * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons
723 *  to use for various cluster sizes.
724 * @private
725 */
726function ClusterIcon(cluster, styles) {
727  cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
728
729  this.cluster_ = cluster;
730  this.className_ = cluster.getMarkerClusterer().getClusterClass();
731  this.styles_ = styles;
732  this.center_ = null;
733  this.div_ = null;
734  this.sums_ = null;
735  this.visible_ = false;
736
737  this.setMap(cluster.getMap()); // Note: this causes onAdd to be called
738}
739
740
741/**
742 * Adds the icon to the DOM.
743 */
744ClusterIcon.prototype.onAdd = function () {
745  var cClusterIcon = this;
746  var cMouseDownInCluster;
747  var cDraggingMapByCluster;
748
749  this.div_ = document.createElement("div");
750  this.div_.className = this.className_;
751  if (this.visible_) {
752    this.show();
753  }
754
755  this.getPanes().overlayMouseTarget.appendChild(this.div_);
756
757  // Fix for Issue 157
758  this.boundsChangedListener_ = google.maps.event.addListener(this.getMap(), "bounds_changed", function () {
759    cDraggingMapByCluster = cMouseDownInCluster;
760  });
761
762  google.maps.event.addDomListener(this.div_, "mousedown", function () {
763    cMouseDownInCluster = true;
764    cDraggingMapByCluster = false;
765  });
766
767  google.maps.event.addDomListener(this.div_, "click", function (e) {
768    cMouseDownInCluster = false;
769    if (!cDraggingMapByCluster) {
770      var theBounds;
771      var mz;
772      var mc = cClusterIcon.cluster_.getMarkerClusterer();
773      /**
774       * This event is fired when a cluster marker is clicked.
775       * @name MarkerClusterer#click
776       * @param {Cluster} c The cluster that was clicked.
777       * @event
778       */
779      google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
780      google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name
781
782      // The default click handler follows. Disable it by setting
783      // the zoomOnClick property to false.
784      if (mc.getZoomOnClick()) {
785        // Zoom into the cluster.
786        mz = mc.getMaxZoom();
787        theBounds = cClusterIcon.cluster_.getBounds();
788        mc.getMap().fitBounds(theBounds);
789        // There is a fix for Issue 170 here:
790        setTimeout(function () {
791          mc.getMap().fitBounds(theBounds);
792          // Don't zoom beyond the max zoom level
793          if (mz !== null && (mc.getMap().getZoom() > mz)) {
794            mc.getMap().setZoom(mz + 1);
795          }
796        }, 100);
797      }
798
799      // Prevent event propagation to the map:
800      e.cancelBubble = true;
801      if (e.stopPropagation) {
802        e.stopPropagation();
803      }
804    }
805  });
806
807  google.maps.event.addDomListener(this.div_, "mouseover", function () {
808    var mc = cClusterIcon.cluster_.getMarkerClusterer();
809    /**
810     * This event is fired when the mouse moves over a cluster marker.
811     * @name MarkerClusterer#mouseover
812     * @param {Cluster} c The cluster that the mouse moved over.
813     * @event
814     */
815    google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_);
816  });
817
818  google.maps.event.addDomListener(this.div_, "mouseout", function () {
819    var mc = cClusterIcon.cluster_.getMarkerClusterer();
820    /**
821     * This event is fired when the mouse moves out of a cluster marker.
822     * @name MarkerClusterer#mouseout
823     * @param {Cluster} c The cluster that the mouse moved out of.
824     * @event
825     */
826    google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_);
827  });
828};
829
830
831/**
832 * Removes the icon from the DOM.
833 */
834ClusterIcon.prototype.onRemove = function () {
835  if (this.div_ && this.div_.parentNode) {
836    this.hide();
837    google.maps.event.removeListener(this.boundsChangedListener_);
838    google.maps.event.clearInstanceListeners(this.div_);
839    this.div_.parentNode.removeChild(this.div_);
840    this.div_ = null;
841  }
842};
843
844
845/**
846 * Draws the icon.
847 */
848ClusterIcon.prototype.draw = function () {
849  if (this.visible_) {
850    var pos = this.getPosFromLatLng_(this.center_);
851    this.div_.style.top = pos.y + "px";
852    this.div_.style.left = pos.x + "px";
853  }
854};
855
856
857/**
858 * Hides the icon.
859 */
860ClusterIcon.prototype.hide = function () {
861  if (this.div_) {
862    this.div_.style.display = "none";
863  }
864  this.visible_ = false;
vendor: 7,053 bytes, lines 865-1101
865};
866
867
868/**
869 * Positions and shows the icon.
870 */
871ClusterIcon.prototype.show = function () {
872  if (this.div_) {
873    var img = "";
874    // NOTE: values must be specified in px units
875    var bp = this.backgroundPosition_.split(" ");
876    var spriteH = parseInt(bp[0].trim(), 10);
877    var spriteV = parseInt(bp[1].trim(), 10);
878    var pos = this.getPosFromLatLng_(this.center_);
879    this.div_.style.cssText = this.createCss(pos);
880    img = "<img src='" + this.url_ + "' style='position: absolute; top: " + spriteV + "px; left: " + spriteH + "px; ";
881    if (!this.cluster_.getMarkerClusterer().enableRetinaIcons_) {
882      img += "clip: rect(" + (-1 * spriteV) + "px, " + ((-1 * spriteH) + this.width_) + "px, " +
883          ((-1 * spriteV) + this.height_) + "px, " + (-1 * spriteH) + "px);";
884    }
885    else {
886        img += "width: " + this.width_ + "px;" + "height: " + this.height_ + "px;";
887    }
888    img += "'>";
889    this.div_.innerHTML = img + "<div style='" +
890        "position: absolute;" +
891        "top: " + this.anchorText_[0] + "px;" +
892        "left: " + this.anchorText_[1] + "px;" +
893        "color: " + this.textColor_ + ";" +
894        "font-size: " + this.textSize_ + "px;" +
895        "font-family: " + this.fontFamily_ + ";" +
896        "font-weight: " + this.fontWeight_ + ";" +
897        "font-style: " + this.fontStyle_ + ";" +
898        "text-decoration: " + this.textDecoration_ + ";" +
899        "text-align: center;" +
900        "width: " + this.width_ + "px;" +
901        "line-height:" + this.height_ + "px;" +
902        "'>" + (this.cluster_.hideLabel_ ? ' ' : this.sums_.text) + "</div>";
903    if (typeof this.sums_.title === "undefined" || this.sums_.title === "") {
904      this.div_.title = this.cluster_.getMarkerClusterer().getTitle();
905    } else {
906      this.div_.title = this.sums_.title;
907    }
908    this.div_.style.display = "";
909  }
910  this.visible_ = true;
911};
912
913
914/**
915 * Sets the icon styles to the appropriate element in the styles array.
916 *
917 * @param {ClusterIconInfo} sums The icon label text and styles index.
918 */
919ClusterIcon.prototype.useStyle = function (sums) {
920  this.sums_ = sums;
921  var index = Math.max(0, sums.index - 1);
922  index = Math.min(this.styles_.length - 1, index);
923  var style = this.styles_[index];
924  this.url_ = style.url;
925  this.height_ = style.height;
926  this.width_ = style.width;
927  this.anchorText_ = style.anchorText || [0, 0];
928  this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)];
929  this.textColor_ = style.textColor || "black";
930  this.textSize_ = style.textSize || 11;
931  this.textDecoration_ = style.textDecoration || "none";
932  this.fontWeight_ = style.fontWeight || "bold";
933  this.fontStyle_ = style.fontStyle || "normal";
934  this.fontFamily_ = style.fontFamily || "Arial,sans-serif";
935  this.backgroundPosition_ = style.backgroundPosition || "0 0";
936};
937
938
939/**
940 * Sets the position at which to center the icon.
941 *
942 * @param {google.maps.LatLng} center The latlng to set as the center.
943 */
944ClusterIcon.prototype.setCenter = function (center) {
945  this.center_ = center;
946};
947
948
949/**
950 * Creates the cssText style parameter based on the position of the icon.
951 *
952 * @param {google.maps.Point} pos The position of the icon.
953 * @return {string} The CSS style text.
954 */
955ClusterIcon.prototype.createCss = function (pos) {
956  var style = [];
957  style.push("cursor: pointer;");
958  style.push("position: absolute; top: " + pos.y + "px; left: " + pos.x + "px;");
959  style.push("width: " + this.width_ + "px; height: " + this.height_ + "px;");
960  return style.join("");
961};
962
963
964/**
965 * Returns the position at which to place the DIV depending on the latlng.
966 *
967 * @param {google.maps.LatLng} latlng The position in latlng.
968 * @return {google.maps.Point} The position in pixels.
969 */
970ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) {
971  var pos = this.getProjection().fromLatLngToDivPixel(latlng);
972  pos.x -= this.anchorIcon_[1];
973  pos.y -= this.anchorIcon_[0];
974  pos.x = parseInt(pos.x, 10);
975  pos.y = parseInt(pos.y, 10);
976  return pos;
977};
978
979
980/**
981 * Creates a single cluster that manages a group of proximate markers.
982 *  Used internally, do not call this constructor directly.
983 * @constructor
984 * @param {MarkerClusterer} mc The <code>MarkerClusterer</code> object with which this
985 *  cluster is associated.
986 */
987function Cluster(mc) {
988  this.markerClusterer_ = mc;
989  this.map_ = mc.getMap();
990  this.gridSize_ = mc.getGridSize();
991  this.minClusterSize_ = mc.getMinimumClusterSize();
992  this.averageCenter_ = mc.getAverageCenter();
993  this.hideLabel_ = mc.getHideLabel();
994  this.markers_ = [];
995  this.center_ = null;
996  this.bounds_ = null;
997  this.clusterIcon_ = new ClusterIcon(this, mc.getStyles());
998}
999
1000
1001/**
1002 * Returns the number of markers managed by the cluster. You can call this from
1003 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
1004 * for the <code>MarkerClusterer</code> object.
1005 *
1006 * @return {number} The number of markers in the cluster.
1007 */
1008Cluster.prototype.getSize = function () {
1009  return this.markers_.length;
1010};
1011
1012
1013/**
1014 * Returns the array of markers managed by the cluster. You can call this from
1015 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
1016 * for the <code>MarkerClusterer</code> object.
1017 *
1018 * @return {Array} The array of markers in the cluster.
1019 */
1020Cluster.prototype.getMarkers = function () {
1021  return this.markers_;
1022};
1023
1024
1025/**
1026 * Returns the center of the cluster. You can call this from
1027 * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
1028 * for the <code>MarkerClusterer</code> object.
1029 *
1030 * @return {google.maps.LatLng} The center of the cluster.
1031 */
1032Cluster.prototype.getCenter = function () {
1033  return this.center_;
1034};
1035
1036
1037/**
1038 * Returns the map with which the cluster is associated.
1039 *
1040 * @return {google.maps.Map} The map.
1041 * @ignore
1042 */
1043Cluster.prototype.getMap = function () {
1044  return this.map_;
1045};
1046
1047
1048/**
1049 * Returns the <code>MarkerClusterer</code> object with which the cluster is associated.
1050 *
1051 * @return {MarkerClusterer} The associated marker clusterer.
1052 * @ignore
1053 */
1054Cluster.prototype.getMarkerClusterer = function () {
1055  return this.markerClusterer_;
1056};
1057
1058
1059/**
1060 * Returns the bounds of the cluster.
1061 *
1062 * @return {google.maps.LatLngBounds} the cluster bounds.
1063 * @ignore
1064 */
1065Cluster.prototype.getBounds = function () {
1066  var i;
1067  var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
1068  var markers = this.getMarkers();
1069  for (i = 0; i < markers.length; i++) {
1070    bounds.extend(markers[i].getPosition());
1071  }
1072  return bounds;
1073};
1074
1075
1076/**
1077 * Removes the cluster from the map.
1078 *
1079 * @ignore
1080 */
1081Cluster.prototype.remove = function () {
1082  this.clusterIcon_.setMap(null);
1083  this.markers_ = [];
1084  delete this.markers_;
1085};
1086
1087
1088/**
1089 * Adds a marker to the cluster.
1090 *
1091 * @param {google.maps.Marker} marker The marker to be added.
1092 * @return {boolean} True if the marker was added.
1093 * @ignore
1094 */
1095Cluster.prototype.addMarker = function (marker) {
1096  var i;
1097  var mCount;
1098  var mz;
1099
1100  if (this.isMarkerAlreadyAdded_(marker)) {
1101    return false;
1102  }
1103
1104  if (!this.center_) {
1105    this.center_ = marker.getPosition();
1106    this.calculateBounds_();
1107  } else {
1108    if (this.averageCenter_) {
1109      var l = this.markers_.length + 1;
1110      var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l;
1111      var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l;
1112      this.center_ = new google.maps.LatLng(lat, lng);
1113      this.calculateBounds_();
1114    }
1115  }
1116
1117  marker.isAdded = true;
1118  this.markers_.push(marker);
1119
1120  mCount = this.markers_.length;
1121  mz = this.markerClusterer_.getMaxZoom();
1122  if (mz !== null && this.map_.getZoom() > mz) {
1123    // Zoomed in past max zoom, so show the marker.
1124    if (marker.getMap() !== this.map_) {
1125      marker.setMap(this.map_);
1126    }
1127  } else if (mCount < this.minClusterSize_) {
1128    // Min cluster size not reached so show the marker.
1129    if (marker.getMap() !== this.map_) {
1130      marker.setMap(this.map_);
1131    }
1132  } else if (mCount === this.minClusterSize_) {
1133    // Hide the markers that were showing.
1134    for (i = 0; i < mCount; i++) {
1135      this.markers_[i].setMap(null);
1136    }
1137  } else {
1138    marker.setMap(null);
1139  }
1140
1141  return true;
1142};
1143
1144
1145/**
1146 * Determines if a marker lies within the cluster's bounds.
1147 *
1148 * @param {google.maps.Marker} marker The marker to check.
1149 * @return {boolean} True if the marker lies in the bounds.
1150 * @ignore
1151 */
1152Cluster.prototype.isMarkerInClusterBounds = function (marker) {
1153  return this.bounds_.contains(marker.getPosition());
1154};
1155
1156
1157/**
1158 * Calculates the extended bounds of the cluster with the grid.
1159 */
1160Cluster.prototype.calculateBounds_ = function () {
1161  var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
1162  this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
1163};
1164
1165
1166/**
1167 * Updates the cluster icon.
1168 */
1169Cluster.prototype.updateIcon_ = function () {
1170  var mCount = this.markers_.length;
1171  var mz = this.markerClusterer_.getMaxZoom();
1172
1173  if (mz !== null && this.map_.getZoom() > mz) {
1174    this.clusterIcon_.hide();
1175    return;
1176  }
1177
1178  if (mCount < this.minClusterSize_) {
1179    // Min cluster size not yet reached.
1180    this.clusterIcon_.hide();
1181    return;
1182  }
1183
1184  var numStyles = this.markerClusterer_.getStyles().length;
1185  var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
1186  this.clusterIcon_.setCenter(this.center_);
1187  this.clusterIcon_.useStyle(sums);
1188  this.clusterIcon_.show();
1189};
1190
1191
1192/**
1193 * Determines if a marker has already been added to the cluster.
1194 *
1195 * @param {google.maps.Marker} marker The marker to check.
1196 * @return {boolean} True if the marker has already been added.
1197 */
1198Cluster.prototype.isMarkerAlreadyAdded_ = function (marker) {
1199  for (var i = 0, n = this.markers_.length; i < n; i++) {
1200    if (marker === this.markers_[i]) {
1201      return true;
1202    }
1203  }
1204  return false;
1205};
1206
1207
1208/**
1209 * @name MarkerClustererOptions
1210 * @class This class represents the optional parameter passed to
1211 *  the {@link MarkerClusterer} constructor.
1212 * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square.
1213 * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or
1214 *  <code>null</code> if clustering is to be enabled at all zoom levels.
1215 * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is
1216 *  clicked. You may want to set this to <code>false</code> if you have installed a handler
1217 *  for the <code>click</code> event and it deals with zooming on its own.
1218 * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be
1219 *  the average position of all markers in the cluster. If set to <code>false</code>, the
1220 *  cluster marker is positioned at the location of the first marker added to the cluster.
1221 * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster
1222 *  before the markers are hidden and a cluster marker appears.
1223 * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You
1224 *  may want to set this to <code>true</code> to ensure that hidden markers are not included
1225 *  in the marker count that appears on a cluster marker (this count is the value of the
1226 *  <code>text</code> property of the result returned by the default <code>calculator</code>).
1227 *  If set to <code>true</code> and you change the visibility of a marker being clustered, be
1228 *  sure to also call <code>MarkerClusterer.repaint()</code>.
1229 * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster
1230 *  marker. (Alternatively, you can use a custom <code>calculator</code> function to specify a
1231 *  different tooltip for each cluster marker.)
1232 * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine
1233 *  the text to be displayed on a cluster marker and the index indicating which style to use
1234 *  for the cluster marker. The input parameters for the function are (1) the array of markers
1235 *  represented by a cluster marker and (2) the number of cluster icon styles. It returns a
1236 *  {@link ClusterIconInfo} object. The default <code>calculator</code> returns a
1237 *  <code>text</code> property which is the number of markers in the cluster and an
1238 *  <code>index</code> property which is one higher than the lowest integer such that
1239 *  <code>10^i</code> exceeds the number of markers in the cluster, or the size of the styles
1240 *  array, whichever is less. The <code>styles</code> array element used has an index of
1241 *  <code>index</code> minus 1. For example, the default <code>calculator</code> returns a
1242 *  <code>text</code> value of <code>"125"</code> and an <code>index</code> of <code>3</code>
1243 *  for a cluster icon representing 125 markers so the element used in the <code>styles</code>
1244 *  array is <code>2</code>. A <code>calculator</code> may also return a <code>title</code>
1245 *  property that contains the text of the tooltip to be used for the cluster marker. If
1246 *   <code>title</code> is not defined, the tooltip is set to the value of the <code>title</code>
1247 *   property for the MarkerClusterer.
1248 * @property {string} [clusterClass="cluster"] The name of the CSS class defining general styles
1249 *  for the cluster markers. Use this class to define CSS styles that are not set up by the code
1250 *  that processes the <code>styles</code> array.
1251 * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles
1252 *  of the cluster markers to be used. The element to be used to style a given cluster marker
1253 *  is determined by the function defined by the <code>calculator</code> property.
1254 *  The default is an array of {@link ClusterIconStyle} elements whose properties are derived
1255 *  from the values for <code>imagePath</code>, <code>imageExtension</code>, and
1256 *  <code>imageSizes</code>.
1257 * @property {boolean} [enableRetinaIcons=false] Whether to allow the use of cluster icons that
1258 * have sizes that are some multiple (typically double) of their actual display size. Icons such
1259 * as these look better when viewed on high-resolution monitors such as Apple's Retina displays.
1260 * Note: if this property is <code>true</code>, sprites cannot be used as cluster icons.
1261 * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the
1262 *  number of markers to be processed in a single batch when using a browser other than
1263 *  Internet Explorer (for Internet Explorer, use the batchSizeIE property instead).
1264 * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is
1265 *  being used, markers are processed in several batches with a small delay inserted between
1266 *  each batch in an attempt to avoid Javascript timeout errors. Set this property to the
1267 *  number of markers to be processed in a single batch; select as high a number as you can
1268 *  without causing a timeout error in the browser. This number might need to be as low as 100
1269 *  if 15,000 markers are being managed, for example.
1270 * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH]
1271 *  The full URL of the root name of the group of image files to use for cluster icons.
1272 *  The complete file name is of the form <code>imagePath</code>n.<code>imageExtension</code>
1273 *  where n is the image file number (1, 2, etc.).
1274 * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION]
1275 *  The extension name for the cluster icon image files (e.g., <code>"png"</code> or
1276 *  <code>"jpg"</code>).
1277 * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES]
1278 *  An array of numbers containing the widths of the group of
1279 *  <code>imagePath</code>n.<code>imageExtension</code> image files.
1280 *  (The images are assumed to be square.)
1281 */
1282/**
1283 * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}.
1284 * @constructor
1285 * @extends google.maps.OverlayView
1286 * @param {google.maps.Map} map The Google map to attach to.
1287 * @param {Array.<google.maps.Marker>} [opt_markers] The markers to be added to the cluster.
1288 * @param {MarkerClustererOptions} [opt_options] The optional parameters.
1289 */
1290function MarkerClusterer(map, opt_markers, opt_options) {
1291  // MarkerClusterer implements google.maps.OverlayView interface. We use the
1292  // extend function to extend MarkerClusterer with google.maps.OverlayView
1293  // because it might not always be available when the code is defined so we
1294  // look for it at the last possible moment. If it doesn't exist now then
1295  // there is no point going ahead :)
1296  this.extend(MarkerClusterer, google.maps.OverlayView);
1297
1298  opt_markers = opt_markers || [];
1299  opt_options = opt_options || {};
1300
1301  this.markers_ = [];
1302  this.clusters_ = [];
1303  this.listeners_ = [];
1304  this.activeMap_ = null;
1305  this.ready_ = false;
1306
1307  this.gridSize_ = opt_options.gridSize || 60;
1308  this.minClusterSize_ = opt_options.minimumClusterSize || 2;
1309  this.maxZoom_ = opt_options.maxZoom || null;
1310  this.styles_ = opt_options.styles || [];
1311  this.title_ = opt_options.title || "";
1312  this.zoomOnClick_ = true;
1313  if (opt_options.zoomOnClick !== undefined) {
1314    this.zoomOnClick_ = opt_options.zoomOnClick;
1315  }
1316  this.averageCenter_ = false;
1317  if (opt_options.averageCenter !== undefined) {
1318    this.averageCenter_ = opt_options.averageCenter;
1319  }
1320  this.ignoreHidden_ = false;
1321  if (opt_options.ignoreHidden !== undefined) {
1322    this.ignoreHidden_ = opt_options.ignoreHidden;
1323  }
1324  this.enableRetinaIcons_ = false;
1325  if (opt_options.enableRetinaIcons !== undefined) {
1326    this.enableRetinaIcons_ = opt_options.enableRetinaIcons;
1327  }
1328  this.hideLabel_ = false;
vendor: 11,610 bytes, lines 1329-1796
1329  if (opt_options.hideLabel !== undefined) {
1330    this.hideLabel_ = opt_options.hideLabel;
1331  }
1332  this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH;
1333  this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION;
1334  this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES;
1335  this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR;
1336  this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE;
1337  this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE;
1338  this.clusterClass_ = opt_options.clusterClass || "cluster";
1339
1340  if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) {
1341    // Try to avoid IE timeout when processing a huge number of markers:
1342    this.batchSize_ = this.batchSizeIE_;
1343  }
1344
1345  this.setupStyles_();
1346
1347  this.addMarkers(opt_markers, true);
1348  this.setMap(map); // Note: this causes onAdd to be called
1349}
1350
1351
1352/**
1353 * Implementation of the onAdd interface method.
1354 * @ignore
1355 */
1356MarkerClusterer.prototype.onAdd = function () {
1357  var cMarkerClusterer = this;
1358
1359  this.activeMap_ = this.getMap();
1360  this.ready_ = true;
1361
1362  this.repaint();
1363
1364  // Add the map event listeners
1365  this.listeners_ = [
1366    google.maps.event.addListener(this.getMap(), "zoom_changed", function () {
1367      cMarkerClusterer.resetViewport_(false);
1368      // Workaround for this Google bug: when map is at level 0 and "-" of
1369      // zoom slider is clicked, a "zoom_changed" event is fired even though
1370      // the map doesn't zoom out any further. In this situation, no "idle"
1371      // event is triggered so the cluster markers that have been removed
1372      // do not get redrawn. Same goes for a zoom in at maxZoom.
1373      if (this.getZoom() === (this.get("minZoom") || 0) || this.getZoom() === this.get("maxZoom")) {
1374        google.maps.event.trigger(this, "idle");
1375      }
1376    }),
1377    google.maps.event.addListener(this.getMap(), "idle", function () {
1378      cMarkerClusterer.redraw_();
1379    })
1380  ];
1381};
1382
1383
1384/**
1385 * Implementation of the onRemove interface method.
1386 * Removes map event listeners and all cluster icons from the DOM.
1387 * All managed markers are also put back on the map.
1388 * @ignore
1389 */
1390MarkerClusterer.prototype.onRemove = function () {
1391  var i;
1392
1393  // Put all the managed markers back on the map:
1394  for (i = 0; i < this.markers_.length; i++) {
1395    if (this.markers_[i].getMap() !== this.activeMap_) {
1396      this.markers_[i].setMap(this.activeMap_);
1397    }
1398  }
1399
1400  // Remove all clusters:
1401  for (i = 0; i < this.clusters_.length; i++) {
1402    this.clusters_[i].remove();
1403  }
1404  this.clusters_ = [];
1405
1406  // Remove map event listeners:
1407  for (i = 0; i < this.listeners_.length; i++) {
1408    google.maps.event.removeListener(this.listeners_[i]);
1409  }
1410  this.listeners_ = [];
1411
1412  this.activeMap_ = null;
1413  this.ready_ = false;
1414};
1415
1416
1417/**
1418 * Implementation of the draw interface method.
1419 * @ignore
1420 */
1421MarkerClusterer.prototype.draw = function () {};
1422
1423
1424/**
1425 * Sets up the styles object.
1426 */
1427MarkerClusterer.prototype.setupStyles_ = function () {
1428  var i, size;
1429  if (this.styles_.length > 0) {
1430    return;
1431  }
1432
1433  for (i = 0; i < this.imageSizes_.length; i++) {
1434    size = this.imageSizes_[i];
1435    this.styles_.push({
1436      url: this.imagePath_ + (i + 1) + "." + this.imageExtension_,
1437      height: size,
1438      width: size
1439    });
1440  }
1441};
1442
1443
1444/**
1445 *  Fits the map to the bounds of the markers managed by the clusterer.
1446 */
1447MarkerClusterer.prototype.fitMapToMarkers = function () {
1448  var i;
1449  var markers = this.getMarkers();
1450  var bounds = new google.maps.LatLngBounds();
1451  for (i = 0; i < markers.length; i++) {
1452    bounds.extend(markers[i].getPosition());
1453  }
1454
1455  this.getMap().fitBounds(bounds);
1456};
1457
1458
1459/**
1460 * Returns the value of the <code>gridSize</code> property.
1461 *
1462 * @return {number} The grid size.
1463 */
1464MarkerClusterer.prototype.getGridSize = function () {
1465  return this.gridSize_;
1466};
1467
1468
1469/**
1470 * Sets the value of the <code>gridSize</code> property.
1471 *
1472 * @param {number} gridSize The grid size.
1473 */
1474MarkerClusterer.prototype.setGridSize = function (gridSize) {
1475  this.gridSize_ = gridSize;
1476};
1477
1478
1479/**
1480 * Returns the value of the <code>minimumClusterSize</code> property.
1481 *
1482 * @return {number} The minimum cluster size.
1483 */
1484MarkerClusterer.prototype.getMinimumClusterSize = function () {
1485  return this.minClusterSize_;
1486};
1487
1488/**
1489 * Sets the value of the <code>minimumClusterSize</code> property.
1490 *
1491 * @param {number} minimumClusterSize The minimum cluster size.
1492 */
1493MarkerClusterer.prototype.setMinimumClusterSize = function (minimumClusterSize) {
1494  this.minClusterSize_ = minimumClusterSize;
1495};
1496
1497
1498/**
1499 *  Returns the value of the <code>maxZoom</code> property.
1500 *
1501 *  @return {number} The maximum zoom level.
1502 */
1503MarkerClusterer.prototype.getMaxZoom = function () {
1504  return this.maxZoom_;
1505};
1506
1507
1508/**
1509 *  Sets the value of the <code>maxZoom</code> property.
1510 *
1511 *  @param {number} maxZoom The maximum zoom level.
1512 */
1513MarkerClusterer.prototype.setMaxZoom = function (maxZoom) {
1514  this.maxZoom_ = maxZoom;
1515};
1516
1517
1518/**
1519 *  Returns the value of the <code>styles</code> property.
1520 *
1521 *  @return {Array} The array of styles defining the cluster markers to be used.
1522 */
1523MarkerClusterer.prototype.getStyles = function () {
1524  return this.styles_;
1525};
1526
1527
1528/**
1529 *  Sets the value of the <code>styles</code> property.
1530 *
1531 *  @param {Array.<ClusterIconStyle>} styles The array of styles to use.
1532 */
1533MarkerClusterer.prototype.setStyles = function (styles) {
1534  this.styles_ = styles;
1535};
1536
1537
1538/**
1539 * Returns the value of the <code>title</code> property.
1540 *
1541 * @return {string} The content of the title text.
1542 */
1543MarkerClusterer.prototype.getTitle = function () {
1544  return this.title_;
1545};
1546
1547
1548/**
1549 *  Sets the value of the <code>title</code> property.
1550 *
1551 *  @param {string} title The value of the title property.
1552 */
1553MarkerClusterer.prototype.setTitle = function (title) {
1554  this.title_ = title;
1555};
1556
1557
1558/**
1559 * Returns the value of the <code>zoomOnClick</code> property.
1560 *
1561 * @return {boolean} True if zoomOnClick property is set.
1562 */
1563MarkerClusterer.prototype.getZoomOnClick = function () {
1564  return this.zoomOnClick_;
1565};
1566
1567
1568/**
1569 *  Sets the value of the <code>zoomOnClick</code> property.
1570 *
1571 *  @param {boolean} zoomOnClick The value of the zoomOnClick property.
1572 */
1573MarkerClusterer.prototype.setZoomOnClick = function (zoomOnClick) {
1574  this.zoomOnClick_ = zoomOnClick;
1575};
1576
1577
1578/**
1579 * Returns the value of the <code>averageCenter</code> property.
1580 *
1581 * @return {boolean} True if averageCenter property is set.
1582 */
1583MarkerClusterer.prototype.getAverageCenter = function () {
1584  return this.averageCenter_;
1585};
1586
1587
1588/**
1589 *  Sets the value of the <code>averageCenter</code> property.
1590 *
1591 *  @param {boolean} averageCenter The value of the averageCenter property.
1592 */
1593MarkerClusterer.prototype.setAverageCenter = function (averageCenter) {
1594  this.averageCenter_ = averageCenter;
1595};
1596
1597
1598/**
1599 * Returns the value of the <code>ignoreHidden</code> property.
1600 *
1601 * @return {boolean} True if ignoreHidden property is set.
1602 */
1603MarkerClusterer.prototype.getIgnoreHidden = function () {
1604  return this.ignoreHidden_;
1605};
1606
1607
1608/**
1609 *  Sets the value of the <code>ignoreHidden</code> property.
1610 *
1611 *  @param {boolean} ignoreHidden The value of the ignoreHidden property.
1612 */
1613MarkerClusterer.prototype.setIgnoreHidden = function (ignoreHidden) {
1614  this.ignoreHidden_ = ignoreHidden;
1615};
1616
1617
1618/**
1619 * Returns the value of the <code>enableRetinaIcons</code> property.
1620 *
1621 * @return {boolean} True if enableRetinaIcons property is set.
1622 */
1623MarkerClusterer.prototype.getEnableRetinaIcons = function () {
1624  return this.enableRetinaIcons_;
1625};
1626
1627
1628/**
1629 *  Sets the value of the <code>enableRetinaIcons</code> property.
1630 *
1631 *  @param {boolean} enableRetinaIcons The value of the enableRetinaIcons property.
1632 */
1633MarkerClusterer.prototype.setEnableRetinaIcons = function (enableRetinaIcons) {
1634  this.enableRetinaIcons_ = enableRetinaIcons;
1635};
1636
1637
1638/**
1639 * Returns the value of the <code>imageExtension</code> property.
1640 *
1641 * @return {string} The value of the imageExtension property.
1642 */
1643MarkerClusterer.prototype.getImageExtension = function () {
1644  return this.imageExtension_;
1645};
1646
1647
1648/**
1649 *  Sets the value of the <code>imageExtension</code> property.
1650 *
1651 *  @param {string} imageExtension The value of the imageExtension property.
1652 */
1653MarkerClusterer.prototype.setImageExtension = function (imageExtension) {
1654  this.imageExtension_ = imageExtension;
1655};
1656
1657
1658/**
1659 * Returns the value of the <code>imagePath</code> property.
1660 *
1661 * @return {string} The value of the imagePath property.
1662 */
1663MarkerClusterer.prototype.getImagePath = function () {
1664  return this.imagePath_;
1665};
1666
1667
1668/**
1669 *  Sets the value of the <code>imagePath</code> property.
1670 *
1671 *  @param {string} imagePath The value of the imagePath property.
1672 */
1673MarkerClusterer.prototype.setImagePath = function (imagePath) {
1674  this.imagePath_ = imagePath;
1675};
1676
1677
1678/**
1679 * Returns the value of the <code>imageSizes</code> property.
1680 *
1681 * @return {Array} The value of the imageSizes property.
1682 */
1683MarkerClusterer.prototype.getImageSizes = function () {
1684  return this.imageSizes_;
1685};
1686
1687
1688/**
1689 *  Sets the value of the <code>imageSizes</code> property.
1690 *
1691 *  @param {Array} imageSizes The value of the imageSizes property.
1692 */
1693MarkerClusterer.prototype.setImageSizes = function (imageSizes) {
1694  this.imageSizes_ = imageSizes;
1695};
1696
1697
1698/**
1699 * Returns the value of the <code>calculator</code> property.
1700 *
1701 * @return {function} the value of the calculator property.
1702 */
1703MarkerClusterer.prototype.getCalculator = function () {
1704  return this.calculator_;
1705};
1706
1707
1708/**
1709 * Sets the value of the <code>calculator</code> property.
1710 *
1711 * @param {function(Array.<google.maps.Marker>, number)} calculator The value
1712 *  of the calculator property.
1713 */
1714MarkerClusterer.prototype.setCalculator = function (calculator) {
1715  this.calculator_ = calculator;
1716};
1717
1718/**
1719 * Sets the value of the <code>hideLabel</code> property.
1720 *
1721 *  @param {boolean} printable The value of the hideLabel property.
1722 */
1723MarkerClusterer.prototype.setHideLabel = function (hideLabel) {
1724  this.hideLabel_ = hideLabel;
1725};
1726
1727/**
1728 * Returns the value of the <code>hideLabel</code> property.
1729 *
1730 * @return {boolean} the value of the hideLabel property.
1731 */
1732MarkerClusterer.prototype.getHideLabel = function () {
1733  return this.hideLabel_;
1734};
1735
1736/**
1737 * Returns the value of the <code>batchSizeIE</code> property.
1738 *
1739 * @return {number} the value of the batchSizeIE property.
1740 */
1741MarkerClusterer.prototype.getBatchSizeIE = function () {
1742  return this.batchSizeIE_;
1743};
1744
1745
1746/**
1747 * Sets the value of the <code>batchSizeIE</code> property.
1748 *
1749 *  @param {number} batchSizeIE The value of the batchSizeIE property.
1750 */
1751MarkerClusterer.prototype.setBatchSizeIE = function (batchSizeIE) {
1752  this.batchSizeIE_ = batchSizeIE;
1753};
1754
1755
1756/**
1757 * Returns the value of the <code>clusterClass</code> property.
1758 *
1759 * @return {string} the value of the clusterClass property.
1760 */
1761MarkerClusterer.prototype.getClusterClass = function () {
1762  return this.clusterClass_;
1763};
1764
1765
1766/**
1767 * Sets the value of the <code>clusterClass</code> property.
1768 *
1769 *  @param {string} clusterClass The value of the clusterClass property.
1770 */
1771MarkerClusterer.prototype.setClusterClass = function (clusterClass) {
1772  this.clusterClass_ = clusterClass;
1773};
1774
1775
1776/**
1777 *  Returns the array of markers managed by the clusterer.
1778 *
1779 *  @return {Array} The array of markers managed by the clusterer.
1780 */
1781MarkerClusterer.prototype.getMarkers = function () {
1782  return this.markers_;
1783};
1784
1785
1786/**
1787 *  Returns the number of markers managed by the clusterer.
1788 *
1789 *  @return {number} The number of markers.
1790 */
1791MarkerClusterer.prototype.getTotalMarkers = function () {
1792  return this.markers_.length;
1793};
1794
1795
1796/**
1797 * Returns the current array of clusters formed by the clusterer.
1798 *
1799 * @return {Array} The array of clusters formed by the clusterer.
1800 */
1801MarkerClusterer.prototype.getClusters = function () {
1802  return this.clusters_;
1803};
1804
1805
1806/**
1807 * Returns the number of clusters formed by the clusterer.
1808 *
1809 * @return {number} The number of clusters formed by the clusterer.
1810 */
1811MarkerClusterer.prototype.getTotalClusters = function () {
1812  return this.clusters_.length;
1813};
1814
1815
1816/**
1817 * Adds a marker to the clusterer. The clusters are redrawn unless
1818 *  <code>opt_nodraw</code> is set to <code>true</code>.
1819 *
1820 * @param {google.maps.Marker} marker The marker to add.
1821 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1822 */
1823MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) {
1824  this.pushMarkerTo_(marker);
1825  if (!opt_nodraw) {
1826    this.redraw_();
1827  }
1828};
1829
1830
1831/**
1832 * Adds an array of markers to the clusterer. The clusters are redrawn unless
1833 *  <code>opt_nodraw</code> is set to <code>true</code>.
1834 *
1835 * @param {Array.<google.maps.Marker>} markers The markers to add.
1836 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1837 */
1838MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) {
1839  var key;
1840  for (key in markers) {
1841    if (markers.hasOwnProperty(key)) {
1842      this.pushMarkerTo_(markers[key]);
1843    }
1844  }
1845  if (!opt_nodraw) {
1846    this.redraw_();
1847  }
1848};
1849
1850
1851/**
1852 * Pushes a marker to the clusterer.
1853 *
1854 * @param {google.maps.Marker} marker The marker to add.
1855 */
1856MarkerClusterer.prototype.pushMarkerTo_ = function (marker) {
1857  // If the marker is draggable add a listener so we can update the clusters on the dragend:
1858  if (marker.getDraggable()) {
1859    var cMarkerClusterer = this;
1860    google.maps.event.addListener(marker, "dragend", function () {
1861      if (cMarkerClusterer.ready_) {
1862        this.isAdded = false;
1863        cMarkerClusterer.repaint();
1864      }
1865    });
1866  }
1867  marker.isAdded = false;
1868  this.markers_.push(marker);
1869};
1870
1871
1872/**
1873 * Removes a marker from the cluster and map.  The clusters are redrawn unless
1874 *  <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if the
1875 *  marker was removed from the clusterer.
1876 *
1877 * @param {google.maps.Marker} marker The marker to remove.
1878 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1879 * @param {boolean} [opt_noMapRemove] Set to <code>true</code> to prevent removal from map but still removing from cluster management
1880 * @return {boolean} True if the marker was removed from the clusterer.
1881 */
1882MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw, opt_noMapRemove) {
1883  var removeFromMap = true && !opt_noMapRemove;
1884  var removed = this.removeMarker_(marker,removeFromMap);
1885
1886  if (!opt_nodraw && removed) {
1887    this.repaint();
1888  }
1889
1890  return removed;
1891};
1892
1893
1894/**
1895 * Removes an array of markers from the cluster and map. The clusters are redrawn unless
1896 *  <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if markers
1897 *  were removed from the clusterer.
1898 *
1899 * @param {Array.<google.maps.Marker>} markers The markers to remove.
1900 * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
1901 * @param {boolean} [opt_noMapRemove] Set to <code>true</code> to prevent removal from map but still removing from cluster management
1902 * @return {boolean} True if markers were removed from the clusterer.
1903 */
1904MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw, opt_noMapRemove) {
1905  var i, r;
1906  var removed = false;
1907  var removeFromMap = true && !opt_noMapRemove;
1908
1909  for (i = 0; i < markers.length; i++) {
1910    r = this.removeMarker_(markers[i],removeFromMap);
1911    removed = removed || r;
1912  }
1913
1914  if (!opt_nodraw && removed) {
1915    this.repaint();
1916  }
1917
1918  return removed;
1919};
1920
1921
1922/**
1923 * Removes a marker and returns true if removed, false if not.
1924 *
1925 * @param {google.maps.Marker} marker The marker to remove
1926 * @param {boolean} removeFromMap set to <code>true</code> to explicitly remove from map as well as cluster manangement
1927 * @return {boolean} Whether the marker was removed or not
1928 */
1929MarkerClusterer.prototype.removeMarker_ = function (marker,removeFromMap) {
1930  var i;
1931  var index = -1;
1932  if (this.markers_.indexOf) {
1933    index = this.markers_.indexOf(marker);
1934  } else {
1935    for (i = 0; i < this.markers_.length; i++) {
1936      if (marker === this.markers_[i]) {
1937        index = i;
1938        break;
1939      }
1940    }
1941  }
1942
1943  if (index === -1) {
1944    // Marker is not in our list of markers, so do nothing:
1945    return false;
1946  }
1947
1948  if (removeFromMap){
1949    marker.setMap(null);
1950  }
1951
1952  this.markers_.splice(index, 1); // Remove the marker from the list of managed markers
1953  return true;
1954};
1955
1956
1957/**
1958 * Removes all clusters and markers from the map and also removes all markers
1959 *  managed by the clusterer.
1960 */
1961MarkerClusterer.prototype.clearMarkers = function () {
1962  this.resetViewport_(true);
1963  this.markers_ = [];
1964};
1965
1966
1967/**
1968 * Recalculates and redraws all the marker clusters from scratch.
1969 *  Call this after changing any properties.
1970 */
1971MarkerClusterer.prototype.repaint = function () {
1972  var oldClusters = this.clusters_.slice();
1973  this.clusters_ = [];
1974  this.resetViewport_(false);
1975  this.redraw_();
1976
1977  // Remove the old clusters.
1978  // Do it in a timeout to prevent blinking effect.
1979  setTimeout(function () {
1980    var i;
1981    for (i = 0; i < oldClusters.length; i++) {
1982      oldClusters[i].remove();
1983    }
1984  }, 0);
1985};
1986
1987
1988/**
1989 * Returns the current bounds extended by the grid size.
1990 *
1991 * @param {google.maps.LatLngBounds} bounds The bounds to extend.
1992 * @return {google.maps.LatLngBounds}
vendor: 4,297 bytes, lines 1992-2140
1992 The extended bounds.
1993 * @ignore
1994 */
1995MarkerClusterer.prototype.getExtendedBounds = function (bounds) {
1996  var projection = this.getProjection();
1997
1998  // Turn the bounds into latlng.
1999  var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
2000      bounds.getNorthEast().lng());
2001  var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
2002      bounds.getSouthWest().lng());
2003
2004  // Convert the points to pixels and the extend out by the grid size.
2005  var trPix = projection.fromLatLngToDivPixel(tr);
2006  trPix.x += this.gridSize_;
2007  trPix.y -= this.gridSize_;
2008
2009  var blPix = projection.fromLatLngToDivPixel(bl);
2010  blPix.x -= this.gridSize_;
2011  blPix.y += this.gridSize_;
2012
2013  // Convert the pixel points back to LatLng
2014  var ne = projection.fromDivPixelToLatLng(trPix);
2015  var sw = projection.fromDivPixelToLatLng(blPix);
2016
2017  // Extend the bounds to contain the new bounds.
2018  bounds.extend(ne);
2019  bounds.extend(sw);
2020
2021  return bounds;
2022};
2023
2024
2025/**
2026 * Redraws all the clusters.
2027 */
2028MarkerClusterer.prototype.redraw_ = function () {
2029  this.createClusters_(0);
2030};
2031
2032
2033/**
2034 * Removes all clusters from the map. The markers are also removed from the map
2035 *  if <code>opt_hide</code> is set to <code>true</code>.
2036 *
2037 * @param {boolean} [opt_hide] Set to <code>true</code> to also remove the markers
2038 *  from the map.
2039 */
2040MarkerClusterer.prototype.resetViewport_ = function (opt_hide) {
2041  var i, marker;
2042  // Remove all the clusters
2043  for (i = 0; i < this.clusters_.length; i++) {
2044    this.clusters_[i].remove();
2045  }
2046  this.clusters_ = [];
2047
2048  // Reset the markers to not be added and to be removed from the map.
2049  for (i = 0; i < this.markers_.length; i++) {
2050    marker = this.markers_[i];
2051    marker.isAdded = false;
2052    if (opt_hide) {
2053      marker.setMap(null);
2054    }
2055  }
2056};
2057
2058
2059/**
2060 * Calculates the distance between two latlng locations in km.
2061 *
2062 * @param {google.maps.LatLng} p1 The first lat lng point.
2063 * @param {google.maps.LatLng} p2 The second lat lng point.
2064 * @return {number} The distance between the two points in km.
2065 * @see http://www.movable-type.co.uk/scripts/latlong.html
2066*/
2067MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) {
2068  var R = 6371; // Radius of the Earth in km
2069  var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
2070  var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
2071  var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
2072    Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
2073    Math.sin(dLon / 2) * Math.sin(dLon / 2);
2074  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
2075  var d = R * c;
2076  return d;
2077};
2078
2079
2080/**
2081 * Determines if a marker is contained in a bounds.
2082 *
2083 * @param {google.maps.Marker} marker The marker to check.
2084 * @param {google.maps.LatLngBounds} bounds The bounds to check against.
2085 * @return {boolean} True if the marker is in the bounds.
2086 */
2087MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) {
2088  return bounds.contains(marker.getPosition());
2089};
2090
2091
2092/**
2093 * Adds a marker to a cluster, or creates a new cluster.
2094 *
2095 * @param {google.maps.Marker} marker The marker to add.
2096 */
2097MarkerClusterer.prototype.addToClosestCluster_ = function (marker) {
2098  var i, d, cluster, center;
2099  var distance = 40000; // Some large number
2100  var clusterToAddTo = null;
2101  for (i = 0; i < this.clusters_.length; i++) {
2102    cluster = this.clusters_[i];
2103    center = cluster.getCenter();
2104    if (center) {
2105      d = this.distanceBetweenPoints_(center, marker.getPosition());
2106      if (d < distance) {
2107        distance = d;
2108        clusterToAddTo = cluster;
2109      }
2110    }
2111  }
2112
2113  if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) {
2114    clusterToAddTo.addMarker(marker);
2115  } else {
2116    cluster = new Cluster(this);
2117    cluster.addMarker(marker);
2118    this.clusters_.push(cluster);
2119  }
2120};
2121
2122
2123/**
2124 * Creates the clusters. This is done in batches to avoid timeout errors
2125 *  in some browsers when there is a huge number of markers.
2126 *
2127 * @param {number} iFirst The index of the first marker in the batch of
2128 *  markers to be added to clusters.
2129 */
2130MarkerClusterer.prototype.createClusters_ = function (iFirst) {
2131  var i, marker;
2132  var mapBounds;
2133  var cMarkerClusterer = this;
2134  if (!this.ready_) {
2135    return;
2136  }
2137
2138  // Cancel previous batch processing if we're working on the first batch:
2139  if (iFirst === 0) {
2140    /**
2141     * This event is fired when the <code>MarkerClusterer</code> begins
2142     *  clustering markers.
2143     * @name MarkerClusterer#clusteringbegin
2144     * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
2145     * @event
2146     */
2147    google.maps.event.trigger(this, "clusteringbegin", this);
2148
2149    if (typeof this.timerRefStatic !== "undefined") {
2150      clearTimeout(this.timerRefStatic);
2151      delete this.timerRefStatic;
2152    }
2153  }
2154
2155  // Get our current map view bounds.
2156  // Create a new bounds object so we don't affect the map.
2157  //
2158  // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug:
2159  if (this.getMap().getZoom() > 3) {
2160    mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(),
2161      this.getMap().getBounds().getNorthEast());
2162  } else {
2163    mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625));
2164  }
2165  var bounds = this.getExtendedBounds(mapBounds);
2166
2167  var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length);
2168
2169  for (i = iFirst; i < iLast; i++) {
2170    marker = this.markers_[i];
2171    if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
2172      if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) {
2173        this.addToClosestCluster_(marker);
2174      }
2175    }
2176  }
2177
2178  if (iLast < this.markers_.length) {
2179    this.timerRefStatic = setTimeout(function () {
2180      cMarkerClusterer.createClusters_(iLast);
2181    }, 0);
2182  } else {
2183    delete this.timerRefStatic;
2184
2185    /**
2186     * This event is fired when the <code>MarkerClusterer</code> stops
2187     *  clustering markers.
2188     * @name MarkerClusterer#clusteringend
2189     * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
2190     * @event
2191     */
2192    google.maps.event.trigger(this, "clusteringend", this);
2193
2194    for (i = 0; i < this.clusters_.length; i++) {
2195      this.clusters_[i].updateIcon_();
2196    }
2197  }
2198};
2199
2200
2201/**
2202 * Extends an object's prototype by another's.
2203 *
2204 * @param {Object} obj1 The object to be extended.
2205 * @param {Object} obj2 The object to extend with.
2206 * @return {Object} The new extended object.
2207 * @ignore
2208 */
2209MarkerClusterer.prototype.extend = function (obj1, obj2) {
2210  return (function (object) {
2211    var property;
2212    for (property in object.prototype) {
2213      this.prototype[property] = object.prototype[property];
2214    }
2215    return this;
2216  }).apply(obj1, [obj2]);
2217};
2218
2219
2220/**
2221 * The default function for determining the label text and style
2222 * for a cluster icon.
2223 *
2224 * @param {Array.<google.maps.Marker>} markers The array of markers represented by the cluster.
2225 * @param {number} numStyles The number of marker styles available.
2226 * @return {ClusterIconInfo} The information resource for the cluster.
2227 * @constant
2228 * @ignore
2229 */
2230MarkerClusterer.CALCULATOR = function (markers, numStyles) {
2231  var index = 0;
2232  var title = "";
2233  var count = markers.length.toString();
2234
2235  var dv = count;
2236  while (dv !== 0) {
2237    dv = parseInt(dv / 10, 10);
2238    index++;
2239  }
2240
2241  index = Math.min(index, numStyles);
2242  return {
2243    text: count,
2244    index: index,
2245    title: title
2246  };
2247};
2248
2249
2250/**
2251 * The number of markers to process in one batch.
2252 *
2253 * @type {number}
2254 * @constant
2255 */
2256MarkerClusterer.BATCH_SIZE = 2000;
2257
2258
2259/**
2260 * The number of markers to process in one batch (IE only).
2261 *
2262 * @type {number}
2263 * @constant
2264 */
2265MarkerClusterer.BATCH_SIZE_IE = 500;
2266
2267
2268/**
2269 * The default root name for the marker cluster images.
2270 *
2271 * @type {string}
2272 * @constant
2273 */
2274MarkerClusterer.IMAGE_PATH = "//cdn.rawgit.com/mahnunchik/markerclustererplus/master/images/m";
2275
2276
2277/**
2278 * The default extension name for the marker cluster images.
2279 *
2280 * @type {string}
2281 * @constant
2282 */
2283MarkerClusterer.IMAGE_EXTENSION = "png";
2284
2285
2286/**
2287 * The default array of sizes for the marker cluster images.
2288 *
2289 * @type {Array.<number>}
2290 * @constant
2291 */
2292MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90];
2293
2294if (typeof String.prototype.trim !== 'function') {
2295  /**
2296   * IE hack since trim() doesn't exist in all browsers
2297   * @return {string} The string with removed whitespace
2298   */
2299  String.prototype.trim = function() {
2300    return this.replace(/^\s+|\s+$/g, '');
2301  };
2302}
2303
2304
2305var Limbach = {
2306    initDatepicker: function() {
2307      var fields = $('.datepicker-input');
2308      if ($(fields).length > 0) {
2309        $('.datepicker-input').flatpickr({
2310          dateFormat: 'd.m.Y',
2311          locale: 'de'
2312        });
2313      }
2314    },
2315
2316    isExtraSmall: function () {
2317        return $(window).width() >= '480';
2318    },
2319
2320    isSmall: function () {
2321        return $(window).width() >= '768';
2322    },
2323
2324    isMedium: function () {
2325        return $(window).width() >= '992';
2326    },
2327
2328    isLarge: function () {
2329        return $(window).width() >= '1200';
2330    },
2331
2332    initSliderTeaser: function () {
2333        if (this.isMedium() === true) {
2334            var $sliderTeaser = $('.teaser__summary-about-box'),
2335                sliderTeaserHeight = $sliderTeaser.outerHeight(),
2336                requiredHeight = 0,
2337                minHeight = 200;
2338
2339            $sliderTeaser.mouseenter(function () {
2340                requiredHeight = $(this).find('.teaser__summary-about-content').outerHeight() + sliderTeaserHeight + 35;
2341                $(this).stop().animate({
2342                    minHeight: (requiredHeight > minHeight) ? requiredHeight : minHeight + 'px'
2343                }, 200);
2344            }).mouseleave(function () {
2345                $(this).stop().animate({
2346                    minHeight: '0'
2347                }, 400);
2348            });
2349        }
2350    },
2351
2352    initContactVitae: function () {
2353        var $vitaeButton = $('.contact__vitae-button');
2354
2355        $vitaeButton.on('touch click', function () {
2356            var $this = $(this);
2357            var $btnCaption = $this.find('.contact__vitae-button-caption');
2358            var $btnCaptionText = $btnCaption.text();
2359            $this.nextAll('.contact__vitae').stop().slideToggle();
2360            $this.find('.contact__vitae-button-caption').text($btnCaptionText === '+' ? '-' : '+')
2361        });
2362    },
2363
2364    initAccordion: function () {
2365        var $accordionBox = $('.accordion__title');
2366
2367
2368
2369        $accordionBox.click(function (e) {
2370
2371            e.preventDefault();
2372            e.stopPropagation()
2373            if( $(this).prop("tagName") == 'p' || $(this).prop("tagName") == 'P' ) {
2374                $(this).next('.accordion__content').slideToggle('fast');
2375            } else {
2376                $(this).parent().find('.accordion__content').first().stop().slideToggle('fast');
2377            }
2378
2379            $(this).toggleClass('active');
2380        });
2381    },
2382
2383    initMainNaviSearchButton: function () {
2384        var $searchButton = $('.meta-navigation__search-button');
2385
2386        $searchButton.click(function () {
2387            $('.meta-navigation__search-input').fadeToggle().focus();
2388        });
2389    },
2390
2391    initThrottledResize: function () {
2392        this.initTeaserEqualHeights();
2393        this.initMobileMenu();
2394        this.initDownloadSlider();
2395        this.initMobileHeader();
2396        this.initEqualHeights();
2397        this.initSearchFilter();
2398        this.initSliderCaptionPosition();
2399    },
2400
2401    initTeaserEqualHeights: function () {
2402        var $teaserElements = $('div[class^="teaser__main--"],div[class^="teaser__main-image"]').not('div[class^="teaser__main--content"], div[class^="teaser__main--content"] > div[class^="teaser__main"], div[class^=teaser__main--square]');
2403        $teaserElements.css('min-height', '0');
2404        var maxHeight = 0;
2405
2406        function getMaxHeight() {
2407            $.each($teaserElements, function () {
2408                var height = $(this).outerHeight(true);
2409
2410                if (height > maxHeight) {
2411                    maxHeight = height;
2412                }
2413            });
2414        }
2415
2416        getMaxHeight();
2417
2418        $.each($teaserElements, function () {
2419            if(typeof $(this).closest('.container').data('context') === 'undefined') {
2420                $(this).css('min-height', maxHeight);
2421            }
2422        });
2423
2424        //$teaserElements.css('min-height', maxHeight);
2425    },
2426
2427    initEqualHeights: function () {
2428        var $autoHeightElements = $('div[data-autoheight^="true"]');
2429        var maxHeight = 0;
2430        $autoHeightElements.css('minHeight', maxHeight);
2431        $autoHeightElements.height('auto');
2432
2433        $.each($autoHeightElements, function (key, val) {
2434            if ($(this).outerHeight() > maxHeight) {
2435                maxHeight = $(this).outerHeight();
2436            }
2437
2438        });
2439
2440        //$autoHeightElements.height(maxHeight);
2441        $autoHeightElements.css('minHeight', maxHeight);
2442
2443    },
2444
2445    initContact: function () {
2446        if (this.isMedium === true) {
2447            var $contactBox = $('.contact__box'),
2448                $contactContent = $('.contact__content');
2449
2450            $.each($contactBox, function () {
2451                $(this).find('.contact__content').height($(this).innerHeight());
2452                $(this).find('.contact__image-content').height($(this).innerHeight());
2453            });
2454        }
2455    },
2456
2457    initCalculatorDescription: function () {
2458        var $showDescriptionBtn = $('.calculator__description-show');
2459
2460        $showDescriptionBtn.click(function (evt) {
2461            evt.preventDefault();
2462            var $currentText = $(this).text();
2463            $(this).text($currentText === 'Beschreibung anzeigen' ? 'Beschreibung ausblenden' : 'Beschreibung anzeigen');
2464            $(this).next('.calculator__description').stop().slideToggle();
2465        });
2466    },
2467
2468    initCalculatorShowResult: function () {
2469        var $calculateBtn = $('.calculator__element > input[type="button"]');
2470
2471        $calculateBtn.click(function (evt) {
2472            $(this).parents('.calculator__box').next('.calculator__result').stop().slideDown();
2473            if (typeof _paq != 'undefined') {
2474            	_paq.push(['trackEvent', 'Calculator', 'Calculate', document.title]);
2475            }
2476        });
2477    },
2478
2479    initCalculatorSubmit: function () {
2480        $('.calculator__input').keypress(function (evt) {
2481            if ((evt.which && evt.which == 13) || (evt.keyCode && evt.keyCode == 13)) {
2482                $('input[type=button]').click();
2483                return false;
2484            } else {
2485                return true;
2486            }
2487        });
2488    },
2489
2490    initPayType: function () {
2491        var $elv = $('#elv'),
2492            $elvForm = $('#elv-form'),
2493            $billing = $('#billing'),
2494            $billingForm = $('#billing-form');
2495
2496        $elv.click(function () {
2497            $billingForm.stop().slideUp();
2498            $elvForm.stop().slideDown();
2499        });
2500
2501        $billing.click(function () {
2502            $elvForm.stop().slideUp();
2503            $billingForm.stop().slideDown();
2504        });
2505    },
2506
2507    initOverlay: function () {
2508        var that = this;
2509
2510        $('a[class^=main-navigation__link]').mouseover(function () {
2511            if (that.isMedium()) {
2512                var submenuSelector = '.main-navigation__sub-menu';
2513
2514                if ($(this).next().is(submenuSelector)) {
2515                    $('.content__overlay').stop().fadeIn(300);
2516
2517                    // Check if we need to flip the menu
2518                    var $submenu = $(this).next();
2519                    if ($submenu.offset().left + $submenu.outerWidth(true) > $(window).innerWidth()) {
2520                        $submenu.addClass('main-navigation__sub-menu--flipped');
2521                    }
2522                } else {
2523                    // Mouseover from one menu with submenu to menu without submenu should hide overlay
2524                    $('.content__overlay').stop().fadeOut(250);
2525                }
2526            }
2527
2528        });
2529
2530        $('.main-navigation__list').mouseleave(function () {
2531            if (that.isMedium()) {
2532                $('.content__overlay').stop().fadeOut(250);
2533
2534                // Reset the flipped
2535                $('.main-navigation__link + .main-navigation__sub-menu').removeClass('main-navigation__sub-menu--flipped');
2536            }
2537        });
2538    },
2539
2540    initFooterOverlay: function () {
2541        var that = this;
2542        $('.footer-navigation__link').mouseover(function () {
2543            if (that.isMedium()) {
2544                var submenuSelectors = 'ul.level2Block';
2545                if ($(this).next().is(submenuSelectors)) {
2546                    $('.content__overlay').stop().fadeIn(300);
2547                } else {
2548                    // Mouseover from one menu with submenu to menu without submenu should hide overlay
2549                    $('.content__overlay').stop().fadeOut(250);
2550                }
2551            }
2552        });
2553
2554        $('.level2Block').mouseover(function () {
2555            if (that.isMedium()) {
2556                $('.content__overlay').stop().fadeIn(300);
2557            }
2558        });
2559
2560        $('.footer-navigation__wrapper').mouseleave(function () {
2561            if (that.isMedium()) {
2562                $('.content__overlay').stop().fadeOut(250);
2563            }
2564        });
2565    },
2566
2567    initializedMobileMenu: false,
2568    metaMobileMenu: function() {
2569      if (!this.isMedium()) {
2570        var menu = $('#meta-navigation .meta-first');
2571        $(menu).find('.has-sub').each(function(_i, el) {
2572          if ($(el).find('.cloned-first').length == 0) {
2573            var clone = $(this).children('a').clone();
2574            $(clone).find('i').remove();
2575            $(this).children('ul').prepend(clone);
2576            $(clone).wrap('<li class="cloned-first"></li>');
2577          }
2578        });
2579
2580        $(menu).on('click', '.has-sub a', function(ev) {
2581          if ($(this).parent().hasClass('has-sub')) {
2582            ev.preventDefault();
2583            if ($(this).parent().hasClass('is-open')) {
2584              $(this).parent().removeClass('is-open');
2585              $(this).parent().children('ul').slideUp();
2586            } else {
2587              $(this).parent().addClass('is-open');
2588              $(this).parent().children('ul').slideDown();
2589            }
2590          }
2591        });
2592      }
2593    },
2594    initMobileMenu: function () {
2595        var that = this;
2596
2597        if (!this.isMedium()) {
2598            // This should only be bound once
2599            if (!this.initializedMobileMenu) {
2600                that.metaMobileMenu();
2601
2602                // footer menu
2603                $('.footer-navigation__trigger').on('click touch', function (evt) {
2604                    evt.preventDefault();
2605                    $(this).next().stop().slideToggle();
2606                });
2607
2608
2609                // main menu
2610                $('.main-navigation__trigger').on('click touch', function (evt) {
2611                    evt.preventDefault();
2612                    if (!that.isMedium()) {
2613                        $('#navigation').stop(true, true).slideToggle();
2614                        $(this).toggleClass('isOpen');
2615                    }
2616                });
2617                $('.main-navigation__sub-item-caption--haschild, [class^=main-navigation__link]').on('click touch', function (evt) {
2618                    if (!that.isMedium()) {
2619                        var $el = $(this),
2620                            $next = $el.next();
2621
2622                        if ($next.length > 0 && $next.hasClass('main-navigation__sub-menu') || $next.hasClass('main-navigation__sub-item')) {
2623                            evt.preventDefault();
2624                        }
2625
2626                        $(this).next().stop(true, true).slideToggle();
2627                    }
2628                });
2629
2630                $('[class^=main-navigation__link]').each(function () {
2631                    var $el = $(this),
2632                        $next = $el.next(),
2633                        hasSubmenu = $next.length > 0 && $next.hasClass('main-navigation__sub-menu');
2634
2635                    if (hasSubmenu) {
2636                        // Find first submenu item and clone it
2637                        var $menu = $next.find('.main-navigation__sub-item-list');
2638
2639                        $menu.prepend('<li class="main-navigation__sub-item-head main-navigation__sub-item--only-mobile"><a href="' + $el.attr('href') + '" class="main-navigation__sub-item-caption">Übersicht ' + $el.text() + '</a></li>');
2640
2641                        $menu.find('.main-navigation__sub-item-head > a').each(function () {
2642                            var $item = $(this),
2643                                $next = $item.next(),
2644                                hasSubMenuItem = $next.length > 0 && $next.hasClass('main-navigation__sub-item');
2645
2646                            if (hasSubMenuItem) {
2647                                $next.prepend('<li class="main-navigation__sub-item-element main-navigation__sub-item--only-mobile"><a class="main-navigation__sub-item-link" href="' + $item.attr('href') + '">
2647Übersicht ' + $item.text() + '</a></li>');
2648                            }
2649                        });
2650                    }
2651                });
2652
2653                this.initializedMobileMenu = true;
2654            }
2655        } else {
2656            $('.main-navigation__items').show();
2657        }
2658    },
2659
2660    initTriggerFirstLink: function () {
2661        $("[data-trigger-first-link]").click(function (e) {
2662            // Prevent bubbling
2663            if (!$(e.target).is('a')) {
2664                $(this).find('a').eq(0)[0].click();
2665            }
2666        });
2667    },
2668
2669    initDownloadSlider: function () {
2670        var $downloadSlider = $('.download__slider');
2671        if ($downloadSlider.length) {
2672            $downloadSlider.slick({
2673                centerMode: true,
2674                centerPadding: '120px',
2675                slidesToShow: 3,
2676                swipeToSlide: true,
2677                responsive: [
2678                    {
2679                        breakpoint: 768,
2680                        settings: {
2681                            arrows: false,
2682                            centerMode: true,
2683                            centerPadding: '40px',
2684                            slidesToShow: 2
2685                        }
2686                    },
2687                    {
2688                        breakpoint: 480,
2689                        settings: {
2690                            arrows: false,
2691                            centerMode: true,
2692                            centerPadding: '40px',
2693                            slidesToShow: 1
2694                        }
2695                    }
2696                ]
2697            });
2698        }
2699    },
2700
2701    initToTop: function () {
2702        $(window).scroll(function () {
2703            if ($(this).scrollTop() > 100) {
2704                $('#back-top').stop().fadeIn();
2705            } else {
2706                $('#back-top').stop().fadeOut();
2707            }
2708        });
2709
2710        // scroll body to 0px on click
2711        $('#back-top').click(function () {
2712            $('body,html').animate({
2713                scrollTop: 0
2714            }, 800);
2715            return false;
2716        });
2717    },
2718
2719    scroll: function (e) {
2720        /*var $footer = $(".footer-content__background--shifted"),
2721         $headline = $footer.find('.footer-content__headline').first(),
2722         headlinePosition = $headline.offset(),
2723         scrollTop = $(window).scrollTop(),
2724         $toTop = $('#back-top'),
2725         attr = $toTop.attr('data-old-bottom-value');
2726
2727         if ((scrollTop + $(window).height()) >= headlinePosition.top + $headline.outerHeight(true)) {
2728         if (attr === undefined) {
2729         $toTop.attr('data-old-bottom-value', $toTop.css('bottom'));
2730         }
2731
2732         $toTop.css({
2733         transition: 'none',
2734         bottom: (scrollTop + $(window).height()) - headlinePosition.top - ($headline.outerHeight(true) / 2)
2735         });
2736         } else {
2737         if (attr !== undefined) {
2738         $toTop.css({
2739         bottom: attr
2740         });
2741         }
2742         }*/
2743    },
2744
2745    initQuoteHeight: function () {
2746        var $quote = $('.quote__text');
2747        if ($quote.length) {
2748            $quote.closest($('[class*=background__]').css('minHeight', 0));
2749        }
2750    },
2751
2752    initMobileHeader: function () {
2753        var $heroUnitContainer = $('.hero-unit .container-fluid').first();
2754        if (typeof $heroUnitContainer.css('backgroundImage') !== 'undefined' && parseInt($heroUnitContainer.css('minHeight')) > 0) {
2755            if (!this.isMedium()) {
2756                $heroUnitContainer.after($('.hero-unit .container-fluid .hero__content').first());
2757            } else {
2758                $heroUnitContainer.append($heroUnitContainer.next('.hero__content'));
2759            }
2760        }
2761    },
2762
2763    initDifferentBillingAddress: function () {
2764        var $billingAddressCheckbox = $('#billingAddressLabel'),
2765            $eventBilling = $('.event__booking-billing');
2766
2767        if ($billingAddressCheckbox.length) {
2768            if ($billingAddressCheckbox.prev().is(':checked')) {
2769                //$eventBilling.show();
2770            }
2771
2772            $billingAddressCheckbox.on('touch click', function () {
2773                //$eventBilling.stop().slideToggle();
2774            });
2775        }
2776    },
2777
2778    initSearchFilter: function () {
2779        var $searchFilterLink = $('.search__result-show-filter'),
2780            $searchItemList = $('.search__result-filter-items');
2781
2782        if ($searchItemList.length) {
2783            $searchFilterLink.on('click touch', function (evt) {
2784                evt.preventDefault();
2785                $(this).find('i').toggleClass('fa-angle-down fa-angle-up');
2786                $searchItemList.stop().slideToggle();
2787            });
2788        }
2789    },
2790
2791    initSwiperSlider: function () {
2792        var mySwiper = new Swiper('.swiper-container', {
2793            loop: true,
2794            autoplay: 7000,
2795            effect: 'fade',
2796            speed: 1250,
2797            paginationClickable: true,
2798
2799            pagination: '.swiper-pagination'
2800        });
2801    },
2802
2803    initSliderCaptionPosition: function () {
2804        var $sliderHeadCaption = $('.slider__head-caption-content');
2805        var $sliderBodytextCaption = $('.slider__bodytext-caption-content');
2806        if ($sliderHeadCaption.length) {
2807            if (this.isMedium()) {
2808                var windowWidth = $(window).width(),
2809                    containerWidth = $('.container').first().width(),
2810                    captionDistance = (windowWidth - containerWidth) / 2;
2811                $sliderHeadCaption.css('paddingRight', captionDistance + 'px');
2812                $sliderBodytextCaption.css('paddingRight', captionDistance + 'px');
2813            } else {
2814                $sliderHeadCaption.css('paddingRight', 0);
2815                $sliderBodytextCaption.css('paddingRight', 0);
2816            }
2817        }
2818    },
2819
2820    initSearchModalBox: function () {
2821        var $body = $('body'),
2822            $fields = $('input.search__input, input.meta-navigation__search-input'),
2823            $modal = $('<div><div id="modal_box" class="modal-overlay"><div class="modal-box"><div class="modal-box__text">Bitte verwenden Sie als Suchbegriff Worte mit einer Länge von mindestens 4 Zeichen.</div><div class="modal-box__button" id="modal-close"><span class="button__primary">Schlie&szlig;en</span></div></div></div></div>');
2824
2825        if ($fields.length) {
2826
2827            $fields.each(function () {
2828                var _field = $(this);
2829                var _form = _field.closest('form');
2830
2831                _form.submit(function (event) {
2832                    if (_field.val().length < 4) {
2833                        _field.attr('autocomplete', 'off');
2834                        event.preventDefault();
2835                        notifyUser(_field);
2836                    }
2837                });
2838            });
2839
2840        }
2841
2842        function notifyUser(dependingField) {
2843
2844            $body.append($modal.html());
2845
2846            $('#modal-close').bind('click', function () {
2847                removeBox(dependingField);
2848            });
2849
2850            dependingField.focus();
2851        }
2852
2853        function removeBox(dependingField) {
2854            $('.modal-overlay').fadeOut(200, function () {
2855                $(this).remove();
2856            });
2857
2858            dependingField.removeAttr('autocomplete');
2859        }
2860    },
2861
2862    initEventTeaserEqualHeight: function() {
2863        var $teaser = $('.event__teaser');
2864        var captionMax = 0;
2865        var infoMax = 0;
2866        var contentMax = 0;
2867        var windowWidth = $(window).width();
2868
2869        if ($teaser.length) {
2870            if (windowWidth > 768) {
2871                $teaser.each(function() {
2872                    var captionHeight = $(this).find('.event__teaser-caption').outerHeight();
2873                    var infoHeight = $(this).find('.event__teaser-information').innerHeight();
2874                    var contentHeight = $(this).find('.event__teaser-content').innerHeight();
2875
2876                    if (captionHeight > captionMax) {
2877                        captionMax = captionHeight;
2878                    }
2879
2880                    if (infoHeight > infoMax) {
2881                        infoMax = infoHeight;
2882                    }
2883
2884                    if (contentHeight > contentMax) {
2885                        contentMax = contentHeight;
2886                    }
2887                });
2888
2889                $teaser.each(function() {
2890                    $(this).find('.event__teaser-caption').css('min-height', captionMax);
2891                    $(this).find('.event__teaser-information').css('min-height', infoMax);
2892                    $(this).find('.event__teaser-content').css('min-height', contentMax);
2893                });
2894
2895            } else {
2896                $teaser.each(function() {
2897                    $(this).find('.event__teaser-caption').removeAttr('style');
2898                    $(this).find('.event__teaser-information').removeAttr('style');
2899                    $(this).find('.event__teaser-content').removeAttr('style');
2900                });
2901            }
2902        }
2903    },
2904
2905    initMapRouteLink: function() {
2906    	$('.map__route-link').click(function(evt) {
2907    		if(typeof _paq != 'undefined') {
2908    			_paq.push(['trackEvent', 'Map', 'findRoute', document.title]);
2909    		}
2910    	});
2911    },
2912
2913    mailForm: function() {
2914        $('div.csc-mailform li.csc-form-element-radio input').each(function () {
2915            $(this).insertBefore($(this).prev('label'));
2916        });
2917    },
2918
2919    initTabs: function() {
2920      $('.mega-tab-menu').each(function() {
2921        var highest = 0;
2922        $(this).find('li').each(function() {
2923          var $this = $(this);
2924          if ( $this.outerHeight() > highest ) {
2925            highest = $this.outerHeight();
2926          }
2927        });
2928        $(this).find('li a').css('height', highest);
2929        $(this).find('li button').css('height', highest);
2930      });
2931    }
2932
2933};
2934
2935$(document).ready(function () {
2936    Limbach.initContactVitae();
2937    Limbach.initAccordion();
2938    Limbach.initMainNaviSearchButton();
2939    Limbach.initSliderTeaser();
2940    Limbach.initTeaserEqualHeights();
2941    Limbach.initEqualHeights();
2942    Limbach.initContact();
2943    Limbach.initCalculatorDescription();
2944    Limbach.initCalculatorShowResult();
2945    Limbach.initCalculatorSubmit();
2946    Limbach.initPayType();
2947    Limbach.initOverlay();
2948    Limbach.initFooterOverlay();
2949    Limbach.initMobileMenu();
2950    Limbach.initTriggerFirstLink();
2951    Limbach.initDownloadSlider();
2952    Limbach.initToTop();
2953    Limbach.scroll();
2954    Limbach.initQuoteHeight();
2955    Limbach.initMobileHeader();
2956    Limbach.initDifferentBillingAddress();
2957    Limbach.initSearchFilter();
2958    Limbach.initSwiperSlider();
2959    Limbach.initSliderCaptionPosition();
2960    Limbach.initSearchModalBox();
2961    Limbach.initEventTeaserEqualHeight();
2962    Limbach.initMapRouteLink();
2963    Limbach.mailForm();
2964    Limbach.initTabs();
2965    Limbach.initDatepicker();
2966
2967    var resizeTimeout;
2968    $(window).on('resize', function () {
2969        clearTimeout(resizeTimeout);
2970        resizeTimeout = setTimeout(function () {
2971            Limbach.initThrottledResize();
2972            Limbach.initEventTeaserEqualHeight();
2973        }, 100);
2974    });
2975
2976    $(window).scroll(function (e) {
2977        Limbach.scroll(e);
2978    });
2979
2980    if ($(".meta-navigation__link--highlighted.last").children("i").length > 0) {
2981        $(".hide-sm #language-switch").addClass("mr");
2982    }
2983});
2984
2985// Generated by CoffeeScript 2.2.4
2986(function() {
2987  var cart, chunks, shop;
2988
2989  chunks = function(array, size) {
2990    var results;
2991    results = [];
2992    while (array.length) {
2993      results.push(array.splice(0, size));
2994    }
2995    return results;
2996  };
2997
2998  cart = {
2999    sel: {
3000      cart: '.simpleshop-cart',
3001      inner: '.simpleshop-cart-inner',
3002      itemAmount: '.simpleshop-item-amount',
3003      toggle: '#cart-trigger'
3004    },
3005    updateAmount: function() {
3006      return console.log('wurst');
3007    },
3008    run: function() {
3009      var me;
3010      me = this;
3011      ($(this.sel.itemAmount)).change(function() {
3012        var amount, item, url;
3013        amount = ($(this)).val();
3014        item = ($(this)).data('item');
3015        url = "/simpleshop/update-item-amount?item=";
3016        url += item;
3017        url += "&amount=";
3018        url += amount;
3019        return $.ajax({
3020          url: url
3021        });
3022      });
3023      return ($(this.sel.toggle)).click(function() {
3024        ($(me.sel.cart)).toggleClass('open');
3025        return ($(me.sel.inner)).finish().slideToggle();
3026      });
3027    }
3028  };
3029
3030  shop = {
3031    sel: {
3032      parent: '.parent-category-checkbox',
3033      products: '.tx-simpleshop .products',
3034      addBtn: '.add-to-cart',
3035      categoryTree: '.category-tree',
3036      categoryToggle: '.category-toggle'
3037    },
3038    init: function() {
3039      var me, sets;
3040      me = this;
3041      this.equalizeHeight();
3042      sets = $('.childs');
3043      return sets.each(function(i, set) {
3044        var checkedSilbings, childs, parent;
3045        parent = ($(set)).siblings(me.sel.parent);
3046        childs = ($(set)).find('input[type="checkbox"]');
3047        checkedSilbings = ($(set)).find('input[type="checkbox"]:checked');
3048        if (childs.length === checkedSilbings.length) {
3049          return ($(parent)).prop('checked', true);
3050        }
3051      });
3052    },
3053    parentChange: function(parent) {
3054      var childs;
3055      childs = ($(parent)).siblings('.childs').find('input[type="checkbox"]');
3056      if (($(parent)).is(':checked')) {
3057        return ($(childs)).each(function(i, cb) {
3058          return ($(cb)).prop('checked', true);
3059        });
3060      } else {
3061        return ($(childs)).each(function(i, cb) {
3062          return ($(cb)).prop('checked', false);
3063        });
3064      }
3065    },
3066    childChange: function(child) {
3067      var checkedSilbings, parent, siblings;
3068      parent = (($(child)).parents('.childs')).siblings(shop.sel.parent);
3069      siblings = (($(child)).parents('.childs')).find('input[type="checkbox"]');
3070      if (!(($(child)).is(':checked'))) {
3071        return ($(parent)).prop('checked', false);
3072      } else {
3073        checkedSilbings = (($(child)).parents('.childs')).find('input[type="checkbox"]:checked');
3074        if (siblings.length === checkedSilbings.length) {
3075          return ($(parent)).prop('checked', true);
3076        }
3077      }
3078    },
3079    equalizeHeight: function() {
3080      var bottomHeight, count, first, heights, heightsPerRow, itemsPerRow, products;
3081      heights = [];
3082      itemsPerRow = 0;
3083      first = 0;
3084      count = true;
3085      ($('.tx-simpleshop .simpleshop-product')).each(function(i, el) {
3086        if (i === 0) {
3087          first = ($(el)).offset().left;
3088        }
3089        if ((i !== 0) && (first === ($(el)).offset().left)) {
3090          count = false;
3091        }
3092        if (count) {
3093          itemsPerRow += 1;
3094        }
3095        return heights.push(($(el)).height());
3096      });
3097      heightsPerRow = [];
3098      (chunks(heights, itemsPerRow)).forEach(function(a) {
3099        var height;
3100        height = 0;
3101        a.forEach(function(h) {
3102          if (height < h) {
3103            return height = h;
3104          }
3105        });
3106        return heightsPerRow.push(height);
3107      });
3108      bottomHeight = ($('.tx-simpleshop .simpleshop-product .product form .inner-bottom')).first().height();
3109      products = chunks($('.tx-simpleshop .simpleshop-product .product form .inner-top'), itemsPerRow);
3110      return ($(products)).each(function(row, productsInRow) {
3111        return ($(productsInRow)).each(function(_i, product) {
3112          return ($(product)).height(heightsPerRow[row] - bottomHeight);
3113        });
3114      });
3115    },
3116    run: function() {
3117      var me;
3118      me = this;
3119      this.init();
3120      ($(this.sel.categoryToggle)).on('click', function(ev) {
3121        return ($(me.sel.categoryTree)).finish().slideToggle();
3122      });
3123      ($(this.sel.categoryTree)).on('click', 'a.parent', function(ev) {
3124        ev.preventDefault();
3125        return ($(this)).siblings('.childs').slideToggle();
3126      });
3127      ($(this.sel.parent)).on('change', function() {
3128        return me.parentChange(this);
3129      });
3130      ($('.childs input[type="checkbox"]')).on('change', function() {
3131        return me.childChange(this);
3132      });
3133      return ($('.form-add-to-cart')).on('submit', function(ev) {
3134        var btn;
3135        if (($(this)).hasClass('submitted')) {
3136          ev.preventDefault();
3137          false;
3138        }
3139        ($(this)).addClass('submitted');
3140        btn = ($(this)).find(shop.sel.addBtn);
3141        ($(btn)).attr('readOnly', true);
3142        ($(btn)).addClass('button-disabled');
3143        return (($(btn)).find('i')).show();
3144      });
3145    }
3146  };
3147
3148  $(window).load(function() {
3149    if (($('.tx-simpleshop')).length > 0) {
3150      shop.run();
3151    }
3152    if (($('.simpleshop-cart')).length > 0) {
3153      return cart.run();
3154    }
3155  });
3156
3157}).call(this);

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.