PageSourceSearch

https://staycobblestone.com/wp-content/themes/harland_custom/js/site.js

js staycobblestone.com collected 2026-10-03 22:14:31 UTC 159,421 bytes, 4,824 lines download raw bytes

1/* 
2
3Project: www.staycobblestone.com
4Conrtibutors: Jeramiah Harland
5tue jul 16 6.19 pm
6----------------------
7Contents
8----------------------
9
101. General
112. Mobile
123. Scroll Horizontal Navigation
134. Calendar/Booking
145. Read More/Read Less
156. Lightbox & Scrolling
167. Group Booking
178. Gallery Script
189. States/Cities Dropdown
1910. TripAdvisor
2011. Google Places API - Explore and Location Maps
2112. 404 Page
2213. Helper Functions
2314. Guest Experience Survey Functions
2415. Anaytics Tracking
2516. Sliders
26
27*/
28
29/*jslint bitwise: true */
30
31/*global ga */
32
33/*global google */
34
35/*global cfsData */
36
37/*global jsData */
38
39/*jshint unused:false*/
40
41jQuery(document).ready(function ($) {
42
43    "use strict";
44
45// --------------------------------------------------------------------------
46// --------------------------------------------------------- General
47// --------------------------------------------------------------------------
48
49    //Adds placeholders for browsers that don't support them
50    try {
51
52        $('input, textarea').placeholder();
53
54    } catch (err) {
55    }
56
57    //Give tel-links 'phone_number' class
58    $('a[href^=tel]').each(function () {
59        $(this).addClass('phone_number');
60    });
61    $(window).on('load', function () {
62        $('a[href^=tel]').each(function () {
63            $(this).addClass('phone_number');
64        });
65    });
66
67    // Prevent all form submit events
68    // We will handle these on our own
69    $('form').submit(function (e) {
70        e.peventDefault();
71    });
72
73    // Booking link click from anywhere but the form
74    $('body').on('click', '.book-now', function (e) {
75
76        e.preventDefault();
77
78        // Track Event
79        trackEvent('Reservations', 'book-link', 'Booking Link', 5, true);
80
81        var bookingIDPattern = /\b\d{5}\b/g;
82
83        if ($(this).attr('href').indexOf('#') > -1 && bookingIDPattern.test($(this).attr('href'))) {
84
85            var bookingID = $(this).attr('href').match(bookingIDPattern)[0];
86            openBooking('', bookingID);
87
88        } else {
89
90            openBooking();
91
92        }
93    });
94
95    // centerSlideshows
96    var centerSlideshows = (function () {
97
98        var resizeTimer;
99
100        //Center Slideshows
101        function centerSlideshow() {
102            var windowWidth = viewport().width;
103            var marginDifference = 0;
104            var $targetImage = $('#property-slider .slide-image, #page-404-header .slide-image');
105            if (windowWidth <= 2000) { // width of image
106
107                if (windowWidth <= 1000) {
108                    $targetImage = $('#mobile-slider .image');
109                }
110
111                marginDifference = -1 * ($targetImage.width() - windowWidth) / 2;
112                $targetImage.css('margin-left', marginDifference + 'px');
113
114            } else {
115                $targetImage.css('margin-left', '0px');
116
117            }
118        }
119
120        $(window).on('resize', function (e) {
121
122            clearTimeout(resizeTimer);
123            resizeTimer = setTimeout(function () {
124
125                centerSlideshow();
126
127            }, 100);
128
129        });
130
131        $(window).load(function () {
132            centerSlideshow();
133        });
134
135        function viewport() {
136            var e = window, a = 'inner';
137            if (!('innerWidth' in window)) {
138                a = 'client';
139                e = document.documentElement || document.body;
140            }
141            return {width: e[a + 'Width'], height: e[a + 'Height']};
142        }
143
144        // return
145        return {
146            center: centerSlideshow
147        };
148
149    })(); // End Center Slideshow
150
151    // Center Slideshow as soon as document is ready
152    centerSlideshows.center();
153
154// --------------------------------------------------------------------------
155// -------------------------------------------------------------- Mobile
156// --------------------------------------------------------------------------
157
158    // Menu
159    var mobileMenu = (function () {
160
161        var mobileVisible = false;
162
163        $('#mobile-menu-button').click(function () {
164            $('#mobile-menu, html').addClass('menu-open');
165            mobileVisible = true;
166        });
167
168        $('#mobile-menu-close').click(function () {
169            if ($('#mobile-menu, html').hasClass('menu-open')) {
170                $('#mobile-menu, html').removeClass('menu-open');
171                mobileVisible = false;
172            }
173        });
174
175        $(document).mouseup(function (e) {
176            if (mobileVisible && !$(e.target).is('#mobile-menu *, #mobile-menu') || hasCustomClass(e.target)) {
177                if ($('#mobile-menu, html').hasClass('menu-open')) {
178                    $('#mobile-menu, html').removeClass('menu-open');
179                    mobileVisible = false;
180                }
181            }
182        });
183
184        $('#mobile-menu').on('click', '.mobile-icon', function (e) {
185            e.preventDefault();
186            mobileIconClick(this);
187        });
188
189        function mobileIconClick(icon) {
190
191            var a = $(icon).find('a');
192            var span = $(icon).find('span');
193
194            if (a.length > 0) {
195                var href = a.attr('href');
196                window.open(href, '_blank');
197            } else if (span.length > 0) {
198                toggleMobileIconContent(span.text());
199                setTimeout(function () {
200                    toggleMobileIconContent();
201                }, 2000);
202            }
203
204        }
205
206        function toggleMobileIconContent(content) {
207
208            var contentContainer = $('#mobile-icon-overlay');
209
210            if (contentContainer.hasClass('open')) {
211                contentContainer.removeClass('open');
212                contentContainer.empty();
213            } else {
214                var c = $('<span>').text(content);
215                contentContainer.append(c);
216                contentContainer.addClass('open');
217            }
218
219        }
220
221    })(); // End mobileMenu
222
223// --------------------------------------------------------------------------
224// ---------------------------------------------------------------- Captions
225// --------------------------------------------------------------------------
226
227    $('.caption .icon-close').on('click', function () {
228        $(this).closest('.caption').stop().fadeOut();
229    });
230
231
232// --------------------------------------------------------------------------
233// --------------------------------------------- Scroll Horizontal Navigation
234// --------------------------------------------------------------------------
235    if ($('.scroll-this-main').length && $('.scroll-this-top').length || $('.scroll-this-bottom').length) {
236
237        // Protect scope
238        var scrollingFeatures = (function () {
239
240            var $main = $('.scroll-this-main');
241            var $topNav = ($('.scroll-this-top').length !== -1) ? $('.scroll-this-top') : '';
242            var $outerWrapper = $('#outer-wrapper');
243            var $bottomNav = ($('.scroll-this-bottom').length !== -1) ? $('.scroll-this-bottom') : '';
244            var $mainContent = ($main.find('.row, .slick-dots').length !== -1) ? $main.find('.row, .slick-dots') : '';
245            var $images = ($main.find('.slide').children('img').length !== -1) ? $main.find('.slide').children('img') : '';
246            var $phantom = ($('#blue-phantom').length !== -1) ? $('#blue-phantom') : '';
247            var $adminBar = $('#wpadminbar');
248            var loggedIn = $('body').hasClass('logged-in');
249
250            // resize time
251            var rTime;
252
253            // Timeout
254            var timeout = false;
255
256            // milliseconds
257            var ms = 100;
258
259            function init() {
260                updateHeight();
261                updateScrolling();
262            }
263
264            function updateScrolling() {
265
266                // Window (viewport)
267                var $window = $(window);
268
269                // Sets the position where, once reached, the fixed class will be applied (stick to bottom)
270                var fixToBottomAt = $main.offset().top + $main.outerHeight();
271                if ($bottomNav !== '') {
272                    fixToBottomAt += $bottomNav.outerHeight();
273                }
274
275                // Sets the position where, once reached, the fixed class will be applied (stick to top)
276                var fixToTopAt = $main.offset().top + $main.outerHeight();
277
278                // Current scroll position plus window height
279                var windowBottomPos = $window.scrollTop() + $window.height();
280
281                if (fixToBottomAt > windowBottomPos) { // When slider is bigger than the window
282
283                    // fix to bottom
284
285                    // Remove 'top' add 'fixed'
286                    //$booking.removeClass('top').addClass('fixed');
287
288                    // For slide content
289                    //var newBottom = Math.round(fixToBottomAt - windowBottomPos);
290                    //$slideContent.css('bottom', newBottom);
291
292                    // Remove top navigation to fixed
293                    //$outerWrapper.css('margin-top', 0);
294                    //$booking.css('top', 'auto');
295                    //$topNav.removeClass('fixed open');
296
297                    // Show phantom booking
298                    //showPhantom();
299
300                } else if ($window.scrollTop() > fixToTopAt) { // When scrollbar is passed the slider/booking
301
302                    // Admin Bar
303                    var adminBar = loggedIn && $adminBar.css('position') === 'fixed' ? Math.round($adminBar.outerHeight()) : 0;
304
305                    // Add 'fixed' and 'top'
306                    if ($bottomNav !== '') {
307                        $bottomNav.addClass('fixed top');
308                        //$bottomNav.css('top', '0px');
309                        $bottomNav.css('top', (adminBar + 'px'));
310                        if ($phantom !== '') {
311                            showPhantom();
312                        }
313                    }
314
315                    if ($topNav !== '' && $topNav.is(':visible')) {
316                        $bottomNav.css('top', (adminBar + Math.round($topNav.outerHeight())) + 'px');
317                        $topNav.addClass('fixed').css('top', adminBar);
318                        $topNav.addClass('open');
319
320                        // Add top navigation to fixed
321                        $outerWrapper.css('margin-top', Math.round($topNav.outerHeight()) + 'px');
322                    }
323
324                } else { // Booking is visible and so is the slider
325
326                    if ($bottomNav !== '') {
327                        // Remove 'fixed'
328                        $bottomNav.removeClass('fixed');
329                        $bottomNav.css('top', 'auto');
330                        if ($phantom !== '') {
331                            hidePhantom();
332                        }
333                    }
334
335                    if ($topNav !== '') {
336                        // Remove top navigation to fixed
337                        $topNav.removeClass('fixed open').css('top', 0);
338                    }
339
340                    if ($mainContent !== '') {
341                        // Set bottom of content back to 0
342                        $mainContent.css('bottom', 0);
343                    }
344
345                    $outerWrapper.css('margin-top', 0);
346
347
348                }
349
350                //	Parallax
351                //	if($images.height() > $slider.outerHeight()){ // When image is bigger than the slider
352                //
353                //		var overflow = $images.height() - $slider.outerHeight();
354                //
355                //		var newTop = Math.round((overflow - $(window).scrollTop())*-1);
356                //		$images.css('transform', 'translateY( ' + newTop/2 + 'px)');
357                //
358                //	}else{
359                //		$images.css('transform', 'none');
360                //	}
361
362            }
363
364
365            function showPhantom() {
366                $phantom.height($bottomNav.outerHeight()).show();
367            }
368
369            function hidePhantom() {
370                $phantom.hide();
371            }
372
373            function updateHeight() {
374                // Remove previous height attribute
375                $main.css('height', '');
376                // Set the slide height as the active image height
377
378                var $slide = $main.find('.slide');
379
380                if ($slide.length !== -1 && $slide.is(':visible')) {
381
382                    var $img = $main.find('.slide-image');
383
384                    var newHeight = $img.height();
385
386                    $main.find('.slide').height(newHeight);
387                }
388
389            }
390
391            $(window).scroll(throttle(25, function () {
392                updateScrolling();
393            }));
394
395            $(window).on('resize', function () {
396                updateContent();
397            });
398
399            $(window).on('load', function () {
400                updateHeight();
401            });
402
403            function updateContent() {
404                rTime = new Date();
405                if (timeout === false) {
406                    timeout = true;
407                    setTimeout(resizeEnd, ms);
408                }
409            }
410
411            function resizeEnd() {
412                if (new Date() - rTime < ms) {
413                    setTimeout(resizeEnd, ms);
414                } else {
415                    timeout = false;
416                    init();
417                }
418            }
419
420            // return
421            return {
422                init: init,
423                updateHeight: updateHeight,
424                updateScroll: updateScrolling
425            };
426
427        })(); // End scrollingFeatures
428
429    }
430
431// --------------------------------------------------------------------------
432// -------------------------------------------------------- Calendar/Booking
433// --------------------------------------------------------------------------
434
435    if ($('#interior-booking').length) {
436
437        var bookingCalendars = (function () {
438
439            var inputWidth = ($('#interior-booking').find('input.datepicker-checkin').outerWidth()) * -1;
440
441            function init() {
442                $('input.datepicker-checkin').Zebra_DatePicker({
443                    direction: true,
444                    format: 'm/d/Y',
445                    show_icon: false,
446                    offset: [inputWidth, 0],
447                    first_day_of_week: 0,
448                    header_navigation: ['<i class="fa fa-angle-left"></i>', '<i class="fa fa-angle-right"></i>'],
449                    pair: $('input.datepicker-checkout')
450                });
451
452                $('input.datepicker-checkout').Zebra_DatePicker({
453                    direction: 1,
454                    show_icon: false,
455                    offset: [inputWidth, 0],
456                    first_day_of_week: 0,
457                    header_navigation: ['<i class="fa fa-angle-left"></i>', '<i class="fa fa-angle-right"></i>'],
458                    format: 'm/d/Y'
459                });
460            }
461
462            // return
463            return {
464                init: init,
465                hide: hideCalendars,
466
467            };
468        })(); // End bookingCalendar
469
470        // Set Calendars
471        $(window).on('load', function () {
472            bookingCalendars.init();
473        });
474
475        // Check availablity form submit
476        $('#check-availability .book-now-form').on('click', function (e) {
477
478            // Stop page from jumping to link
479            e.preventDefault();
480
481            // Track Event
482            trackEvent('Reservations', 'book-form', 'Booking Form', 5, true);
483
484            // Get form elements as array
485            var formData = {};
486            $.each($('#check-availability form').serializeArray(), function (i, field) {
487                formData[field.name] = field.value;
488            });
489
490            if (($(this).attr('data-ivhtravel') == 'IVHTravel') || ($(this).attr('data-ivhtravel') == 'WebRezPro')) {
491                openNewBooking(formData, 0, $(this).attr('data-ivhtravel'));
492                return false;
493            }
494
495            openBooking(formData);
496
497        });
498    }
499
500    function openNewBooking(data, bookingID, method) {
501
502        var bookingData = data;
503        if (typeof bookingData === 'undefined') {
504            bookingData = '';
505        }
506
507        // Today
508        var date = new Date();
509        var dd = date.getDate();
510        var mm = date.getMonth() + 1;
511        var yyyy = date.getFullYear();
512
513        var today = mm + '/' + dd + '/' + yyyy;
514
515        // Tomrrow
516        var tomorrow = mm + '/' + (dd + 1) + '/' + yyyy;
517
518        // Defaults
519        var chain = '7721';
520        var step = '2';
521        var shell = 'GDC';
522        var hotel = '00000';
523        var arrive = today;
524        var depart = tomorrow;
525        var rooms = '1';
526        var no_adults = '2';
527        var no_children = '0';
528        var utm_source = 'IBC';
529        var device = 'desktop';
530
531        if ((typeof bookingID !== 'undefined') && (bookingID != 0)) {
532            hotel = bookingID;
533        }
534
535        if (bookingData !== '') {
536            // Get date and replace slashes
537            arrive = bookingData['check-in'];
538            depart = bookingData['check-out'];
539        }
540
541        // Fix slashes for URL
542        //arrive = arrive.replace(/\//g,'%2F');
543        //depart = depart.replace(/\//g,'%2F');
544
545        arrive = arrive.replace(/(\d\d)\/(\d\d)\/(\d{4})/, "$3-$1-$2");
546        depart = depart.replace(/(\d\d)\/(\d\d)\/(\d{4})/, "$3-$1-$2");
547
548        // Use cfsData if we have it
549        if (typeof cfsData !== "undefined" && cfsData !== null) {
550
551            chain = cfsData.property.chain_id;
552            hotel = cfsData.property.booking_id_ivhtravel;
553
554        }
555
556        if (hotel === '00000' || hotel === '') {
557            hotel = '';
558        } else {
559            hotel = '?hotel=' + hotel;
560        }
561        var link = jQuery('span.book-now-form').attr('data-cstmurl');
562        if (method == 'IVHTravel') {
563            arrive = '&checkin=' + arrive;
564            depart = '&checkout=' + depart;
565
566            rooms = '&rooms=' + rooms;
567            no_adults = '&no_adults=' + no_adults;
568            no_children = '&no_children=' + no_children;
569            utm_source = '&utm_source=' + utm_source;
570            device = '&device=' + device;
571
572            // Build URL
573
574            var url = link + hotel + arrive + depart + rooms + no_adults + n
574o_children + utm_source + device;
575        }
576        // var url = 'https://www.ivhtravel.com/hotel-centerstone-inn-doswell-at-kings-dominion-doswell-h87913.html'+hotel+arrive+depart+rooms+no_adults+no_children+utm_source+device;
577        if (method == 'WebRezPro') {
578            if (link.indexOf('?') < 1) link = link + '?';
579            var url = link + '&date_from=' + arrive.replace(/-/g, '') + '&date_to=' + depart.replace(/-/g, '') + '&num_adults=' + no_adults + '&num_children=' + no_children;
580        }
581        // Open new window
582        // console.log(url);
583        window.open(url, "_self");
584    }
585
586    function openBooking(data, bookingID) {
587
588        var bookingData = data;
589        if (typeof bookingData === 'undefined') {
590            bookingData = '';
591        }
592
593        // Today
594        var date = new Date();
595        var dd = date.getDate();
596        var mm = date.getMonth() + 1;
597        var yyyy = date.getFullYear();
598
599        var today = mm + '/' + dd + '/' + yyyy;
600
601        // Tomrrow
602        var tomorrow = mm + '/' + (dd + 1) + '/' + yyyy;
603
604        // Defaults
605        var chain = '7721';
606        var step = '2';
607        var shell = 'GDC';
608        var hotel = '00000';
609        var arrive = today;
610        var depart = tomorrow;
611
612        if (typeof bookingID !== 'undefined') {
613            hotel = bookingID;
614        }
615
616        if (bookingData !== '') {
617            // Get date and replace slashes
618            arrive = bookingData['check-in'];
619            depart = bookingData['check-out'];
620        }
621
622        // Fix slashes for URL
623        arrive = arrive.replace(/\//g, '%2F');
624        depart = depart.replace(/\//g, '%2F');
625
626
627        // Use cfsData if we have it
628        if (typeof cfsData !== "undefined" && cfsData !== null) {
629
630            chain = cfsData.property.chain_id;
631            hotel = cfsData.property.booking_id;
632
633        }
634
635        // Build strings for URL
636        chain = '?chain=' + chain;
637        step = '&step=' + step;
638        shell = '&shell=' + shell;
639
640        if (hotel === '00000' || hotel === '') {
641            hotel = '';
642        } else {
643            hotel = '&hotel=' + hotel;
644        }
645
646        arrive = '&arrive=' + arrive;
647        depart = '&depart=' + depart;
648
649        var linkParam = '';
650        if (typeof ga !== 'undefined') {
651
652            ga('require', 'linker');
653
654            ga(function (tracker) {
655                linkParam = tracker.get('linkerParam');
656                linkParam = '&' + linkParam;
657            });
658
659        }
660
661        // Build URL
662        var url = 'https://be.synxis.com/' + chain + step + shell + hotel + arrive + depart + linkParam;
663
664        // Open new windows
665        window.open(url, "_self");
666
667        // Open in same window
668        window.location(url);
669
670    }
671
672// --------------------------------------------------------------------------
673// ----------------------------------------------------- Read More/Read Less
674// --------------------------------------------------------------------------
675
676    if ($('#read-container').length) {
677        // Hide content area
678        if ($('#interior-main .content').children().length > 2) {
679            toggleViewInit();
680        } else {
681            $('#read-container').hide();
682        }
683    }
684
685    function toggleView() {
686
687        // Track Event
688        trackEvent('Interaction', 'read-more-less', 'Read Property Description');
689
690        // Get number of visible children
691        var numVisible = $('#interior-main .content').children(':visible').length;
692        // Hide read more/read less buttons
693        $('#read-container').children().hide();
694        // if there is only one visible child
695        // fade all children in otherwise only show first p
696        if (numVisible === 2) {
697            $('#interior-main .content').find('p:not(:nth-child(-n+2))').stop().slideDown();
698            $('#read-container .less').show();
699        } else {
700            $('#interior-main .content').find('p:not(:nth-child(-n+2))').stop().slideUp();
701            $('#read-container .more').show();
702        }
703    }
704
705    function toggleViewInit() {
706        // Hide all children except first on initial load;
707        $('#interior-main .content').find('p:not(:nth-child(-n+2))').hide();
708        $('#read-container .more').show();
709    }
710
711    $('#read-container .less, #read-container .more').on('click', function () {
712        toggleView();
713    });
714
715// --------------------------------------------------------------------------
716// --------------------------------------------------------- Weather
717// --------------------------------------------------------------------------
718
719    // initWeather runs on document ready and sets the weather
720    $(window).on('load', function () {
721        if ($('#property-weather').length && typeof cfsData !== "undefined" && cfsData !== null) {
722            initWeather();
723        }
724    });
725
726    function initWeather() {
727        var html = '';
728        $.ajax({
729            url: jsData.ajaxURL,
730            type: 'POST',
731            data: {
732                action: 'get_weather',
733                latitude: cfsData.map.latitude,
734                longitude: cfsData.map.longitude
735            },
736            dataType: 'json',
737            async: true
738        })
739            .success(function (response) {
740                if (response.error) {
741                    hideWeather();
742                } else {
743                    var weather = getCondDetails(response);
744                    html = '<i class="' + weather.icon + '"></i>';
745                    html += '<span class="temp">' + weather.temp + '°</span>';
746                    html += windChill(weather.feelslike);
747                    html += '<span class="status">
747' + weather.text + '</span>';
748                    $('#property-weather').html(html);
749                }
750            })
751            .fail(function (error) {
752                hideWeather();
753            });
754
755    }
756
757    function hideWeather() {
758        $('#property-weather').hide();
759    }
760
761    function windChill(temp) {
762        var display = '';
763        if (temp < 0) {
764            display = '<span id="wind-chill">(Feels Like ' + temp + '°)</span>';
765        }
766        return display;
767    }
768
769    function getCondDetails(current) {
770
771        // Create weather object to return
772        var weather = {
773            temp: current.temp,
774            icon: '',
775            text: current.summary,
776            feelslike: current.feelslike
777        };
778
779        switch (current.icon) {
780
781            case 'clear-day':
782                weather.icon = 'wi wi-day-sunny';
783                break;
784
785            case 'clear-night':
786                weather.icon = 'wi wi-night-clear';
787                break;
788
789            case 'rain':
790                weather.icon = 'wi wi-rain';
791                break;
792
793            case 'snow':
794                weather.icon = 'wi wi-snow';
795                break;
796
797            case 'sleet':
798                weather.icon = 'wi wi-sleet';
799                break;
800
801            case 'wind':
802                weather.icon = 'wi wi-windy';
803                break;
804
805            case 'fog':
806                weather.icon = 'wi wi-fog';
807                break;
808
809            case 'cloudy':
810                weather.icon = 'wi wi-cloudy';
811                break;
812
813            case 'partly-cloudy-day':
814                weather.icon = 'wi wi-day-cloudy';
815                break;
816
817            case 'partly-cloudy-night':
818                weather.icon = 'wi wi-night-cloudy';
819                break;
820
821            case 'hail':
822                weather.icon = 'wi wi-hail';
823                break;
824
825            case 'thunderstorm':
826                weather.icon = 'wi wi-thunderstorm';
827                break;
828
829            case 'tornado':
830                weather.icon = 'wi wi-tornado';
831                break;
832
833            default:
834                weather.icon = 'wi wi-cloudy';
835                break;
836
837        }
838
839        return weather;
840
841    }
842
843// --------------------------------------------------------------------------
844// ---------------------------------------------------- Lightbox & Scrolling
845// --------------------------------------------------------------------------
846
847    // Lightbox overlay - Scroll
848    $(window).scroll(throttle(100, function () {
849        if ($('.current-open').length && $('.current-open').is(':visible')) {
850            scrollOverlay('slow');
851        }
852    }));
853
854    // Lightbox overlay - Functions
855    function scrollOverlay(speed, callback) {
856        // Optional Speed
857        speed = speed;
858        if (typeof speed === 'undefined') {
859            speed = 250;
860        }
861
862        // Content container
863        var $scrollObject = $(".current-open");
864        var topOffset = 25;
865
866        // Variables
867        var distanceToBottom = $(document).height() - $scrollObject.offset().top - $scrollObject.outerHeight();
868        var distanceFromTop = $(window).scrollTop() + topOffset + $scrollObject.outerHeight();
869        var overFlowHeight = distanceFromTop + topOffset - $(document).height();
870
871        // Scroll if visible element can't be seen in viewport
872        // or if element is hidden, then always scroll it
873        // this makes sure it's always accurately positioned when it appears
874        if (!$($scrollObject.find('.scroll-marker')).isOnScreen(0.1, 0.1) || !$scrollObject.is(':visible')) {
875            if (overFlowHeight > 0) {
876                $scrollObject.stop().animate({
877                    top: ($(window).scrollTop() - overFlowHeight) + 'px'
878                }, speed);
879            } else {
880                $scrollObject.stop().animate({
881                    top: ($(window).scrollTop() + topOffset) + 'px'
882                }, speed);
883            }
884        }
885
886        // Optional Callback function
887        if (typeof callback === 'function' && callback()) {
888            callback();
889        }
890    }
891
892    // Lightbox click
893    $(document).on('click', '.open-lightbox', function (e) {
894        e.preventDefault();
895        openLightbox(this);
896    });
897
898    // Lightbox open
899    function openLightbox(e) {
900
901        var getTarget = '';
902        if (typeof e === 'string') {
903            if (e.indexOf('.') === -1 && e.indexOf('#') === -1) {
904                getTarget = '#' + e;
905            } else {
906                getTarget = e;
907            }
908        } else if (typeof $(e).attr('id') !== 'undefined' && $(e).attr('id') !== null) {
909            getTarget = '#' + $(e).attr('id');
910        } else {
911            getTarget = $(e).attr('href');
912        }
913
914        var targetObject = getTarget + '-lightbox';
915        $(targetObject).addClass('current-open');
916        $('#page-overlay').fadeIn(function () {
917            // Use the same function from the lightbox overlay
918            scrollOverlay(100);
919            $(targetObject).css('display', 'block').animate({opacity: 1});
920        });
921    }
922
923    // Lightbox close
924    function closeLightbox(e) {
925        if (!$(e.target).parents('.lightbox-content').length || $(e.target).hasClass('lightbox-close')) {
926            $('#page-overlay, .lightbox-content').stop().fadeOut(function () {
927                $('.lightbox-content').removeClass('current-open');
928            });
929        }
930    }
931
932    // Click page overlay or close button
933    $('#page-overlay, .lightbox-close').click(function () {
934        closeLightbox(this);
935    });
936
937    // Escape button press
938    $(document).keyup(function (e) {
939        if (!$(document.activeElement).closest('.lightbox-content').length || e.which === 27 && $('#page-overlay').length) {
940            closeLightbox(this);
941        }
942    });
943
944// --------------------------------------------------------------------------
945// --------------------------------------------------------- Privacy Alert
946// --------------------------------------------------------------------------
947
948    // Close Privacy Alert
949    $('#privacy-alert .close-privacy').on('click', function () {
950        $('#privacy-alert').hide();
951    });
952
953    $('#privacy-alert .privacy-consent-button').on('click', function (e) {
954
955        e.preventDefault();
956
957        if (typeof jsData !== "undefined" && jsData !== null) {
958
959            // Use Ajax to submit to functions.php
960            $.ajax({
961                url: jsData.ajaxURL,
962                type: 'POST',
963                data: {
964                    action: 'add_privacy_consent'
965                },
966                dataType: 'json',
967                async: true
968            })
969                .success(function (response) {
970
971                    if (!response.fail) {
972
973                        // Set cookie
974                        console.log('privacy cookie set');
975                        location.reload();
976
977                    } else {
978                        console.log(response.fail);
979                    }
980
981
982                })
983                .fail(function (error) {
984                    console.log(error);
985                })
986                .always(function (response) {
987
988                    console.log(response);
989
990                    // Hide
991                    $('#privacy-alert').hide();
992                });
993
994        }
995
996    });
997
998
999// --------------------------------------------------------------------------
1000// ---------------------------------------------------------- Group Booking
1001// --------------------------------------------------------------------------
1002
1003    var groupBooking = (function () {
1004
1005        var working = false;
1006
1007        function validateFormData() {
1008            var data = $('#group-booking-form form');
1009            if (validate.form(data)) {
1010                // Pass
1011                toggleLoader();
1012                if (typeof jsData !== "undefined" && jsData !== null) {
1013                    if (typeof cfsData !== "undefined" && cfsData !== null) {
1014
1015                        // Fix obfuscated email
1016                        cfsData.property.email = fixEmail(cfsData.property.email);
1017
1018                        // Use Ajax to submit to functions.php
1019                        $.ajax({
1020                            url: jsData.ajaxURL,
1021                            type: 'POST',
1022                            data: {
1023                                action: 'send_mail_group_booking',
1024                                form: data.serialize(),
1025                                property: $.param(cfsData.property)
1026                            },
1027                            dataType: 'json',
1028                            async: true
1029                        })
1030                            .success(function (response) {
1031                                if (response.success) {
1032                                    success();
1033                                } else {
1034                                    fail(response.fail);
1035                                }
1036                            })
1037                            .fail(function (error) {
1038                                fail(error);
1039                            })
1040                            .always(function () {
1041
1042                                // Virtual pageview
1043                                // virtualPageView('group-booking-submit');
1044
1045                                toggleLoader();
1046                                working = false;
1047                            });
1048                    }
1049                } // End If
1050            } else {
1051                // Fail
1052                working = false;
1053                console.log('validation failed');
1054            }
1055        }
1056
1057        function createMessage(type, msg) {
1058
1059            var errorMessage = msg;
1060            if (typeof errorMessage === 'undefined') {
1061                errorMessage = '';
1062            }
1063
1064            var $formContainer = $('#group-booking-form');
1065
1066            var responseContainer = $('<div>').attr({
1067                class: 'response-container ' + type.toLowerCase()
1068            });
1069
1070            var messageIcon = $('<i>').attr({
1071                class: 'fa'
1072            }).appendTo(responseContainer);
1073
1074            var message = $('<div>').attr({
1075                class: 'cobble-font message'
1076            }).appendTo(responseContainer);
1077
1078            var messageDescription = $('<p>').attr({
1079                class: 'message-description'
1080            }).appendTo(responseContainer);
1081
1082            var messageSubText = $('<span>').attr({
1083                class: 'message-sub-text'
1084            }).text('').appendTo(responseContainer);
1085
1086            $formContainer.append(responseContainer);
1087
1088
1089            if (type === 'success') {
1090
1091                messageIcon.addClass('fa-check');
1092                message.text('Success!');
1093                messageDescription.text('Your message has been sent to ' + cfsData.property.city + '. Note: This does not book your stay, it only gets the information to ' + cfsData.property.city + '.');
1094                messageSubText.text('Click here to send another');
1095
1096                messageSubText.one('click', function () {
1097                    toggleMessage(toggleForm());
1098                });
1099
1100            } else {
1101
1102                messageIcon.addClass('fa-exclamation');
1103                message.text('There was an error!');
1104                messageDescription.text('If you continue to experience an issue, please call or email ' + cfsData.property.city + ' directly.');
1105
1106                if (errorMessage !== '') {
1107                    console.log(errorMessage);
1108                    messageSubText.text('Error Message: ' + errorMessage + ' - click here to try again');
1109                }
1110
1111                messageSubText.one('click', function () {
1112                    toggleMessage(toggleForm());
1113                });
1114
1115            }
1116
1117        }
1118
1119        function toggleMessage(callback) {
1120
1121            var $message = $('#group-booking-form .response-container');
1122
1123            if ($message.is(':visible')) {
1124                $message.stop().fadeOut(function () {
1125                    $message.remove();
1126                    // Optional Callback function
1127                    if (typeof callback === 'function' && callback()) {
1128                        callback();
1129                    }
1130                });
1131            } else {
1132                $message.stop().fadeIn(function () {
1133                    // Optional Callback function
1134                    if (typeof callback === 'function' && callback()) {
1135                        callback();
1136                    }
1137                });
1138            }
1139        }
1140
1141        function toggleForm(callback) {
1142
1143            var $form = $('#group-booking-form .form');
1144
1145            if ($form.is(':visible')) {
1146                $form.stop().fadeOut(function () {
1147                    // Optional Callback function
1148                    if (typeof callback === 'function' && callback()) {
1149                        callback();
1150                    }
1151                });
1152            } else {
1153                $form.stop().fadeIn(function () {
1154                    // Optional Callback function
1155                    if (typeof callback === 'function' && callback()) {
1156                        callback();
1157                    }
1158                });
1159            }
1160        }
1161
1162        function toggleLoader() {
1163
1164            var $loader = $('#group-booking-form .submit-button .loader');
1165
1166            if ($loader.is(':visible')) {
1167                $loader.hide();
1168            } else {
1169                $loader.css('display', 'inline-block');
1170            }
1171        }
1172
1173        function success() {
1174
1175            var $formContainer = $('#group-booking-form');
1176
1177            createMessage('success');
1178
1179            toggleMessage(toggleForm());
1180        }
1181
1182        function fail(msg) {
1183
1184            // For debugging
1185            msg = msg;
1186            if (typeof msg === 'undefined') {
1187                msg = 'fail';
1188            }
1189
1190            createMessage('fail', msg);
1191
1192            toggleMessage(toggleForm());
1193        }
1194
1195        // Group Booking Events
1196        $('.show-group-booking').click(function (e) {
1197
1198            e.preventDefault();
1199
1200            validate.setRequired($('#group-booking-form form'));
1201
1202            openLightbox('group-booking');
1203
1204            var inputWidth = ($('input.datepicker-group-arrive').outerWidth()) * -1;
1205
1206            $('input.datepicker-group-arrive').Zebra_DatePicker({
1207                direction: true,
1208                format: 'm/d/Y',
1209                show_icon: false,
1210                offset: [inputWidth, 0],
1211                header_navigation: ['<i class="fa fa-angle-left"></i>', '<i class="fa fa-angle-right"></i>'],
1212                pair: $('input.datepicker-group-depart')
1213            });
1214
1215            $('input.datepicker-group-depart').Zebra_DatePicker({
1216                direction: 1,
1217                show_icon: false,
1218                offset: [inputWidth, 0],
1219                header_navigation: ['<i class="fa fa-angle-left"></i>', '<i class="fa fa-angle-right"></i>'],
1220                format: 'm/d/Y'
1221            });
1222        });
1223
1224        $('#group-booking-form .submit-button').on('click', function (e) {
1225            e.preventDefault();
1226            if (!working) {
1227
1228                // Track Event
1229                trackEvent('Information Request', 'book-group', 'Group Booking Request', 3, true);
1230
1231                working = true;
1232                groupBooking.send();
1233            }
1234        });
1235
1236        $('#group-booking-form form :input').on('change', function (e) {
1237            validate.clearError(this);
1238        });
1239
1240        // return
1241        return {
1242            send: validateFormData
1243        };
1244    })(); // End groupBooking
1245
1246
1247// --------------------------------------------------------------------------
1248// ---------------------------------------------------------- Gallery Script
1249// --------------------------------------------------------------------------
1250
1251    if (typeof (cfsData) !== "undefined" && cfsData !== null) {
1252
1253        $('.show-gallery').click(function (e) {
1254            e.preventDefault();
1255
1256            // Track Event
1257            trackEvent('Interaction', 'gallery-open', 'Viewed Photo Gallery');
1258
1259            var scrollPos = 0;
1260            var galleryIndex = $(this).find('img').attr('class');
1261            if (typeof galleryIndex !== "undefined" && galleryIndex !== null && galleryIndex.indexOf("index-") >= 0) {
1262                galleryIndex = galleryIndex.substring(galleryIndex.indexOf("index-"));
1263                galleryIndex = galleryIndex.replace(/\D/g, '');
1264                galleryIndex = parseInt(galleryIndex);
1265            } else {
1266                galleryIndex = 0;
1267            }
1268
1269            $(this).lightGallery({
1270                dynamic: true,
1271                mode: "lg-fade",
1272                download: false,
1273                enableTouch: true,
1274                dynamicEl: cfsData.gallery,
1275                index: galleryIndex
1276            }).on('onCloseAfter.lg', function (event) {
1277                scrollingFeatures.updateScroll();
1278            });
1279        });
1280
1281    }
1282
1283// --------------------------------------------------------------------------
1284// --------------------------------------------------- States/Cities Dropdown
1285// --------------------------------------------------------------------------
1286
1287    var findACobblestone = (function () {
1288
1289        var statesCities;
1290
1291        function init() {
1292            if (typeof (jsData) !== "undefined" && jsData !== null) {
1293                $.ajax({
1294                    url: jsData.ajaxURL,
1295                    type: 'GET',
1296                    data: {
1297                        action: 'get_states_cities'
1298                    },
1299                    dataType: 'json',
1300                    async: true
1301                })
1302                    .success(function (response) {
1303                        statesCities = response;
1304                        createDropdown();
1305                    });
1306            }
1307        }
1308
1309        function createDropdown() {
1310            $.each(statesCities, function (key, value) {
1311                $.each(value.info, function (abbr, full) {
1312                    $('<option>').attr({
1313                        value: abbr
1314                    }).text(full).appendTo('#select-cobblestone-state');
1315                });
1316            });
1317        }
1318
1319        function updateDropdown() {
1320            $('#select-cobblestone-city').find('option:not(.keep)').remove();
1321
1322            var state = $('#select-cobblestone-state').val();
1323            $.each(statesCities, function (key, value) {
1324                $.each(value.properties, function (city, info) {
1325                    if (info.state === state) {
1326                        $('<option>').attr({
1327                            value: info.link,
1328                            id: cleanTitle(info.city) + '_' + cleanT
1328itle(info.state_abbr) + '_listing'
1329                        }).text(info.city).appendTo('#select-cobblestone-city');
1330                    }
1331                });
1332            });
1333
1334            // todo try here
1335            // Event listener - Add event handler for clicking on place in list
1336            // document.getElementById(cleanTitle(info.city) + '_' + cleanTitle(info.state_abbr) + '_listing').addEventListener('click', function(event) {
1337            // 	//removed li[0], a refernce to the object from the earlier iteration
1338            //     if(!working && !$(event.target).is('a')){
1339            // 		working = true;
1340            // 		clickPlaceList(this);
1341            // 	}
1342            // });
1343
1344        }
1345
1346        function updateHref() {
1347
1348            var $submitButton = $('#select-cobblestone .find-form');
1349
1350            var $city = $('#select-cobblestone-city');
1351
1352            var newHref = '#select-cobblestone';
1353
1354            if ($city.val() !== 'select-city') {
1355                newHref = $city.val();
1356            }
1357
1358            $submitButton.attr('href', newHref);
1359
1360        }
1361
1362        $('#select-cobblestone-state').on('change', function () {
1363            updateDropdown();
1364        });
1365
1366        $('#select-cobblestone-city').on('change', function () {
1367            updateHref();
1368        });
1369
1370        $('#select-cobblestone .find-form').on('click', function (e) {
1371
1372            var $state = $('#select-cobblestone-state');
1373            var $city = $('#select-cobblestone-city');
1374
1375            if ($city.val() === 'select-city' || $state.val() === 'select-state') {
1376                e.preventDefault();
1377            } else {
1378                // Do nothing, let event fire normally
1379                // Track Event
1380                trackEvent('Navigation', 'select-state-city', 'Navigated Using Dropdowns', 1);
1381            }
1382        });
1383
1384        // return
1385        return {
1386            init: init,
1387            update: updateDropdown
1388        };
1389
1390    })(); // End findACobblestone
1391
1392    if ($('#select-cobblestone').length) {
1393        findACobblestone.init();
1394    }
1395
1396// --------------------------------------------------------------------------
1397// ------------------------------------------------------------- TripAdvisor
1398// --------------------------------------------------------------------------
1399
1400    if ($('#tripadvisor-info').length && typeof cfsData !== "undefined" && cfsData !== null) {
1401
1402        initTripAdvisor();
1403
1404        // On tab click
1405        $('#tripadvisor-info .tab').on('click', function () {
1406
1407            toggleTripAdvisor();
1408
1409        });
1410
1411        // Close TripAdvisor if clicking on something else
1412        $(document).click(function (e) {
1413            if ($('#tripadvisor-info').hasClass('open') && !$(e.target).closest('#tripadvisor-info').length) {
1414                closeTripAdvisor();
1415            }
1416        });
1417    }
1418
1419    function toggleTripAdvisor() {
1420        if ($('#tripadvisor-info').hasClass('open')) {
1421            closeTripAdvisor();
1422        } else {
1423
1424            // Track Event
1425            trackEvent('Interaction', 'tripadvisor-open', 'Opened TripAdvisor Tab');
1426
1427            openTripAdvisor();
1428        }
1429    }
1430
1431    function openTripAdvisor() {
1432        $('#tripadvisor-info').addClass('open');
1433    }
1434
1435    function closeTripAdvisor() {
1436        $('#tripadvisor-info').removeClass('open');
1437    }
1438
1439    function initTripAdvisor() {
1440
1441        if (cfsData.property.tripadvisor_id !== '' && $('#tripadvisor-info').length) {
1442            getTripAdvisorData(cfsData.property);
1443        }
1444    }
1445
1446    function getTripAdvisorData(data) {
1447
1448        var id = data.tripadvisor_id;
1449        var key = 'da12eab3768f454e8e3caacebf19b779';
1450        $.ajax({
1451            url: "https://api.tripadvisor.com/api/partner/2.0/location/" + id + "?key=" + key,
1452            dataType: 'json',
1453            success: function (response) {
1454                // Show TripAdvisor tab
1455                $('#tripadvisor-info').css({
1456                    'visibility': 'visible'
1457                }).addClass('ready');
1458
1459                // Show TripAdvisor in footer
1460                showTripAdvisorFooter();
1461
1462                // Set content
1463                setTripAdvisorContent(response, data);
1464
1465                // Load TripAdvisor Comments
1466                setGesArray(response);
1467            }
1468        })
1469            .fail(function () {
1470                $('#interior-explore .tab').css('top', 165 + 'px');
1471                $('#tripadvisor-info').hide();
1472            });
1473
1474    }
1475
1476    function showTripAdvisorFooter() {
1477        var ta = $('<div>').attr({
1478            class: 'sprite tripadvisor'
1479        }).insertAfter('#footer .copyright');
1480    }
1481
1482    function setTripAdvisorContent(ta, data) {
1483
1484        // Ranking data
1485        if (ta.ranking_data !== null) {
1486
1487            // Rank string
1488            $('<h4>').attr({
1489                id: 'ta-ranking-string'
1490            }).html(ta.ranking_data.ranking_string.replace(/(\#\d{1,4}
1490?\s)/g, '<span>$1</span>')).insertBefore('#ta-title');
1491
1492            // Based on # of reviews
1493            $('<a>').attr({
1494                href: ta.web_url,
1495                target: '_blank',
1496                id: 'ta-num-reviews'
1497            }).html('based on ' + ta.num_reviews + ' reviews').insertAfter('#ta-title');
1498
1499            // Image rating
1500            $('<img>').attr({
1501                id: '#ta-rating-src',
1502                src: ta.rating_image_url,
1503                alt: 'TripAdvisor Rating'
1504            }).insertAfter('#ta-title');
1505
1506            // Read a review link
1507            $('#tripadvisor-read-review').attr("href", ta.web_url);
1508
1509        }
1510
1511        // Link to page url and display lightbox write a review
1512        if (data.tripadvisor_has_review) {
1513            $('#tripadvisor-write-review').addClass('open-lightbox');
1514        } else {
1515            $('#tripadvisor-write-review').attr('href', ta.write_review);
1516        }
1517
1518        // Awards
1519        if ($(ta.awards).length > 0) {
1520            var awards = '<ul>';
1521            $.each(ta.awards, function (i, v) {
1522                awards += '<li><img src="' + v.images.large + '" alt="' + v.display_name + '" /><span>' + v.display_name + '</span></li>';
1523            });
1524            awards += '</ul>';
1525            $('#ta-awards').append(awards).show();
1526        }
1527
1528    }
1529
1530// --------------------------------------------------------------------------
1531// --------------------------- Google Places API - Explore and Location Maps
1532// --------------------------------------------------------------------------
1533
1534    var googleMap = (function () {
1535
1536        var geocoder;
1537        var map;
1538        var mapMarkers = [];
1539        var tinyIcon;
1540        var defaultIcon;
1541        var selectedIcon;
1542        var propertyIcon;
1543        var defaultPropertyIcon;
1544        var zoomLevelDefaultIcon;
1545        var selectedMarker;
1546        var propertyMarker;
1547        var currentService;
1548        var mapContainer;
1549        var mapPanel;
1550        var mapData;
1551        var propertyData;
1552        var propertiesList;
1553        var metaData;
1554        var templateDir;
1555        var mapOptions;
1556        var mapType;
1557        var latLng;
1558        var bounds;
1559        var currentCenter;
1560        var newOffsetCenter;
1561        var initialSearch = true;
1562        var working = false;
1563        var isLoaded = false;
1564        var resizeTimer;
1565
1566        // For Google Places API Bug
1567        var placesListCheck = [];
1568
1569        // Map Styles
1570        var mapStyles = [{
1571            "featureType": "administrative",
1572            "elementType": "geometry.fill",
1573            "stylers": [{"color": "#727545"}]
1574        }, {
1575            "featureType": "administrative",
1576            "elementType": "geometry.stroke",
1577            "stylers": [{"color": "#454f4b"}]
1578        }, {
1579            "featureType": "administrative.country",
1580            "elementType": "labels.text.fill",
1581            "stylers": [{"color": "#e3e4d3"}]
1582        }, {
1583            "featureType": "administrative.country",
1584            "elementType": "labels.text.stroke",
1585            "stylers": [{"color": "#75684d"}]
1586        }, {
1587            "featureType": "administrative.province",
1588            "elementType": "labels.text.fill",
1589            "stylers": [{"color": "#dbdcc8"}]
1590        }, {
1591            "featureType": "administrative.province",
1592            "elementType": "labels.text.stroke",
1593            "stylers": [{"color": "#858b64"}, {"weight": "2.5"}]
1594        }, {
1595            "featureType": "administrative.locality",
1596            "elementType": "labels.text.fill",
1597            "stylers": [{"color": "#ffffff"}]
1598        }, {
1599            "featureType": "administrative.locality",
1600            "elementType": "labels.text.stroke",
1601            "stylers": [{"color": "#767762"}, {"weight": "1.5"}]
1602        }, {
1603            "featureType": "administrative.neighborhood",
1604            "elementType": "labels.text.fill",
1605            "stylers": [{"visibility": "off"}]
1606        }, {
1607            "featureType": "landscape",
1608            "elementType": "geometry.fill",
1609            "stylers": [{"color": "#9fa274"}]
1610        }, {
1611            "featureType": "poi",
1612            "elementType": "geometry.fill",
1613            "stylers": [{"color": "#8e9160"}]
1614        }, {"featureType": "poi", "elementType": "labels", "stylers": [{"visibility": "off"}]}, {
1615            "featureType": "road",
1616            "elementType": "geometry.fill",
1617            "stylers": [{"color": "#8d946d"}]
1618        }, {
1619            "featureType": "road",
1620            "elementType": "geometry.stroke",
1621            "stylers": [{"color": "#848a64"}]
1622        }, {
1623            "featureType": "road",
1624            "elementType": "labels.text.fill",
1625            "stylers": [{"color": "#dbdcc8"}]
1626        }, {
1627            "featureType": "road",
1628            "elementType": "labels.text.stroke",
1629            "stylers": [{"color": "#778164"}]
1630        }, {
1631            "featureType": "road",
1632            "elementType": "labels.icon",
1633            "stylers": [{"hue": "#0090ff"}, {"saturation": "100"}, {"gamma": "0.37"}]
1634        }, {
1635            "featureType": "road.highway",
1636            "elementType": "geometry.fill",
1637            "stylers": [{"color": "#83765b"}]
1638        }, {
1639            "featureType": "road.highway",
1640            "elementType": "geometry.stroke",
1641            "stylers": [{"color": "#75684d"}]
1642        }, {
1643            "featureType": "road.highway",
1644            "elementType": "labels.text.fill",
1645            "stylers": [{"color": "#ffffff"}]
1646        }, {
1647            "featureType": "road.highway",
1648            "elementType": "labels.text.stroke",
1649            "stylers": [{"color": "#64573c"}]
1650        }, {
1651            "featureType": "road.arterial",
1652            "elementType": "geometry.fill",
1653            "stylers": [{"color": "#93876e"}]
1654        }, {
1655            "featureType": "road.arterial",
1656            "elementType": "geometry.stroke",
1657            "stylers": [{"color": "#8c8066"}]
1658        }, {
1659            "featureType": "road.arterial",
1660            "elementType": "labels.text.fill",
1661            "stylers": [{"color": "#dbdcc8"}]
1662        }, {
1663            "featureType": "road.arterial",
1664            "elementType": "labels.text.stroke",
1665            "stylers": [{"color": "#6d7759"}, {"weight": "2.5"}]
1666        }, {
1667            "featureType": "road.local",
1668            "elementType": "geometry.fill",
1669            "stylers": [{"color": "#8c926c"}]
1670        }, {
1671            "featureType": "road.local",
1672            "elementType": "geometry.stroke",
1673            "stylers": [{"color": "#7a805b"}]
1674        }, {
1675            "featureType": "road.local",
1676            "elementType": "labels.icon",
1677            "stylers": [{"visibility": "off"}]
1678        }, {
1679            "featureType": "transit",
1680            "elementType": "geometry.fill",
1681            "stylers": [{"color": "#8b8d83"}]
1682        }, {
1683            "featureType": "transit",
1684            "elementType": "labels.icon",
1685            "stylers": [{"visibility": "off"}]
1686        }, {
1687            "featureType": "water",
1688            "elementType": "geometry.fill",
1689            "stylers": [{"color": "#011f36"}]
1690        }, {"featureType": "water", "elementType": "labels.text.stroke", "stylers": [{"visibility": "off"}]}];
1691
1692        // Loading
1693        function loading(msg, show, hide, targetLoader) {
1694
1695            // Optional Params
1696            msg = msg;
1697            if (typeof msg === 'undefined') {
1698                msg = '';
1699            }
1700
1701            show = show;
1702            if (typeof show === 'undefined') {
1703                show = false;
1704            }
1705
1706            hide = hide;
1707            if (typeof hide === 'undefined') {
1708                hide = false;
1709            }
1710
1711            var loader = targetLoader;
1712            if (typeof loader === 'undefined') {
1713                loader = $('#map-loader');
1714            }
1715
1716            // Update loading message
1717            if (msg !== '') {
1718                // Update loading message
1719                loader.find('.text').text(msg);
1720            }
1721
1722            // Show Loader
1723            if (show === true) {
1724                loader.show();
1725            }
1726
1727            if (hide === true) {
1728                loader.hide();
1729            }
1730
1731        }
1732
1733        // Initialize Explore Map
1734        function initExplore(data) {
1735
1736            // Set type
1737            mapType = 'explore';
1738
1739            // Set container
1740            mapContainer = $('#interior-explore');
1741
1742            // Set Panel
1743            mapPanel = mapContainer.find('.places-container');
1744
1745            // Set mapData
1746            mapData = data.map;
1747            if (typeof mapData === 'undefined') {
1748                mapData = '';
1749            }
1750
1751            // Set propertyData
1752            propertyData = data.property;
1753            if (typeof propertyData === 'undefined') {
1754                propertyData = '';
1755            }
1756
1757            // Set metaData
1758            metaData = data.meta;
1759            if (typeof metaData === 'undefined') {
1760                metaData = '';
1761            }
1762
1763            // Set templateDir
1764            templateDir = data.templateDir;
1765            if (typeof templateDir === 'undefined') {
1766                templateDir = '';
1767            }
1768
1769            // Get initial LatLng
1770            if (typeof mapData.latitude !== 'undefined' && typeof mapData.longitude !== 'undefined') {
1771                latLng = initLatLng(mapData.latitude, mapData.longitude);
1772            } else {
1773                latLng = initLatLng();
1774            }
1775
1776            // Set Options
1777            mapOptions = {
1778                zoom: 15,
1779                maxZoom: 18,
1780                minZoom: 6,
1781                center: latLng,
1782                backgroundColor: '#011f36',
1783                mapTypeId: google.maps.MapTypeId.ROADMAP,
1784                disableDefaultUI: false,
1785                scrollwheel: true,
1786                mapTypeControl: false,
1787                panControl: false,
1788                zoomControl: true,
1789                zoomControlOptions: {
1790                    style: google.maps.ZoomControlStyle.DEFAULT,
1791                    position: google.maps.ControlPosition.RIGHT_BOTTOM
1792                },
1793                scaleControl: true,  // fixed to BOTTOM_RIGHT
1794                streetViewControl: false,
1795                streetViewControlOptions: {
1796                    position: google.maps.ControlPosition.RIGHT_TOP
1797                },
1798                styles: mapStyles
1799            };
1800
1801            // Open/Initialize Map
1802            openMap(init(mapOptions));
1803
1804        }
1805
1806        // Initialize Find a Cobblestone Map
1807        function initFind() {
1808
1809            // Set type
1810            mapType = 'find';
1811
1812            // Set container
1813            mapContainer = $('#interior-find');
1814            if (typeof jsData !== 'undefined' && jsData.isMobile === 'true') {
1815                mapContainer.addClass('is-mobile');
1816            }
1817
1818            // Set Panel
1819            mapPanel = mapContainer.find('.places-container');
1820
1821            // Set templateDir
1822            templateDir = jsData.templateDir;
1823            if (typeof templateDir === 'undefined') {
1824                templateDir = '';
1825            }
1826
1827
1828            latLng = initLatLng();
1829
1830            // Set Options
1831            mapOptions = {
1832                zoom: 5,
1833                maxZoom: 18,
1834                minZoom: 5,
1835                center: latLng,
1836                backgroundColor: '#011f36',
1837                mapTypeId: google.maps.MapTypeId.ROADMAP,
1838                disableDefaultUI: false,
1839                scrollwheel: true,
1840                mapTypeControl: false,
1841                panControl: false,
1842                zoomControl: true,
1843                zoomControlOptions: {
1844                    style: google.maps.ZoomControlStyle.DEFAULT,
1845                    position: google.maps.ControlPosition.RIGHT_BOTTOM
1846                },
1847                scaleControl: true,  // fixed to BOTTOM_RIGHT
1848                streetViewControl: false,
1849                streetViewControlOptions: {
1850                    position: google.maps.ControlPosition.RIGHT_TOP
1851                },
1852                styles: mapStyles
1853            };
1854
1855            // Initialize Map
1856            init(mapOptions);
1857            loading('Loading Properties...', true, '', $('#property-list-loader'));
1858
1859        }
1860
1861        function getPropertiesForMap() {
1862            $.ajax({
1863                url: jsData.ajaxURL,
1864                type: 'POST',
1865                data: {
1866                    action: 'get_properties_for_map'
1867                },
1868                dataType: 'json',
1869                async: true
1870            })
1871                .success(function (response) {
1872                    if (!response.error) {
1873                        // Build Markers/Places
1874                        buildMarkersCustom(response);
1875                        loading('', '', true, $('#property-list-loader'));
1876
1877                    }
1878                })
1879                .fail(function () {
1880                });
1881        }
1882
1883        // LatLng
1884        function initLatLng(lat, lng) {
1885            // const { Map } = await google.maps.importLibrary("maps");
1886            // Geocoding converts a string address into coordiantes (lat/lng)
1887            geocoder = new google.maps.Geocoder();
1888
1889            lat = lat;
1890            if (typeof lat === 'undefined') {
1891                lat = '';
1892            }
1893
1894            lng = lng;
1895            if (typeof lng === 'undefined') {
1896                lng = '';
1897            }
1898
1899            var _latLng;
1900
1901            // Set intital position
1902            if (lat !== '' && lng !== '') {
1903                _latLng = new google.maps.LatLng(lat, lng);
1904            } else {
1905                // North America
1906                _latLng = new google.maps.LatLng(37.09024, -95.712891);
1907            }
1908
1909            return _latLng;
1910
1911        }
1912
1913        // Map Container
1914        function openMap(callback) {
1915            // Add class open
1916            mapContainer
1917                .addClass('open')
1918                .one(whichTransitionEvent(), function (event) {
1919
1920                    if (mapContainer.hasClass('loaded')) {
1921                        openPanel();
1922                    }
1923
1924                    // Optional callback function
1925                    if (typeof callback === 'function' && callback()) {
1926                        callback();
1927                    }
1928
1929                });
1930        }
1931
1932        function mapLoaded() {
1933            isLoaded = true;
1934        }
1935
1936        function isMapLoaded() {
1937            return isLoaded;
1938        }
1939
1940        function closeMap(callback) {
1941            closePanel(function () {
1942                mapContainer
1943                    .removeClass('open')
1944                    .one(whichTransitionEvent(), function (event) {
1945
1946                        // Optional callback function
1947                        if (typeof callback === 'function' && callback()) {
1948                            callback();
1949                        }
1950                    });
1951            });
1952        }
1953
1954        // Panel
1955        function openPanel(callback) {
1956
1957            var $placesList = mapPanel.find('.places-list');
1958
1959            mapPanel
1960                .addClass('open')
1961                .one(whichTransitionEvent(), function (event) {
1962
1963                    // Add ready to places list
1964                    $placesList.addClass('ready');
1965
1966                    // Optional callback function
1967                    if (typeof callback === 'function' && callback()) {
1968                        callback();
1969                    }
1970
1971                });
1972
1973            updateListHeight();
1974
1975            // Sometimes TransitionEnd fails when cached
1976            // hacky solution to display content if it fails
1977            setTimeout(function () {
1978                if (!$placesList.hasClass('ready')) {
1979                    $placesList.addClass('ready');
1980                    updateListHeight();
1981                    console.log('transition end failed');
1982                }
1983            }, 1000);
1984
1985        }
1986
1987        function cloneGoogleLogo() {
1988
1989            setTimeout(function () {
1990
1991                var logo = $('a[title="Click to see this area on Google Maps"]').closest('div').clone();
1992
1993                if (logo.length !== 0) {
1994
1995                    if (logo.find('img').attr('src').indexOf('transparent.png') === -1) {
1996                        $('[title="Click to see this area on Google Maps"]').hide();
1997                        logo.addClass('google-logo').appendTo(mapPanel);
1998                    }
1999
2000                }
2001
2002            }, 1000);
2003
2004        }
2005
2006        function closePanel(callback) {
2007
2008            mapPanel
2009                .removeClass('open')
2010                .one(whichTransitionEvent(), function (event) {
2011
2012                    // Optional callback function
2013                    if (typeof callback === 'function' && callback()) {
2014                        callback();
2015                    }
2016
2017                });
2018
2019        }
2020
2021        function updateMapHeight() {
2022
2023            var windowBottom = $(window).height();
2024
2025            var $pageHeader = $('.default-navigation');
2026
2027            var $map = $('#interior-find');
2028
2029            var $pageFooter = $('#footer');
2030
2031            var newHeight = windowBottom - ($map.offset().top - $(window).scrollTop()) - $pageFooter.outerHeight();
2032
2033            $map.height(newHeight);
2034
2035        }
2036
2037        function updateListHeight() {
2038
2039            var windowBottom = $(window).height();
2040
2041            var list = '';
2042            var bottomOffset = 50;
2043            if (mapType === 'explore') {
2044
2045                list = $('#interior-explore .places-list');
2046
2047            } else if (mapType === 'find') {
2048
2049                list = $('#interior-find .places-list');
2050
2051                if (mapContainer.hasClass('is-mobile')) {
2052                    bottomOffset = $('#footer').outerHeight();
2053                } else {
2054                    bottomOffset = $('#footer').outerHeight() + 35;
2055                }
2056            }
2057
2058            var newHeight = windowBottom - (list.offset().top - $(window).scrollTop()) - bottomOffset;
2059
2060            list.height(newHeight);
2061
2062            if ($('#interior-explore .scroll-container').length) {
2063
2064                var details = $('#interior-explore .scroll-container');
2065
2066                var alsoNewHeight = windowBottom - (details.offset().top - $(window).scrollTop()) - bottomOffset;
2067
2068                details.height(alsoNewHeight);
2069            }
2070
2071        }
2072
2073        $(window).on('resize', function () {
2074
2075            clearTimeout(resizeTimer);
2076            resizeTimer = setTimeout(function () {
2077
2078                // Update height of places list
2079                if (typeof mapContainer !== 'undefined') {
2080                    if (mapType === 'explore') {
2081
2082                    } else if (mapType === 'find') {
2083                        updateMapHeight();
2084                    }
2085
2086                    if (mapPanel.hasClass('open')) {
2087                        updateListHeight();
2088                    }
2089                    centerOnMarker();
2090                }
2091
2092            }, 100);
2093
2094        });
2095
2096        // Initialize/Reinit
2097        async function init(mapOptions) {
2098            const { Map } = await google.maps.importLibrary("maps");
2099            // Create map
2100            map = new google.maps.Map($('.map-canvas')[0], mapOptions);
2101
2102            // Service we're using
2103            currentService = new google.maps.places.PlacesService(map);
2104
2105            // Update loading message
2106            loading('Initializing...');
2107
2108            // Event listener - idle singles the map is loaded
2109            google.maps.event.addListenerOnce(map, 'idle', function () {
2110
2111                loading('Loading Map...');
2112
2113                if (mapType === 'explore') {
2114
2115                    setMainMarker(function () {
2116
2117                        // Initial query
2118                        searchMap('', 'general', 'distance');
2119
2120                    });
2121
2122                } else if (mapType === 'find') {
2123
2124                    // Get properties for map
2125                    getPropertiesForMap();
2126
2127                }
2128
2129            });
2130
2131            // Event listener - tilesloaded signals that the maps fully loaded
2132            google.maps.event.addListenerOnce(map, 'tilesloaded', function () {
2133
2134                // Trigger mapLoaded event
2135                $(document).trigger('mapLoaded');
2136
2137            });
2138
2139            // Event listener - Restrict Panning
2140            var allowedBounds = new google.maps.LatLngBounds(
2141                new google.maps.LatLng(28.70, -127.50),
2142                new google.maps.LatLng(48.85, -55.90)
2143            );
2144
2145            var lastValidCenter = map.getCenter();
2146
2147            google.maps.event.addListener(map, 'center_changed', function () {
2148                if (allowedBounds.contains(map.getCenter())) {
2149                    // still within valid bounds, so save the last valid position
2150                    lastValidCenter = map.getCenter();
2151                    currentCenter = lastValidCenter;
2152                    return;
2153                }
2154
2155                // not valid anymore then return to last valid position
2156                map.panTo(lastValidCenter);
2157            });
2158
2159            // Event listener - On zoom change
2160            google.maps.event.addListener(map, 'zoom_changed', function () {
2161                if (mapType === 'find') {
2162                    updateMarkerIcons();
2163                }
2164            });
2165
2166            // Create global icons
2167            tinyIcon = {
2168                url: templateDir + '/images/site/map-icon-tiny.png',
2169                size: new google.maps.Size(8, 8),
2170                anchor: new google.maps.Point(4, 4)
2171            };
2172
2173            defaultIcon = {
2174                url: templateDir + '/images/site/map-icon-dot.png',
2175                size: new google.maps.Size(17, 16),
2176                anchor: new google.maps.Point(8, 8)
2177            };
2178
2179            selectedIcon = {
2180                url: templateDir + '/images/site/map-icon-place.png',
2181                size: new google.maps.Size(30, 56),
2182                anchor: new google.maps.Point(15, 47)
2183            };
2184
2185            defaultPropertyIcon = {
2186                url: templateDir + '/images/site/map-icon-cobblestone-default.png',
2187                size: new google.maps.Size(54, 84),
2188                anchor: new google.maps.Point(27, 72)
2189            };
2190
2191            propertyIcon = {
2192                url: templateDir + '/images/site/map-icon-cobblestone.png',
2193                size: new google.maps.Size(54, 84),
2194                anchor: new google.maps.Point(27, 72)
2195            };
2196
2197        }
2198
2199        function reinit() {
2200            // redraw map
2201            google.maps.event.trigger(map, 'resize');
2202
2203            if (mapType === 'explore') {
2204
2205                // recenter
2206                centerOnMarker();
2207
2208            } else if (mapType === 'find') {
2209
2210            }
2211        }
2212
2213        function offsetCenter(marker) {
2214
2215            // latlng is the apparent centre-point
2216            // offsetx is the distance you want that point to move to the right, in pixels
2217            // offsety is the distance you want that point to move upwards, in pixels
2218            // offset can be negative
2219            // offsetx and offsety are both optional
2220
2221            var markerLatlng;
2222            var mapLatlng = map.getCenter();
2223            if (typeof (marker) === 'undefined' && typeof (selectedMarker) === 'undefined') {
2224                markerLatlng = map.getCenter();
2225            } else if (typeof (marker) === 'undefined' && typeof (selectedMarker) !== 'undefined') {
2226                markerLatlng = selectedMarker.position;
2227            } else {
2228                markerLatlng = marker.position;
2229            }
2230
2231            var scale = Math.pow(2, map.getZoom());
2232            var nw = new google.maps.LatLng(
2233                map.getBounds().getNorthEast().lat(),
2234                map.getBounds().getSouthWest().lng()
2235            );
2236
2237            var worldCoordinateNW = map.getProjection().fromLatLngToPoint(nw);
2238            var worldCoordinate = map.getProjection().fromLatLngToPoint(markerLatlng);
2239            var pixelOffset = new google.maps.Point(
2240                Math.floor((worldCoordinate.x - worldCoordinateNW.x) * scale),
2241                Math.floor((worldCoordinate.y - worldCoordinateNW.y) * scale)
2242            );
2243
2244            var infoWidth = 200;
2245
2246            var offsetx = 0;
2247            // pixelOffset.x is the distance from the left side of the window
2248            // we -200 to compensate for when the infowindow is open
2249            if ((pixelOffset.x - infoWidth) < mapPanel.outerWidth()) {
2250
2251                // Below we get the offset - the width of the panel, this should give us the amount over or under the overlay.
2252                offsetx = (pixelOffset.x - mapPanel.outerWidth());
2253                // If offsetx is less than 0, multiply it by -1 otherwise leave it. We want a positive number though.
2254                offsetx = offsetx < 0 ? offsetx * -1 : offsetx;
2255                // Add 200 to the offset to accommidate the infowindow
2256                offsetx += infoWidth;
2257
2258                // If the pixelOffset is bigger than the window
2259            } else if (pixelOffset.x > $(window).innerWidth()) {
2260
2261                // Subtract the window size from the offset, add the panel width
2262                offsetx = (pixelOffset.x - $(window).innerWidth()) + (mapPanel.outerWidth());
2263                // subtract the infowindow margin
2264                offsetx -= infoWidth;
2265                // We want a negative number to move the center left
2266                offsetx = offsetx < 0 ? offsetx : offsetx * -1;
2267            }
2268
2269            var infoHeight = 250;
2270
2271            var offsety = 0;
2272            if (pixelOffset.y < infoHeight) {
2273                offsety = (infoHeight * -1) + pixelOffset.y;
2274            } else if (pixelOffset.y > $(window).height()) {
2275                offsety = (pixelOffset.y - $(window).innerHeight()) + infoHeight * 0.5;
2276                offsety = offsety * 1;
2277            }
2278
2279            var worldCoordinateCenter = map.getProjection().fromLatLngToPoint(mapLatlng);
2280            var newOffset = new google.maps.Point((offsetx / scale), (offsety / scale));
2281
2282            var worldCoordinateNewCenter = new google.maps.Point(
2283                worldCoordinateCenter.x - newOffset.x,
2284                worldCoordinateCenter.y + newOffset.y
2285            );
2286
2287            var newCenter = map.getProjection().fromPointToLatLng(worldCoordinateNewCenter);
2288
2289            map.setCenter(newCenter);
2290        }
2291
2292        function offsetMap() {
2293
2294            var overlayWidth = mapPanel.outerWidth(); // Width of the overlay DIV
2295            var leftMargin = 25; // Grace margin to avoid too close fits on the edge of the overlay
2296            var rightMargin = 25; // Grace margin to avoid too close fits on the right and leave space for the controls
2297
2298            if (typeof bounds !== 'undefined' && bounds !== false) {
2299
2300                // Top right corner
2301                var topRightCorner = new google.maps.LatLng(map.getBounds().getNorthEast().lat(), map.getBounds().getNorthEast().lng());
2302
2303                // Top right point
2304                var topRightPoint = fromLatLngToPoint(topRightCorner).x;
2305
2306                // Get pixel position of leftmost and rightmost points
2307                var leftCoords = bounds.getSouthWest();
2308                var leftMost = fromLatLngToPoint(leftCoords).x;
2309                var rightMost = fromLatLngToPoint(bounds.getNorthEast()).x;
2310
2311                // Calculate left and right offsets
2312                var leftOffset = (overlayWidth - leftMost);
2313                var rightOffset = ((topRightPoint - rightMargin) - rightMost);
2314
2315                // Only if left offset is needed
2316                if (leftOffset >= 0) {
2317
2318                    if (leftOffset < rightOffset) {
2319
2320                        var mapOffset = Math.round((rightOffset - leftOffset) / 2);
2321
2322                        // Pan the map by the offset calculated on the x axis
2323                        map.panBy(-mapOffset, 0);
2324
2325                        // Get the new left point after pan
2326                        var newLeftPoint = fromLatLngToPoint(leftCoords).x;
2327
2328                        if (newLeftPoint <= overlayWidth) {
2329
2330                            // Leftmost point is still under the overlay
2331                            // Offset map again
2332                            offsetMap();
2333                        }
2334
2335                    } else {
2336
2337                        // Cannot offset map at this zoom level otherwise both leftmost and rightmost points will not fit
2338                        // Zoom out and offset map again
2339                        map.setZoom(map.getZoom() - 1);
2340                        offsetMap();
2341                    }
2342                }
2343            }
2344        }
2345
2346        function fromLatLngToPoint(latLng, offsetx, offsety) {
2347
2348            var scale = Math.pow(2, map.getZoom());
2349            var nw = new google.maps.LatLng(map.getBounds().getNorthEast().lat(), map.getBounds().getSouthWest
2349().lng());
2350            var worldCoordinateNW = map.getProjection().fromLatLngToPoint(nw);
2351            var worldCoordinate = map.getProjection().fromLatLngToPoint(latLng);
2352
2353            var pixelOffset = new google.maps.Point((offsetx / scale) || 0, (offsety / scale) || 0);
2354
2355            var worldCoordinateNewCenter = new google.maps.Point(
2356                Math.floor((worldCoordinate.x - worldCoordinateNW.x) * scale) - pixelOffset.x,
2357                Math.floor((worldCoordinate.y - worldCoordinateNW.y) * scale) + pixelOffset.y
2358            );
2359
2360            return worldCoordinateNewCenter;
2361        }
2362
2363        function fitAndOffsetMap() {
2364
2365            if (bounds !== false) {
2366
2367                map.fitBounds(bounds);
2368                offsetMap();
2369            }
2370        }
2371
2372        // Search - This function accepts several optional parameters to help customize the search
2373        // keywords expects a string
2374        // types can be a string or an array
2375        // blockTypes can be an array or a boolean (indicating weather or not to use the default)
2376        function searchMap(keywords, types, rankBy, blockTypes) {
2377
2378            // keywords = keywords;
2379            if (typeof (keywords) === 'undefined') {
2380                keywords = '';
2381            }
2382
2383            // console.log(types);
2384            types = types;
2385
2386            if (keywords === '' && typeof (types) === 'undefined') {
2387                types = ['point_of_interest'];
2388            }
2389
2390            //rankBy = rankBy;
2391            if (typeof (rankBy) === 'undefined') {
2392                rankBy = 'distance';
2393            }
2394
2395            // Places request
2396            var request = {
2397                location: propertyMarker.position,
2398            };
2399
2400            if (rankBy === 'distance') {
2401                request.rankBy = google.maps.places.RankBy.DISTANCE;
2402
2403            } else if (rankBy === 'prominence') {
2404                request.rankBy = google.maps.places.RankBy.PROMINENCE;
2405                // this radius stipulation led to an error in combination with another bug
2406                request.radius = 30000;
2407            }
2408
2409            //this was altered to fix an issue with places API 7.27.2025
2410            if (keywords !== '') {
2411                request.keyword = keywords;
2412            } else {
2413                request.types = [types];
2414            }
2415
2416            // Clear places list and overlays (the true tells it to also clear the array of markers)
2417            clearPlacesList();
2418            clearOverlays(true);
2419            clearInfos();
2420
2421            // For Google Places API Bug
2422            placesListCheck.length = 0;
2423
2424            console.log(request);
2425
2426            // Send request
2427            currentService.nearbySearch(request, buildMarkers(true, blockTypes));
2428
2429        }
2430
2431        // Marker
2432        function setMainMarker(callback) {
2433            // Add new custom marker
2434            propertyMarker = new google.maps.Marker({
2435                map: map,
2436                icon: propertyIcon,
2437                required: true,
2438                position: latLng
2439            });
2440
2441            var content = getPropertyHTML();
2442
2443            // Build property infowindow
2444            propertyMarker.info = new google.maps.InfoWindow({
2445                content: content.clone()[0]
2446            });
2447
2448            mapMarkers.push(propertyMarker);
2449
2450            // Bounce marker
2451            bounceMarker(propertyMarker, true);
2452            // Event listener - Waits for the creation of the infowindow html structure
2453            google.maps.event.addListener(propertyMarker.info, 'click', function () {
2454                //todo: this was DOMContentLoaded... but that does not exist in that listener
2455                infoWindowReady();
2456            });
2457
2458            // Event listener - Add event handler to current marker
2459            google.maps.event.addListener(propertyMarker, 'click', function () {
2460                clickPlaceMarker(this);
2461            });
2462
2463            // Optional callback function
2464            if (typeof callback === 'function' && callback()) {
2465                callback();
2466            }
2467
2468        }
2469
2470        function infoWindowReady() {
2471            $('.gm-style-iw').parent().hide().addClass('iw-custom').show();
2472        }
2473
2474        function bounceMarker(marker, openInfo) {
2475
2476            // This function previously bounced the marker
2477            // It now just displays it
2478
2479            marker = marker;
2480            if (typeof marker === 'undefined') {
2481                marker = propertyMarker;
2482            }
2483
2484            openInfo = openInfo;
2485            if (typeof openInfo === 'undefined') {
2486                openInfo = false;
2487            }
2488
2489            marker.info.open(map, propertyMarker);
2490
2491        }
2492
2493        function centerOnMarker(marker) {
2494            marker = marker;
2495            if (typeof marker === 'undefined') {
2496                if (typeof (selectedMarker) !== 'undefined') {
2497                    marker = selectedMarker;
2498                } else {
2499                    marker = propertyMarker;
2500                }
2501            }
2502            offsetCenter(marker);
2503            //map.setCenter(marker.position);
2504        }
2505
2506        function fitMarkers(markers) {
2507
2508            markers = markers;
2509            if (typeof markers === 'undefined') {
2510                markers = mapMarkers;
2511            }
2512            var i = 0;
2513            for (i; i < markers.length; i++) {
2514                bounds.extend(markers[i].getPosition());
2515            }
2516            bounds.extend(propertyMarker.position);
2517
2518            map.fitBounds(bounds);
2519        }
2520
2521        function clearOverlays(clearMarkerArray) {
2522
2523            // Optional param to not just clear from view, but also empty array
2524            clearMarkerArray = clearMarkerArray;
2525            if (typeof clearMarkerArray === 'undefined') {
2526                clearMarkerArray = false;
2527            }
2528
2529            clearMarkers();
2530            //clearInfos();
2531
2532            if (clearMarkerArray === true) {
2533                emptyMarkers();
2534            }
2535        }
2536
2537        function clearMarkers() {
2538            if (mapMarkers.length > 0) {
2539                $.each(mapMarkers, function (i, marker) {
2540                    if (marker.required !== true) {
2541                        marker.setMap(null);
2542                    }
2543                });
2544            }
2545        }
2546
2547        function clearInfos() {
2548            if (mapMarkers.length >= 0) {
2549                $.each(mapMarkers, function (i, marker) {
2550                    if (marker.info.getMap()) {
2551                        marker.info.close();
2552                    }
2553                });
2554            }
2555
2556            if (mapType === 'explore') {
2557                if (propertyMarker.info.getMap()) {
2558                    propertyMarker.info.close();
2559                }
2560            }
2561        }
2562
2563        function emptyMarkers() {
2564            mapMarkers = [];
2565        }
2566
2567        function buildMarkersCustom(data) {
2568            // Create list for panel
2569            // This function anticipates a big array of states and properties
2570            $.each(data, function (i, state) {
2571                $.each(state.info, function (abbr, fullState) {
2572
2573                    // Main ul for the menu
2574                    var ul = $('<ul>').attr({
2575                        class: 'state',
2576                        id: cleanTitle(fullState)
2577                    });
2578
2579                    // li for state title
2580                    var name = $('<li>').attr({
2581                        class: 'state-title cobble-font',
2582                        id: cleanTitle(fullState + '_title')
2583                    }).text(fullState).appendTo(ul);
2584
2585                    // Sub menu contains properties
2586                    var subMenu = $('<ul>').attr({
2587                        class: 'sub-menu'
2588                    }).appendTo(ul);
2589
2590                    // Append State
2591                    mapPanel.find('.places-list').append(ul);
2592
2593                    $.each(state.properties, function (city, info) {
2594                        createPlace(info);
2595                    });
2596
2597                    document.getElementById(cleanTitle(cleanTitle(fullState) + '_title')).addEventListener('click', function () {
2598                        if ($(this).closest('.state').find('.sub-menu:visible').length) {
2599                            handleFindList(ul[0]);
2600                        } else {
2601                            var location = fullState + ', United States';
2602                            geocoder.geocode({'address': location}, function (results, status) {
2603                                if (status === google.maps.GeocoderStatus.OK) {
2604
2605                                    map.setZoom(7);
2606                                    if (map.getCenter().equals(results[0].geometry.location)) {
2607                                        handleFindList(ul[0]);
2608                                    } else {
2609                                        map.setCenter(results[0].geometry.location);
2610                                        google.maps.event.addListenerOnce(map, 'idle', function () {
2611                                            handleFindList(ul[0]);
2612                                        });
2613                                    }
2614                                } else {
2615                                    console.log("Could not find location: " + location);
2616                                }
2617                            });
2618                        }
2619                    });
2620                });
2621            });
2622            openPanel(updateMapHeight());
2623        }
2624
2625        function handleFindList(target, closeCurrent) {
2626
2627            closeCurrent = (typeof closeCurrent === 'undefined') ? true : closeCurrent;
2628
2629            if (typeof target === 'string' || target instanceof String) {
2630                target = $('#find-a-cobblestone').find('#' + target);
2631            } else {
2632                target = $(target)[0];
2633                if (target.id.indexOf('_marker') > -1) {
2634                    target.id = target.id.replace('_marker', '_listing');
2635                }
2636            }
2637
2638            if (target instanceof jQuery === false) {
2639                target = $('#' + target.id)[0];
2640            }
2641
2642            //todo this is being thrown as the listener is not attached to the city button...
2643            var sameAsTarget = ($(target).closest('.state').attr('id') === $('#find-a-cobblestone ul').find('.sub-menu:visible').parent().attr('id'));
2644
2645            var subMenuOpen = ($(target).closest('.state').find('.sub-menu:visible').length) ? true : false;
2646
2647            if (closeCurrent || !sameAsTarget) {
2648                $('#find-a-cobblestone ul').find('.sub-menu:visible').stop().slideToggle(300);
2649            }
2650
2651            if (!sameAsTarget || subMenuOpen) {
2652                if (!subMenuOpen) {
2653                    $(target).closest('.state').find('.sub-menu').stop().slideToggle(300, function () {
2654                        var newTop = $('#find-a-cobblestone').scrollTop() - $('#find-a-cobblestone').offset().top + $(target).offset().top;
2655                        $('#find-a-cobblestone').finish().animate({scrollTop: newTop});
2656                    });
2657                } else {
2658
2659                    var newTop = $('#find-a-cobblestone').scrollTop() - $('#find-a-cobblestone').offset().top + $(target).offset().top;
2660                    $('#find-a-cobblestone').finish().animate({scrollTop: newTop});
2661                }
2662
2663            }
2664        }
2665
2666        function buildMarkers(display, blockTypes) {
2667
2668            // Show search form loader
2669            showSearchLoader();
2670
2671            blockTypes = blockTypes;
2672            if (typeof blockTypes === 'undefined') {
2673                blockTypes = '';
2674            }
2675
2676            var initialDisplay = display;
2677            if (typeof initialDisplay === 'undefined') {
2678                initialDisplay = true;
2679            }
2680
2681            return function (results, status, pagination) {
2682
2683                if (status === google.maps.places.PlacesServiceStatus.OK) {
2684
2685                    bounds = new google.maps.LatLngBounds();
2686
2687                    // Filter places
2688                    var placesList = [];
2689                    for (var index = 0; index < results.length; index++) {
2690                        if (filterPlace(results[index], blockTypes) !== false) {
2691                            // Google Places API bug with pagination fix
2692                            // Check to see if place has already been listed
2693                            if ($.inArray(results[index].place_id, placesListCheck) === -1) {
2694                                placesListCheck.push(results[index].place_id);
2695                                placesList.push(results[index]);
2696                            }
2697                        }
2698                    }
2699
2700                    // Check placesListCheck as well because of Google Places API Bug
2701                    // Bascially, placesListCheck contains a list of all results and grows with pagination
2702                    // The API is returning multiple lists (like page 2 and 3 when you get to page 3)
2703                    // Then placesListCheck keeps track of what has already been displayed and removes anything it's
2704                    // already seen. The problem is that, due to the bug, placesList will equal 0 when going from page 2 to 3
2705                    // because the API sends page 2 again and then page 3. Since page 2 has been seen already it thinks there are no
2706                    // results, so it flickers "no results" before getting to page 3.
2707                    if (placesList.length === 0 && placesListCheck.length === 0) {
2708
2709                        showNoResults();
2710
2711                    } else {
2712
2713                        // Hide no results
2714                        showNoResults(false);
2715
2716                        bounds.extend(propertyMarker.position);
2717
2718                        // and create new markers by search result
2719                        for (var i = 0; i < placesList.length; i++) {
2720                            createPlace(placesList[i]);
2721                            bounds.extend(placesList[i].geometry.location);
2722                        }
2723
2724                        // Handle pagination
2725                        var showMore = $('#interior-explore .show-more');
2726                        if (pagination.hasNextPage) {
2727                            // There are more results
2728                            hasMoreResults();
2729
2730                            // show more button click
2731                            // google.maps.event.addDomListener(showMore[0], 'click', function(){
2732                            //     showSearchLoader();
2733                            //     pagination.nextPage();
2734                            // });
2735
2736                            // show more button click
2737                            window.addEventListener('DOMContentLoaded', function () {
2738                                //todo: used showMore[0] here
2739                                showSearchLoader();
2740                                pagination.nextPage();
2741                            });
2742                        } else {
2743                            hasMoreResults(false);
2744                        }
2745
2746                        if (initialSearch) {
2747                            // For initial search, center on the property
2748                            centerOnMarker();
2749                            initialSearch = false;
2750                        } else {
2751                            // Offset map
2752                            fitAndOffsetMap();
2753                        }
2754
2755                    }
2756
2757                } else if (status === google.maps.places.PlacesServiceStatus.ZERO_RESULTS) {
2758
2759                    // Show no results
2760                    showNoResults();
2761                }
2762
2763                // Hide search loader
2764                showSearchLoader(false);
2765
2766            };
2767        }
2768
2769        function filterPlace(obj, blockTypes) {
2770
2771            // Don't filter lodging if it includes campground
2772
2773            blockTypes = blockTypes;
2774            if (typeof blockTypes === 'undefined' || blockTypes === '') {
2775                blockTypes = ['lodging'];
2776            }
2777
2778            // This is so that things listed as lodging and campground are included
2779            var safeTypes = ['campground'];
2780
2781            var isValid = true;
2782            var isSafe = false;
2783            $(obj.types).each(function (i, e) {
2784                if ($.inArray(e, blockTypes) > -1 || obj.permanently_closed) {
2785                    isValid = false;
2786                }
2787                if ($.inArray(e, safeTypes) > -1) {
2788                    isSafe = true;
2789                }
2790            });
2791
2792            if (isSafe && !isValid) {
2793                isValid = true;
2794            }
2795
2796            return isValid;
2797
2798        }
2799
2800        function hasMoreResults(trueFalse) {
2801
2802            trueFalse = trueFalse;
2803            if (typeof trueFalse === 'undefined') {
2804                trueFalse = true;
2805            }
2806
2807            var $showMore = $('#interior-explore .show-more');
2808
2809            if (trueFalse === false) {
2810                $showMore.hide();
2811            } else {
2812                $showMore.show();
2813            }
2814
2815        }
2816
2817        function showNoResults(trueFalse) {
2818
2819            trueFalse = trueFalse;
2820            if (typeof trueFalse === 'undefined') {
2821                trueFalse = true;
2822            }
2823
2824            var $noResults = mapPanel.find('.no-results');
2825
2826            if (trueFalse === false) {
2827                $noResults.hide();
2828            } else {
2829                $noResults.show();
2830                hasMoreResults(false);
2831            }
2832
2833        }
2834
2835        function showSearchLoader(trueFalse) {
2836
2837            trueFalse = trueFalse;
2838            if (typeof trueFalse === 'undefined') {
2839                trueFalse = true;
2840            }
2841
2842            var $loader = $('#search-form .submit-button .loader');
2843
2844            if (trueFalse === false) {
2845                $loader.hide();
2846            } else {
2847                $loader.css('display', 'inline-block');
2848            }
2849
2850        }
2851
2852        function createPlace(place) {
2853            var marker;
2854            var li;
2855            if (mapType === 'explore') {
2856                // Get content for info window wrapped in HTML tags
2857                var content = getPlaceHTML(place);
2858
2859                // Prepare new marker object
2860                marker = new google.maps.Marker({
2861                    position: place.geometry.location,
2862                    map: map,
2863                    title: place.name,
2864                    icon: defaultIcon,
2865                    id: place.place_id + '_marker',
2866                });
2867
2868                // Prepare new infowindow
2869                marker.info = new google.maps.InfoWindow({
2870                    content: content.clone().removeAttr('id')[0]
2871                });
2872
2873                // Add to markers array
2874                mapMarkers.push(marker);
2875
2876                // Create Listing
2877                li = $('<li>').attr({
2878                    class: 'place'
2879                }).html(content).appendTo(mapPanel.find('.places-list ul'));
2880
2881            } else if (mapType === 'find') {
2882                // Create propertyList
2883
2884                // Get content for info window wrapped in HTML tags
2885                var markerContent = getPropertyHTML(place);
2886
2887                // Get content for list
2888                var listContent = getPropertyListHTML(place);
2889
2890                // Prepare new marker object
2891                marker = new google.maps.Marker({
2892                    position: initLatLng(place.latitude, place.longitude),
2893                    map: map,
2894                    title: place.city,
2895                    icon: tinyIcon,
2896                    id: cleanTitle(place.city) + '_' + cleanTitle(place.state_abbr) + '_marker',
2897                    state: cleanTitle(place.state),
2898                    required: true
2899                });
2900
2901                zoomLevelDefaultIcon = marker.getIcon();
2902
2903                // Prepare new infowindow
2904                marker.info = new google.maps.InfoWindow({
2905                    content: markerContent[0]
2906                });
2907
2908                // Add to markers array
2909                mapMarkers.push(marker);
2910
2911                // Create Listing
2912                li = $('<li>').attr({
2913                    class: 'place'
2914                }).html(listContent).appendTo(mapPanel.find('.places-list .state:last-child .sub-menu'));
2915            }
2916
2917            if (typeof (marker) !== 'undefined') {
2918                //used in the third listener..
2919                // let markerName = cleanTitle(place.city) + '_' + cleanTitle(place.state_abbr) + '_marker';
2920
2921                // Event listener - Waits for the creation of the infowindow html structure
2922                google.maps.event.addListener(marker.info, 'DOMContentLoaded', function () {
2923                    infoWindowReady();
2924                });
2925
2926                // Event listener - Add event handler to current marker
2927                google.maps.event.addListener(marker, 'click', function () {
2928                    clickPlaceMarker(this);
2929                });
2930
2931                //todo: replacement
2932                //the object is not loaded yet.... how to wait?
2933                // document.getElementById(markerName).addEventListener('click', function() {
2934                // 		if(!working && !$(event.target).is('a')){
2935                // 			working = true;
2936                // 			clickPlaceList(this);
2937                // 		}
2938                // });
2939
2940                // Event listener - Add event handler for clicking on place in list
2941                // google.maps.event.addDomListener(li[0], 'click', function(event) {
2942                // 	if(!working && !$(event.target).is('a')){
2943                // 		working = true;
2944                // 		clickPlaceList(this);
2945                // 	}
2946                // });
2947
2948                //todo split by map type
2949                if (mapType === 'find') {
2950                    document.getElementById(cleanTitle(place.city) + '_' + cleanTitle(place.state_abbr) + '_listing').addEventListener('click', function () {
2951                        //todo update this "event" call below
2952                        if (!working && !$(event.target).is('a')) {
2953                            working = true;
2954                            clickPlaceList(place, cleanTitle(place.city) + '_' + cleanTitle(place.state_abbr) + '_marker');
2955                        }
2956                    });
2957                } else {
2958                    document.getElementById(place.place_id + '_listing').addEventListener('click', function () {
2959                        //todo update this "event" call below
2960                        if (!working && !$(event.target).is('a')) {
2961                            working = true;
2962                            clickPlaceList(place, place.place_id + '_marker');
2963                        }
2964                    });
2965                }
2966            }
2967
2968        }
2969
2970        function updateCurrentMarker(marker, callback) {
2971
2972            if (mapType === 'explore') {
2973
2974                if (selectedMarker) {
2975                    selectedMarker.setIcon(defaultIcon);
2976                }
2977
2978                if (marker.required !== true) {
2979                    marker.setIcon(selectedIcon);
2980                    selectedMarker = marker;
2981                }
2982
2983            } else if (mapType === 'find') {
2984
2985                if (selectedMarker) {
2986                    selectedMarker.setIcon(zoomLevelDefaultIcon);
2987                }
2988
2989                marker.setIcon(propertyIcon);
2990                selectedMarker = marker;
2991
2992            }
2993
2994            // Optional callback function
2995            if (typeof callback === 'function' && callback()) {
2996                callback();
2997            }
2998
2999        }
3000
3001        function updateMarkerIcons() {
3002
3003            var zoomLevel = map.getZoom();
3004            $.each(mapMarkers, function (i, v) {
3005                if (v !== selectedMarker) {
3006                    if (zoomLevel <= 5) {
3007                        v.setIcon(tinyIcon);
3008                    } else if (zoomLevel <= 6) {
3009                        v.setIcon(defaultIcon);
3010                    } else {
3011                        v.setIcon(defaultPropertyIcon);
3012                    }
3013                }
3014                zoomLevelDefaultIcon = v.getIcon();
3015            });
3016
3017        }
3018
3019        function clickPlaceMarker(marker) {
3020            clearInfos();
3021            var m = marker.getMap();
3022            if (mapType === 'explore') {
3023                closeDetails();
3024            } else if (mapType === 'find') {
3025                handleFindList(marker, false);
3026            }
3027
3028            updateCurrentMarker(marker, setTimeout(function () {
3029                marker.info.open(m, marker);
3030            }, 400));
3031        }
3032
3033        function clickPlaceList(place, mapTarget) {
3034            clearInfos();
3035            let id = place.id;
3036
3037            if (typeof id === 'undefined') {
3038                id = place.place_id;
3039            }
3040
3041            if (typeof mapTarget !== "undefined" && mapTarget !== null) {
3042
3043                //he is looping through each marker to find the one we want? ok.. but... we know 
3043which one we want.
3044                $.each(mapMarkers, function (i, marker) {
3045
3046                    //markers simplified
3047                    // if (id.replace('_listing', '_marker') === marker.id || id + '_marker' === marker.id) {
3048                    if (mapTarget === marker.id) {
3049
3050                        var m = marker.getMap();
3051
3052                        if (mapType === 'explore') {
3053                            openDetails(id);
3054                        } else if (mapType === 'find') {
3055                            //updateCurrentMarker(marker);
3056                            working = false;
3057                        }
3058
3059                        updateCurrentMarker(marker, function () {
3060                            centerOnMarker(marker);
3061                            marker.info.open(m, marker);
3062                        });
3063
3064                        return;
3065                    }
3066                });
3067            }
3068        }
3069
3070        function filterIconUrl(url) {
3071
3072            var types = [
3073                'airport',
3074                'aquarium',
3075                'art_gallery',
3076                'bar',
3077                'barber',
3078                'bowling',
3079                'cafe',
3080                'camping',
3081                'car_dealer',
3082                'casino',
3083                'civic_building',
3084                'dentist',
3085                'doctor',
3086                'electronics',
3087                'fitness',
3088                'gas_station',
3089                'generic_business',
3090                'generic_recreational',
3091                'government',
3092                'lodging',
3093                'museum',
3094                'repair',
3095                'restaurant',
3096                'shopping',
3097                'stadium',
3098                'travel_agent',
3099                'worship_general'
3100            ];
3101
3102            var t = '';
3103            $.each(types, function (i, type) {
3104                if (url.indexOf(type) !== -1) {
3105                    t = type;
3106                }
3107            });
3108
3109            return t;
3110
3111        }
3112
3113        function getIcon(url) {
3114
3115            var type = filterIconUrl(url);
3116
3117            var iconUrl = templateDir;
3118
3119            switch (type) {
3120
3121                case 'airport':
3122                    iconUrl += '/images/site/map-icons/airport-71.png';
3123                    break;
3124
3125                case 'aquarium':
3126                    iconUrl += '/images/site/map-icons/aquarium-71.png';
3127                    break;
3128
3129                case 'art_gallery':
3130                    iconUrl += '/images/site/map-icons/art_gallery-71.png';
3131                    break;
3132
3133                case 'bar':
3134                    iconUrl += '/images/site/map-icons/bar-71.png';
3135                    break;
3136
3137                case 'barber':
3138                    iconUrl += '/images/site/map-icons/barber-71.png';
3139                    break;
3140
3141                case 'bowling':
3142                    iconUrl += '/images/site/map-icons/bowling-71.png';
3143                    break;
3144
3145                case 'cafe':
3146                    iconUrl += '/images/site/map-icons/cafe-71.png';
3147                    break;
3148
3149                case 'camping':
3150                    iconUrl += '/images/site/map-icons/camping-71.png';
3151                    break;
3152
3153                case 'car_dealer':
3154                    iconUrl += '/images/site/map-icons/car_dealer-71.png';
3155                    break;
3156
3157                case 'casino':
3158                    iconUrl += '/images/site/map-icons/casino-71.png';
3159                    break;
3160
3161                case 'civic_building':
3162                    iconUrl += '/images/site/map-icons/civic_building-71.png';
3163                    break;
3164
3165                case 'dentist':
3166                    iconUrl += '/images/site/map-icons/dentist-71.png';
3167                    break;
3168
3169                case 'doctor':
3170                    iconUrl += '/images/site/map-icons/doctor-71.png';
3171                    break;
3172
3173                case 'electronics':
3174                    iconUrl += '/images/site/map-icons/electronics-71.png';
3175                    break;
3176
3177                case 'fitness':
3178                    iconUrl += '/images/site/map-icons/fitness-71.png';
3179                    break;
3180
3181                case 'gas_station':
3182                    iconUrl += '/images/site/map-icons/gas_station-71.png';
3183                    break;
3184
3185                case 'generic_business':
3186                    iconUrl += '/images/site/map-icons/generic_business-71.png';
3187                    break;
3188
3189                case 'generic_recreational':
3190                    iconUrl += '/images/site/map-icons/generic_recreational-71.png';
3191                    break;
3192
3193                case 'government':
3194                    iconUrl += '/images/site/map-icons/government-71.png';
3195                    break;
3196
3197                case 'lodging':
3198                    iconUrl += '/images/site/map-icons/lodging-71.png';
3199                    break;
3200
3201                case 'museum':
3202                    iconUrl += '/images/site/map-icons/museum-71.png';
3203                    break;
3204
3205                case 'repair':
3206                    iconUrl += '/images/site/map-icons/repair-71.png';
3207                    break;
3208
3209                case 'restaurant':
3210                    iconUrl += '/images/site/map-icons/restaurant-71.png';
3211                    break;
3212
3213                case 'shopping':
3214                    iconUrl += '/images/site/map-icons/shopping-71.png';
3215                    break;
3216
3217                case 'stadium':
3218                    iconUrl += '/images/site/map-icons/stadium-71.png';
3219                    break;
3220
3221                case 'travel_agent':
3222                    iconUrl += '/images/site/map-icons/travel_agent-71.png';
3223                    break;
3224
3225                case 'worship_general':
3226                    iconUrl += '/images/site/map-icons/worship_general-71.png';
3227                    break;
3228
3229                default:
3230                    iconUrl += '/images/site/map-icons/generic_business-71.png';
3231
3232            }
3233
3234            return iconUrl;
3235
3236        }
3237
3238        function clearPlacesList() {
3239            mapPanel.find('.places-list li').remove();
3240        }
3241
3242        function getPropertyListHTML(data) {
3243
3244            // Create and return all HTML for the property/state list
3245            var id = data.id;
3246            var logo = data.alt_logo;
3247            var address = data.address;
3248            var city = data.city;
3249            var page = data.link;
3250
3251            // Content container
3252            var html = $('<div>').attr({
3253                class: 'place-content property',
3254            });
3255
3256            // Two columns
3257            var col1 = $('<div>').attr({
3258                class: 'col-1'
3259            });
3260
3261            var col2 = $('<div>').attr({
3262                class: 'col-2'
3263            });
3264
3265            // Create content html
3266            var div = $('<div>').attr({
3267                class: 'logo',
3268                style: 'background-image: url(' + logo + ');'
3269            }).appendTo(col1);
3270
3271            // City
3272            var _city = $('<h3>').attr({
3273                class: 'city',
3274                id: cleanTitle(data.city) + '_' + cleanTitle(data.state_abbr) + '_listing'
3275            }).text(city + ', ' + data.state_abbr).appendTo(col2);
3276
3277            // Address
3278            var _address = $('<span>').attr({
3279                class: 'address'
3280            }).text(address).appendTo(col2);
3281
3282            // Check Page Link
3283            var _pageLink = $('<div>').attr({
3284                class: 'page-link'
3285            }).appendTo(col2);
3286
3287            var _pageLinkLink = $('<a>').attr({
3288                href: data.link,
3289                class: 'cobble-font track-visit-page'
3290            }).text('Visit Page').appendTo(_pageLink);
3291
3292            var _pageLinkIcon = $('<i>').attr({
3293                class: 'fa fa-angle-right'
3294            }).appendTo(_pageLink);
3295
3296
3297            // Append columns to main container
3298            col1.appendTo(html);
3299            col2.appendTo(html);
3300
3301            return html;
3302
3303
3304        }
3305
3306        function getPropertyHTML(data) {
3307
3308            if (typeof data === 'undefined') {
3309
3310                data = $.extend({}, propertyData, metaData);
3311            }
3312
3313            var id = data.id;
3314            var logo = data.alt_logo;
3315            var type = data.type;
3316            var address = data.address_full;
3317            var bookingID = data.booking_id;
3318            var page = data.link;
3319
3320            // _phone will be the unformatted number
3321            var _phone = data.phone;
3322            var phone = data.phone_formatted;
3323            if (phone === '') {
3324                _phone = data.tollfree;
3325                phone = data.tollfree_formatted;
3326            }
3327            var email = fixEmail(data.email);
3328            var desc = data.brief_description;
3329
3330            // Content container
3331            var html = $('<div>').attr({
3332                class: 'place-content property',
3333                id: id
3334            });
3335
3336            // Two columns
3337            var col1 = $('<div>').attr({
3338                class: 'col-1'
3339            });
3340
3341            var col2 = $('<div>').attr({
3342                class: 'col-2'
3343            });
3344
3345            // Create content html
3346            if (logo !== '') {
3347                var div = $('<a>').attr({
3348                    class: 'logo track-logo-link',
3349                    style: 'background-image: url(' + logo + ');',
3350                    href: data.link
3351                }).appendTo(col1);
3352            }
3353
3354            // Desc
3355            if (desc !== '') {
3356                var d = $('<p>').attr({
3357                    class: 'desc',
3358                }).text(desc).appendTo(col2);
3359            }
3360
3361            // Address
3362            if (address !== '') {
3363                var span = $('<span>').attr({
3364                    class: 'address',
3365                }).text(address).appendTo(col2);
3366            }
3367
3368            // Phone
3369            var p = $('<a>').attr({
3370                class: 'phone phone_number track-call',
3371                href: 'tel:' + _phone
3372            }).text(phone).appendTo(col1);
3373
3374            // Email
3375            var e = $('<a>').attr({
3376                class: 'email track-email',
3377                href: 'mailto:' + email
3378            }).text('Send Email').appendTo(col1);
3379
3380            // Booking Link
3381            var b = $('<a>').attr({
3382                class: 'book-now',
3383                href: '#' + bookingID
3384            }).text('Book Now').appendTo(col1);
3385
3386            // Append columns to main container
3387            col1.appendTo(html);
3388            col2.appendTo(html);
3389
3390            return html;
3391        }
3392
3393        function getPlaceHTML(place) {
3394            var icon = getIcon(place.icon);
3395
3396            var id = place.place_id;
3397            if (typeof id === 'undefined') {
3398                id = '';
3399            }
3400
3401            var name = place.name;
3402            if (typeof name === 'undefined') {
3403                name = '';
3404            }
3405
3406            var hours = place.opening_hours;
3407            if (typeof hours === 'undefined') {
3408                hours = '';
3409            }
3410            // else {
3411            //     var isOpen = place.opening_hours.isOpen();
3412            // }
3413            //
3414            // console.log('isOpen = ' + isOpen);
3415
3416            var photos = place.photos;
3417            if (typeof photos === 'undefined') {
3418                photos = [];
3419            }
3420
3421            var vicinity = place.vicinity;
3422            if (typeof vicinity === 'undefined') {
3423                vicinity = '';
3424            }
3425
3426            // Content container
3427            var html = $('<div>').attr({
3428                class: 'place-content',
3429                id: id + '_listing'
3430            });
3431
3432            // Create content html
3433            if (icon !== '') {
3434                var span = $('<span>').attr({
3435                    class: 'icon',
3436                    style: 'background-image: url(' + icon + ');'
3437                }).appendTo(html);
3438            }
3439
3440            // Background style
3441            var photoStyle = '';
3442            if (photos.length > 0) {
3443
3444                photoStyle = 'background-image: url(' + photos[0].getUrl({'maxWidth': 420, 'maxHeight': 300}) + ');';
3445
3446            } else {
3447                photoStyle = 'background-image: url(' + templateDir + '/images/site/black-transparent-light.png);';
3448            }
3449
3450            // Photo
3451            var div = $('<div>').attr({
3452                class: 'photo',
3453                style: photoStyle
3454            }).appendTo(html);
3455
3456            // Place title
3457            var h4 = $('<h4>').attr({
3458                class: 'cobble-font'
3459            }).text(name + ' ').appendTo(html);
3460
3461            // Distance
3462            var distance = $('<span>').attr({
3463                class: 'distance'
3464            }).appendTo(h4);
3465
3466            // Vicinity/Address
3467            if (vicinity !== '') {
3468                var spanOne = $('<span>').attr({
3469                    class: 'address',
3470                }).text(vicinity).appendTo(html);
3471            }
3472
3473            // Open now
3474            if (typeof hours.open_now !== "undefined" && hours.open_now !== null) {
3475                var _class = '';
3476                var _text = '';
3477                if (hours.open_now === true) {
3478                    _class = 'is-open';
3479                    _text = 'Open Now';
3480                } else if (hours.open_now === false) {
3481                    _class = 'is-closed';
3482                    _text = 'Currently Closed';
3483                }
3484                var spanTwo = $('<span>').attr({
3485                    class: _class
3486                }).text(_text).appendTo(html);
3487            }
3488
3489            // Calculate distance
3490            calculateDistance(place);
3491
3492            return html;
3493        }
3494
3495        // Place Details
3496        function toggleDetails() {
3497        }
3498
3499        function clearDetails() {
3500            var loader = $('.place-details .loader').detach();
3501            $('.place-details').empty().append(loader);
3502        }
3503
3504        function setDetailsCSS() {
3505            // Set padding for content under place detail image
3506            var imageHeight = 225 - mapPanel.find('.close-detail').outerHeight();
3507            $('#interior-explore .place-details').css('margin-top', imageHeight + 'px');
3508        }
3509
3510        function openDetails(id) {
3511
3512            // Clear previous details
3513            clearDetails();
3514
3515            // Set any special CSS
3516            setDetailsCSS();
3517
3518            // Show loader
3519            $('.place-details .loader').show();
3520
3521            // Set place ID to be sent to google
3522            var placeId = {
3523                placeId: id
3524            };
3525
3526            // Get details
3527            var service = new google.maps.places.PlacesService(map);
3528            service.getDetails(placeId, function (place, status) {
3529                if (status === google.maps.places.PlacesServiceStatus.OK) {
3530
3531                    // Hide loader
3532                    $('.place-details .loader').hide();
3533
3534                    // Place details container
3535                    var container = $('.place-details');
3536
3537                    // Image/Alt Image
3538                    var image = $('<div>').attr({
3539                        class: 'image'
3540                    }).appendTo(container);
3541
3542                    if (typeof place.photos !== 'undefined' && place.photos !== null) {
3543                        var img = place.photos[0].getUrl({'maxWidth': 500, 'maxHeight': 222});
3544                        image.attr({
3545                            style: 'background-image: url(' + img + ')'
3546                        });
3547                    }
3548
3549                    // Title
3550                    var title = $('<h4>').attr({
3551                        class: 'title cobble-font'
3552                    }).text(place.name).appendTo(container);
3553
3554                    // Type-Price-Open-Now Container
3555                    var tpon = $('<div>').attr({
3556                        class: 'type-price-container'
3557                    });
3558
3559                    // Type
3560                    var type = $('<span>').attr({
3561                        class: 'type sub-title'
3562                    }).text(place.types[0].replace(/[^a-zA-Z-]/g, ' ')).appendTo(tpon);
3563
3564                    // Open Now
3565                    if (typeof place.opening_hours !== "undefined" && place.opening_hours !== null) {
3566
3567                        if (typeof place.opening_hours.isOpen() !== "undefined" && place.opening_hours.isOpen() !== null) {
3568                            //todo: since isOpen is now a boolean, this statement is odd now
3569                            var openNow = place.opening_hours.isOpen();
3570                            var _class = '';
3571                            var _text = '';
3572
3573                            if (openNow) {
3574                                _class = 'is-open';
3575                                _text = 'Open Now';
3576
3577                            } else {
3578                                _class = 'is-closed';
3579                                _text = 'Currently Closed';
3580                            }
3581
3582                            var openContainer = $('<span>').attr({
3583                                class: 'sub-title ' + _class
3584                            }).text(_text).appendTo(tpon);
3585                        }
3586                    }
3587
3588                    // Price Level
3589                    if (typeof place.price_level !== "undefined" && place.price_level !== null) {
3590                        var priceLevel = place.price_level;
3591                        var level;
3592                        switch (priceLevel) {
3593                            case 0:
3594                                level = '';
3595                                break;
3596                            case 1:
3597                                level = '<i class="fa fa-dollar"></i>';
3598                                break;
3599                            case 2:
3600                                level = '<i class="fa fa-dollar"></i><i class="fa fa-dollar"></i>';
3601                                break;
3602                            case 3:
3603                                level = '<i class="fa fa-dollar"></i><i class="fa fa-dollar"></i><i class="fa fa-dollar"></i>';
3604                                break;
3605                            case 4:
3606                                level = '<i class="fa fa-dollar"></i><i class="fa fa-dollar"></i><i class="fa fa-dollar"></i><i class="fa fa-dollar"></i>';
3607                                break;
3608                            default:
3609                                level = '';
3610
3611                        }
3612                        var price = $('<span>').attr({
3613                            class: 'price-level sub-title'
3614                        }).html(level).appendTo(tpon);
3615                    }
3616                    tpon.appendTo(container);
3617
3618                    // Elements with Icons
3619                    var scrollContainer = $('<div>').attr({
3620                        class: 'scroll-container'
3621                    });
3622
3623                    // Address
3624                    if (typeof place.formatted_address !== "undefined" && place.formatted_address !== null) {
3625                        // Container element
3626                        var a = $('<div>').attr({
3627                            class: 'address-container'
3628                        });
3629                        // Icon
3630                        var ai = $('<i>').attr({
3631                            class: 'fa fa-map-marker'
3632                        });
3633                        ai.appendTo(a);
3634                        // Content
3635                        var address = $('<span>').attr({
3636                            class: 'address',
3637                        }).text(place.formatted_address).appendTo(a);
3638                        a.appendTo(scrollContainer);
3639                    }
3640
3641                    // Phone
3642                    if (typeof place.formatted_phone_number !== "undefined" && place.formatted_phone_number !== null) {
3643                        // Container element
3644                        var p = $('<div>').attr({
3645                            class: 'phone-container'
3646                        });
3647                        // Icon
3648                        var pi = $('<i>').attr({
3649                            class: 'fa fa-phone'
3650                        });
3651                        pi.appendTo(p);
3652                        // Content
3653                        var number = place.formatted_phone_number.replace(/[^0-9.]/g, '');
3654                        var phone = $('<a>').attr({
3655                            class: 'phone phone_number',
3656                            href: 'tel:' + number
3657                        }).text(place.formatted_phone_number).appendTo(p);
3658                        p.appendTo(scrollContainer);
3659                    }
3660
3661                    // Website
3662                    if (typeof place.website !== "undefined" && place.website !== null) {
3663                        // Container element
3664                        var w = $('<div>').attr({
3665                            class: 'website-container'
3666                        });
3667                        // Icon
3668                        var wi = $('<i>').attr({
3669                            class: 'fa fa-globe'
3670                        });
3671                        wi.appendTo(w);
3672                        // Content
3673                        var site = $('<a>').attr({
3674                            class: 'website',
3675                            href: place.website,
3676                            target: '_blank'
3677                        }).text('Visit Website').appendTo(w);
3678                        w.appendTo(scrollContainer);
3679                    }
3680
3681                    // Hours
3682                    if (typeof place.opening_hours !== "undefined" && place.opening_hours !== null) {
3683
3684                        if (typeof place.opening_hours.weekday_text !== "undefined" && place.opening_hours.weekday_text !== null) {
3685                            // Container element
3686                            var oh = $('<div>').attr({
3687                                class: 'hours-container'
3688                            });
3689                            // Icon
3690                            var ohi = $('<i>').attr({
3691                                class: 'fa fa-clock-o'
3692                            });
3693                            ohi.appendTo(oh);
3694                            // Content
3695                            var hours = place.opening_hours.weekday_text;
3696                            var ul = $('<ul>').attr({
3697                                class: 'hours'
3698                            }).appendTo(oh);
3699                            $.each(hours, function (i, e) {
3700                                $('<li>').text(e).appendTo(ul);
3701                            });
3702                            oh.appendTo(scrollContainer);
3703                        }
3704
3705                    }
3706
3707                    scrollContainer.appendTo(container);
3708
3709                    // Set class
3710                    mapPanel.addClass('show-details')
3711                        .find('.content-container')
3712                        .one(whichTransitionEvent(), function (event) {
3713                            mapPanel.addClass('ready');
3714                            updateListHeight();
3715                        });
3716
3717                    working = false;
3718
3719                } else {
3720
3721                    updateListHeight();
3722
3723                    working = false;
3724                }
3725
3726            });
3727
3728        }
3729
3730        function closeDetails() {
3731            mapPanel.removeClass('ready')
3732                .one(whichTransitionEvent(), function (event) {
3733                    mapPanel.removeClass('show-details');
3734                    clearDetails();
3735                });
3736            updateListHeight();
3737        }
3738
3739        function calculateDistance(place) {
3740            var service = new google.maps.DistanceMatrixService();
3741            return service.getDistanceMatrix({
3742                origins: [propertyMarker.position],
3743                destinations: [place.geometry.location],
3744                travelMode: google.maps.TravelMode.DRIVING,
3745                unitSystem: google.maps.UnitSystem.IMPERIAL,
3746                avoidHighways: false,
3747                avoidTolls: false
3748            }, displayDistance(place.place_id));
3749        }
3750
3751        function displayDistance(id) {
3752            return function (response, status) {
3753                if (status !== google.maps.DistanceMatrixStatus.OK) {
3754                    console.log('Error was: ' + status);
3755                } else {
3756                    var origins = response.originAddresses;
3757                    var destinations = response.destinationAddresses;
3758                    for (var i = 0; i < origins.length; i++) {
3759                        var results = response.rows[i].elements;
3760                        for (var j = 0; j < results.length; j++) {
3761                            $('#' + id).find('.distance').html(results[j].distance.text);
3762                        }
3763                    }
3764                }
3765            };
3766        }
3767
3768        // Execute Search Forms
3769        function executeSearchForm() {
3770
3771            // Get form elements as array
3772            var formData = {};
3773            $.each($('#search-form form').serializeArray(), function (i, field) {
3774                formData[field.name] = field.value;
3775            });
3776
3777            var keywords = formData.keywords;
3778            var types = formData.type;
3779            var rankBy = formData.rankby;
3780
3781            searchMap(keywords, types, rankBy);
3782
3783        }
3784
3785        // Events
3786
3787        // Map Loaded
3788        $(document).on('mapLoaded', function (event) {
3789            if (mapType === 'explore') {
3790
3791                openPanel(cloneGoogleLogo());
3792
3793            } else if (mapType === 'find') {
3794
3795                if (jsData.isMobile === 'true') {
3796                    mapPanel.addClass('open');
3797                } else {
3798                    openPanel(cloneGoogleLogo());
3799                }
3800
3801            }
3802            mapContainer.addClass('loaded');
3803            mapLoaded();
3804        });
3805
3806        // Close place details
3807        $('.close-detail').on('click', function (e) {
3808            closeDetails();
3809        });
3810
3811        // Close map
3812        $('.close-map').on('click', function (e) {
3813            closeMap();
3814        });
3815
3816        // Keyboard
3817        $(document).keyup(function (e) {
3818
3819            // Escape button press
3820            if (e.which === 27 && mapType === 'explore') {
3821                if (typeof mapPanel !== 'undefined' && mapPanel.hasClass('show-detail
3821s')) {
3822                    closeDetails();
3823                } else if (typeof mapContainer !== 'undefined' && mapContainer.hasClass('open')) {
3824                    closeMap();
3825                }
3826            }
3827
3828        });
3829
3830        $(document).keydown(function (e) {
3831
3832            // Enter button press
3833            if (e.which === 13 && mapType === 'explore') {
3834                if (typeof mapPanel !== 'undefined') {
3835                    var isForm = $(document.activeElement).closest('.form').length ? true : false;
3836                    if (isForm) {
3837                        e.preventDefault();
3838                        executeSearchForm();
3839                    }
3840                }
3841            }
3842
3843        });
3844
3845        return {
3846            initExplore: initExplore,
3847            initFind: initFind,
3848            reinit: reinit,
3849            loaded: isMapLoaded,
3850            openMap: openMap,
3851            closeMap: closeMap,
3852            searchMap: searchMap,
3853            executeSearchForm: executeSearchForm
3854        };
3855
3856    })();
3857
3858    $('#search-form .submit-button').on('click', function (e) {
3859
3860        // Stop form from submitting
3861        e.preventDefault();
3862
3863        // Track Event
3864        trackEvent('Interaction', 'explore-search', 'Searched Nearby');
3865
3866        googleMap.executeSearchForm();
3867
3868    });
3869
3870    $('.show-map').on('click', function (e) {
3871        e.preventDefault();
3872
3873        // Track Event
3874        trackEvent('Interaction', 'explore-open', 'Explored Nearby Places');
3875
3876        if (googleMap.loaded()) {
3877            googleMap.openMap(googleMap.reinit());
3878        } else {
3879            googleMap.initExplore(cfsData);
3880        }
3881    });
3882
3883    if ($('#interior-find').length) {
3884        googleMap.initFind();
3885    }
3886
3887// --------------------------------------------------------------------------
3888// -------------------------------------------------------- 404 Page
3889// --------------------------------------------------------------------------
3890
3891
3892// --------------------------------------------------------------------------
3893// -------------------------------------------------------- Helper Functions
3894// --------------------------------------------------------------------------
3895
3896    // Remove Emoji's from string
3897    function removeEmojis(string) {
3898
3899        var regex = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g;
3900
3901        return string.replace(regex, '');
3902    }
3903
3904    function cleanTitle(string) {
3905        var str = string;
3906        str = $.trim(str).replace(/\W+/g, '-').toLowerCase();
3907        return str;
3908    }
3909
3910    function fixEmail(email) {
3911        email = email.split('[at]').join('@');
3912        email = email.split('[dot]').join('.');
3913
3914        return email;
3915    }
3916
3917// Throttle calls to "callback" routine and ensure that it
3918// is not invoked any more often than "delay" milliseconds.
3919    function throttle(delay, callback) {
3920        var previousCall = new Date().getTime();
3921        return function () {
3922            var time = new Date().getTime();
3923
3924            if ((time - previousCall) >= delay) {
3925                previousCall = time;
3926                // Optional Callback function
3927                if (typeof callback === 'function' && callback()) {
3928                    callback();
3929                }
3930            }
3931        };
3932    }
3933
3934    function genMailTo(email, subject) {
3935
3936        $('.format-email').each(function () {
3937
3938            var span = $('<span>').text(formatEmail(email));
3939
3940            var mailto = $('<a>').attr({
3941                rel: 'nofollow',
3942                class: 'track-email',
3943                href: 'mailto:' + email + '?subject=' + subject
3944            }).html(span);
3945
3946            $(this).append(mailto);
3947
3948        });
3949
3950    }
3951
3952    function formatEmail(email) {
3953
3954        var formatted = email;
3955
3956        formatted = specialNames(formatted);
3957
3958        formatted = ucfirst(formatted);
3959
3960        formatted = formatted.replace('staycobblestone', 'StayCobblestone');
3961        formatted = formatted.replace('stayboarders', 'StayBoarders');
3962
3963        return formatted;
3964    }
3965
3966    function specialNames(string) {
3967
3968        // Some properties are a combination of two names
3969        // like Chippewa Falls, so this function looks for
3970        // them and sets up camel case
3971
3972        var newString = string;
3973
3974        newString = newString.replace('mccook', 'McCook');
3975
3976        newString = newString.replace('stmarys', 'StMarys');
3977
3978        newString = newString.replace('fortdodge', 'FortDodge');
3979
3980        newString = newString.replace('traversecity', 'TraverseCity');
3981
3982        newString = newString.replace('biglake', 'BigLake');
3983
3984        newString = newString.replace('brokenbow', 'BrokenBow');
3985
3986        newString = newString.replace('grandisland', 'GrandIsland');
3987
3988        newString = newString.replace('devilslake', 'DevilsLake');
3989
3990        newString = newString.replace('ashlandcity', 'AshlandCity');
3991
3992        newString = newString.replace('chippewafalls', 'ChippewaFalls');
3993
3994        newString = newString.replace('stevenspoint', 'StevensPoint');
3995
3996        newString = newString.replace('sodasprings', 'SodaSprings');
3997
3998        newString = newString.replace('fairfieldbay', 'FairfieldBay');
3999
4000        newString = newString.replace('wirapids', 'WiRapids');
4001
4002        newString = newString.replace('oshkoshwi', 'OshkoshWi');
4003
4004        return newString;
4005
4006    }
4007
4008    function ucfirst(string) {
4009        return string.charAt(0).toUpperCase() + string.slice(1);
4010    }
4011
4012    if ($('.format-email').length) {
4013        if (typeof cfsData !== 'undefined' && cfsData !== null) {
4014            var email = fixEmail(cfsData.property.email);
4015            genMailTo(email, 'Message for' + ' ' + cfsData.property.type + ' in ' + cfsData.property.city);
4016        }
4017    }
4018
4019    function isInt(value) {
4020        if (isNaN(value)) {
4021            return false;
4022        }
4023        var x = parseFloat(value);
4024        return (x | 0) === x;
4025    }
4026
4027    // Which transition event
4028    // Function from David Walsh
4029    function whichTransitionEvent() {
4030
4031        //var t, el = document.createElement("fakeelement");
4032//
4033//		var transitions = {
4034//			'WebkitTransition' :'webkitTransitionEnd',
4035//			'MozTransition'    :'transitionend',
4036//			'MSTransition'     :'msTransitionEnd',
4037//			'OTransition'      :'oTransitionEnd',
4038//			'transition'       :'transitionEnd'
4039//		};
4040//
4041//		for (t in transitions){
4042//			if (el.style[t] !== undefined){
4043//				return transitions[t];
4044//			}
4045//		}
4046
4047        return 'webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend';
4048
4049    }
4050
4051    function hasCustomClass(e) {
4052
4053        var customClass;
4054
4055        if ($(e).closest('.open-lightbox').length) {
4056            customClass = true;
4057        } else if ($(e).closest('.show-gallery').length) {
4058            customClass = true;
4059        } else if ($(e).closest('.show-group-booking').length) {
4060            customClass = true;
4061        } else if ($(e).closest('.show-map').length) {
4062            customClass = true;
4063        } else {
4064            customClass = false;
4065        }
4066
4067        return customClass;
4068    }
4069
4070    //Scroll smoothly
4071    $('.menu-container a[href*="#"]:not([href="#"])').click(function (e) {
4072
4073        var _offset = 0;
4074
4075        if (!hasCustomClass(this)) {
4076            if ($('#interior-booking').length) {
4077                _offset = $('#interior-booking').outerHeight() + $('#property-navigation').outerHeight();
4078            }
4079            if (navigator.userAgent.match(/(iPod|iPhone|iPad|Android)/)) {
4080                window.scrollTo(0, $(this.hash).offset().top - _offset);
4081            } else {
4082
4083                e.preventDefault();
4084
4085                $('html,body').stop().animate({scrollTop: $(this.hash).offset().top - _offset}, 1500);
4086
4087                if (location.pathname.replace(/^\//, '') === this.pathname.replace(/^\//, '') && location.hostname === this.hostname) {
4088                    var target = $(this.hash);
4089                    target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
4090                    if (target.length) {
4091                        $('html,body').stop().animate({
4092                            scrollTop: target.offset().top - _offset
4093                        }, 1000);
4094                        return false;
4095                    }
4096                }
4097            }
4098        }
4099    });
4100
4101    // Scroll to top
4102    $(window).scroll(function () {
4103        if ($(this).scrollTop() > 600) {
4104            $('#scroll-to-top').stop().fadeIn();
4105        } else {
4106            $('#scroll-to-top').stop().fadeOut();
4107        }
4108    });
4109
4110    // scroll body to 0px on click
4111    $('#scroll-to-top').click(function () {
4112        $('body,html').stop().animate({
4113            scrollTop: 0
4114        }, 800);
4115        return false;
4116    });
4117
4118    // Stop scrolling on user scroll
4119    $('body, html').on('scroll mousedown wheel DOMMouseScroll mousewheel keyup touchmove', function (e) {
4120        if (e.which > 0 || e.type === "mousedown" || e.type === "mousewheel" || e.type === "touchmove") {
4121            $('html, body').stop(true);
4122        }
4123    });
4124
4125    // Form Validation
4126    var validate = (function () {
4127
4128        function checkFormData(form) {
4129
4130            // Determine what type of element passed
4131            var $form;
4132            if (typeof form !== 'undefined') {
4133
4134                if (typeof form === 'string') {
4135                    $form = $(form);
4136                } else {
4137                    $form = form;
4138                }
4139
4140                // Get form elements as array
4141                var formData = {};
4142                $.each($form.serializeArray(), function (i, field) {
4143                    var $field = $form.find('[name=' + field.name + ']');
4144                    if ($field.hasAnyClass('required', 'required-email', 'required-phone', 'required-num', 'required-nonum')) {
4145                        var data = {
4146                            value: field.value,
4147                            class: $field.attr('class'),
4148                            target: $field
4149                        };
4150                        formData[field.name] = data;
4151                    }
4152                });
4153
4154                // Send to validation and get results
4155                var results = validateForm(formData);
4156
4157
4158                // Check validation results
4159                if ($.isEmptyObject(results)) {
4160                    // If it's an empty object the validation passed
4161                    return true;
4162                } else {
4163                    // Otherwise it will contain the failed elements
4164                    // Handle indicating troublesome elements
4165                    $.each(results, function (i, e) {
4166                        $('label[for=' + e.attr('name') + ']').addClass('has-error');
4167                    });
4168                    return false;
4169                }
4170
4171            } else {
4172                // Form is undefined
4173                return false;
4174            }
4175
4176        }
4177
4178        function validateForm(data) {
4179
4180            var failedValidation = {};
4181
4182            $.each(data, function (n, field) {
4183
4184                var v = field.value;
4185                v = $.trim(field.value);
4186                var fail = false;
4187
4188                if (field.class.indexOf('required') > -1) {
4189                    if (v.length === 0 || Number(v) === -1) {
4190                        fail = true;
4191                    }
4192                }
4193
4194                if (field.class.indexOf('required-email') > -1) {
4195
4196                    if (v.length === 0) {
4197                        fail = true;
4198                    } else {
4199                        var emailPattern = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
4200                        if (!emailPattern.test(v)) {
4201                            fail = true;
4202                        }
4203                    }
4204
4205                }
4206
4207                if (field.class.indexOf('required-phone') > -1) {
4208                    if (v.length === 0) {
4209                        fail = true;
4210                    } else {
4211                        var phonePattern = /^(\+\d{1,2}\s?)?(\(?\d{3}\)?([\s.-])?)?\d{3}([\s.-])?\d{4}$/;
4212                        if (!phonePattern.test(v)) {
4213                            fail = true;
4214                        }
4215                    }
4216                }
4217
4218                if (field.class.indexOf('required-num') > -1) {
4219                    if (v.length === 0) {
4220                        fail = true;
4221                    } else {
4222                        var numPattern = /^[0-9]*$/;
4223                        if (!numPattern.test(v)) {
4224                            fail = true;
4225                        }
4226                    }
4227                }
4228
4229                if (field.class.indexOf('required-nonum') > -1) {
4230                    if (v.length === 0) {
4231                        fail = true;
4232                    } else {
4233                        var noNumPattern = /^[a-zA-z\,\.\-\'\`\s]*$/;
4234                        if (!noNumPattern.test(v)) {
4235                            fail = true;
4236                        }
4237                    }
4238                }
4239
4240                if (fail) {
4241                    failedValidation[n] = field.target;
4242                }
4243
4244            });
4245
4246            return failedValidation;
4247
4248        }
4249
4250        function setRequired(form) {
4251
4252            var $form;
4253            if (typeof form !== 'undefined') {
4254                if (typeof form === 'string') {
4255                    $form = $(form);
4256                } else {
4257                    $form = form;
4258                }
4259            }
4260
4261            var required = $form.find(':input[class*="required"]');
4262
4263            $.each(required, function (i, v) {
4264                $(v).siblings('label').addClass('required');
4265            });
4266
4267        }
4268
4269        function clearError(e) {
4270            var $e = $('label[for=' + e.name + ']');
4271            if ($e.hasClass('has-error')) {
4272                $e.removeClass('has-error');
4273            }
4274        }
4275
4276        // return
4277        return {
4278            form: checkFormData,
4279            setRequired: setRequired,
4280            clearError: clearError
4281        };
4282
4283    })(); // End validateForm
4284
4285    // Check for any number of classes
4286    $.fn.hasAnyClass = function () {
4287        for (var i = 0; i < arguments.length; i++) {
4288            if (this.hasClass(arguments[i])) {
4289                return true;
4290            }
4291        }
4292        return false;
4293    };
4294
4295    // Calendars
4296    function hideCalendars() {
4297        if ($('.Zebra_DatePicker').length) {
4298            if ($('.Zebra_DatePicker').hasClass('dp_visible')) {
4299                $('.Zebra_DatePicker').removeClass('dp_visible').addClass('dp_hidden');
4300            }
4301        }
4302    }
4303
4304    $(window).scroll(function () {
4305        hideCalendars();
4306    });
4307
4308    // Prevent Flicker of Unstyled Content (FOUC)
4309    $(window).on('load', function () {
4310        noFouc();
4311    });
4312
4313    function noFouc() {
4314        $('.no-fouc').removeClass('no-fouc');
4315    }
4316
4317// --------------------------------------------------------------------------
4318// --------------------------------------- Guest Experience Survey Functions
4319// --------------------------------------------------------------------------
4320
4321    // Cobblestone Guest Experience Survey Functions
4322    // by Jeramiah Harland
4323    // Updated to TripAdvisor 2/1/17
4324
4325    // Ajax to get comments
4326    if ($('#ges').length) {
4327
4328        // Global variables
4329        var gesArray;
4330        var gesCurrent = -1;
4331        var gesInterval = setInterval(gesTimer, 10000);
4332
4333    }
4334
4335    function setGesArray(data) {
4336
4337        var commentArray = [];
4338        $.each(data.reviews, function () {
4339            if (this.rating > 3) {
4340                var comment = [];
4341                comment.title = $.trim(removeEmojis(this.title));
4342                comment.rating = this.rating;
4343                comment.rating_image_url = this.rating_image_url;
4344                comment.username = this.user.username;
4345                comment.location = this.user.user_location.name;
4346                comment.text = this.text;
4347                comment.url = this.url;
4348                commentArray.push(comment);
4349            }
4350        });
4351
4352        // Set the global variable
4353        gesArray = commentArray;
4354        // Initial cycle
4355        cycleGes();
4356    }
4357
4358    function cycleGes() {
4359
4360        if (typeof gesArray !== "undefined" && gesArray !== null && gesArray.length > 0 && $.isArray(gesArray)) {
4361
4362            // Random number between 0 and the length of the array
4363            var i = randomInt(0, gesArray.length - 1);
4364
4365            while (gesCurrent !== -1 && i === gesCurrent && gesArray.length !== 1) {
4366                i = randomInt(0, gesArray.length - 1);
4367            }
4368
4369            // Set the current index so that we know not to show it twice in a row
4370            gesCurrent = i;
4371
4372            // Set content variables
4373            // gesArray is a global variable
4374            var content = gesArray[i];
4375
4376            // If the content is visible, fade it out, set it, fade it in
4377            if ($('#ges .content').is(':visible')) {
4378                $('#ges .content').fadeOut(function () {
4379                    setContent(content);
4380                });
4381            } else {
4382                // If the content is not visible, set it and fade GES in
4383                // Add class loaded so that CSS3 transitions work after initial load
4384                setContent(content, function () {
4385                    $('#ges').fadeIn(function () {
4386                        $(this).addClass('loaded');
4387                    });
4388                });
4389            }
4390
4391        } else {
4392            $('#ges').hide();
4393        }
4394
4395    }
4396
4397    function closeGes() {
4398        $('#ges').removeClass('loaded').fadeOut(function () {
4399            clearInterval(gesInterval);
4400        });
4401    }
4402
4403    function setContent(content, callback) {
4404
4405        $('#ges .content').fadeOut(function () {
4406            // Set html of content elements
4407            $('#ges .content .title').html('"' + content.title + '"');
4408            if (content.title.length > 28) {
4409                $('#ges .content .title').addClass('long-title');
4410            } else {
4411                $('#ges .content .title').removeClass('long-title');
4412            }
4413            $('#ges .content .rating-image').attr({
4414                src: content.rating_image_url,
4415                alt: 'TripAdivsor Rating of ' + content.rating
4416
4417            });
4418
4419            var usernameLocation = content.username;
4420            if (content.location !== null) {
4421                usernameLocation += ' - ' + content.location;
4422            }
4423
4424            $('#ges .content .username-location').html(usernameLocation);
4425
4426            var readmore = '<a class="read-more track-read-more" target="_blank" href="' + content.url + '">[Read More]</a>';
4427
4428            $('#ges .content .text').html(content.text + ' ').append(readmore);
4429
4430            $('#ges .content').fadeIn(function () {
4431                // Optional Callback function
4432                if (typeof callback === 'function' && callback()) {
4433                    callback();
4434                }
4435            });
4436        });
4437    }
4438
4439    function randomInt(minimum, maximum) {
4440        var _min = Number(minimum);
4441        var _max = Number(maximum);
4442        return Math.floor(Math.random() * (_max - _min + 1) + _min);
4443    }
4444
4445    function gesTimer() {
4446        cycleGes();
4447    }
4448
4449    $(document).on('mouseenter', '#ges', function () {
4450        clearInterval(gesInterval);
4451    });
4452
4453    $(document).on('mouseleave', '#ges', function () {
4454        if ($('#ges').is(':visible') && !$('#page-overlay').is(':visible')) {
4455            gesInterval = setInterval(gesTimer, 10000);
4456        }
4457    });
4458
4459    $(document).on('click', '#ges .content', function (e) {
4460        if (!$(e.target).hasClass('read-more')) {
4461            trackEvent('Interaction', 'click-cycle-review', 'Cycled TripAdvisor Review on Slider');
4462            cycleGes();
4463        }
4464    });
4465
4466    $(document).on('click', '#ges .icon-close', function () {
4467        closeGes();
4468    });
4469
4470    $(document).on('click', '#full-comment-lightbox .icon-close', function () {
4471        gesInterval = setInterval(gesTimer, 10000);
4472    });
4473
4474
4475// --------------------------------------------------------------------------
4476// ------------------------------------------------------ Analytics Tracking
4477// --------------------------------------------------------------------------
4478
4479    function virtualPageView(page) {
4480
4481        // ga is used by Google Analytics
4482
4483        page = cleanTitle(page);
4484
4485        try {
4486
4487            if (typeof (ga) === 'undefined') {
4488                throw 'analytics undefined';
4489            } else {
4490                // Send virtual pageview
4491                ga('send', {
4492                    hitType: 'pageView',
4493                    page: 'virtual/' + page
4494                });
4495            }
4496
4497        } catch (err) {
4498
4499            console.log(err);
4500
4501        }
4502
4503    }
4504
4505    function trackEvent(category, action, label, value, interaction) {
4506
4507        // ga is used by Google Analytics
4508
4509        // Optional
4510        value = value;
4511        if (typeof value === 'undefined' || !isInt(value)) {
4512            value = 0;
4513        }
4514
4515        // Optional
4516        interaction = interaction;
4517        if (typeof interaction === 'undefined') {
4518            interaction = true;
4519        }
4520
4521        try {
4522
4523            if (typeof (ga) === 'undefined') {
4524                throw 'analytics undefined';
4525            } else {
4526                // Track event
4527                ga('send', {
4528                    hitType: 'event',
4529                    eventCategory: category,
4530                    eventAction: action,
4531                    eventLabel: label,
4532                    eventValue: value,
4533                    nonInteraction: interaction
4534                });
4535            }
4536
4537        } catch (err) {
4538
4539            console.log(err);
4540
4541        }
4542
4543    }
4544
4545    // Track event for phone calls from mobile
4546    $('body').on('click', '.track-call', function (e) {
4547        trackEvent('Reservations', 'call-site', 'Call From Site', 5);
4548    });
4549
4550    $('body').on('click', '.track-book', function (e) {
4551        trackEvent('Reservations', 'click-book', 'Clicked Direct Link To Booking', 5);
4552    });
4553
4554    // Track event for modify reservation desktop
4555    $('body').on('click', '.track-modify', function (e) {
4556        trackEvent('Reservations', 'click-modify', 'Clicked Modify Reservation', 1);
4557    });
4558
4559    // Track event for email clicks
4560    $('body').on('click', '.track-email', function (e) {
4561        trackEvent('Information Request', 'email-link', 'Clicked Email Link', 1);
4562    });
4563
4564    // Track event for footer menu
4565    $('body').on('click', '#footer-menu-navigation a', function (e) {
4566        trackEvent('Navigation', 'click-footer', 'Clicked Link In Footer');
4567    });
4568
4569    // Track event for property main menu menu-mobile-property-navigation
4570    $('body').on('click', '#menu-main-property-navigation a', function (e) {
4571        trackEvent('Navigation', 'click-property-menu', 'Clicked Link In Property Menu');
4572    });
4573
4574    // Track event for property mobile menu
4575    $('body').on('click', '#menu-mobile-property-navigation a', function (e) {
4576        trackEvent('Navigation', 'click-property-mobile-menu', 'Clicked Link In Property Mobile Menu');
4577    });
4578
4579    // Track event for default menu (home mobile, 404, map)
4580    $('body').on('click', '#menu-default-navigation a', function (e) {
4581        trackEvent('Navigation', 'click-default-menu', 'Clicked Link In Default Menu');
4582    });
4583
4584    // Track event for social media
4585    $('body').on('click', '.track-facebook', function (e) {
4586        trackEvent('Social Media', 'click-fb', 'Clicked Facebook');
4587    });
4588
4589    $('body').on('click', '.track-twitter', function (e) {
4590        trackEvent('Social Media', 'click-twitter', 'Clicked Twitter');
4591    });
4592
4593    $('body').on('click', '.track-google_plus', function (e) {
4594        trackEvent('Social Media', 'click-gplus', 'Clicked Google Plus');
4595    });
4596
4597    $('body').on('click', '.track-linkedin', function (e) {
4598        trackEvent('Social Media', 'click-linkedin', 'Clicked LinkedIn');
4599    });
4600
4601    $('body').on('click', '.track-pinterest', function (e) {
4602        trackEvent('Social Media', 'click-pinterest', 'Clicked Pinterest');
4603    });
4604
4605    $('body').on('click', '.track-youtube', function (e) {
4606        trackEvent('Social Media', 'click-youtube', 'Clicked YouTube');
4607    });
4608
4609    // Track event for custom link inside of homepage or property slider
4610    $('body').on('click', '.track-custom-link', function (e) {
4611        trackEvent('Interaction', 'click-custom', 'Clicked Link In Slider');
4612    });
4613
4614    // Track event for what's new section
4615    $('body').on('click', '.track-whats-new', function (e) {
4616        var targetTitle = cleanTitle($(this).closest('.whats-new-image').find('h3').text());
4617        if (typeof (targetTitle) === 'undefined' || targetTitle === '') {
4618            targetTitle = 'whats-new';
4619        }
4620        trackEvent('Navigation', 'click-' + targetTitle, 'Clicked Link In Whats New', 1);
4621    });
4622
4623    // Track event for coming soon
4624    $('body').on('click', '.track-coming-soon', function (e) {
4625        var targetTitle = cleanTitle($(this).find('.location').text());
4626        if (typeof (targetTitle) === 'undefined' || targetTitle === '') {
4627            targetTitle = 'coming-soon';
4628        }
4629        trackEvent('Navigation', 'click-' + targetTitle, 'Clicked Link In Coming Soon', 1);
4630    });
4631
4632    // Track event for homepage 'Find a cobblestone' button
4633    $('body').on('click', '.track-find', function (e) {
4634        trackEvent('Navigation', 'click-find', 'Clicked Find A Cobblestone');
4635    });
4636
4637    // Track event for homepage 'Franchise Opportunities' button
4638    $('body').on('click', '.track-franchise', function (e) {
4639        trackEvent('Navigation', 'click-franchise', 'Clicked Franchise Opportunities');
4640    });
4641
4642    // Track event for navigation from map
4643    $('body').on('click', '.track-visit-page', function (e) {
4644        trackEvent('Navigation', 'click-visit-page', 'Clicked Visit Page');
4645    });
4646
4647    $('body').on('click', '.track-logo-link', function (e) {
4648        trackEvent('Navigation', 'click-logo-link', 'Clicked Logo Link');
4649    });
4650
4651    $('body').on('click', '.track-map-it', function (e) {
4652        trackEvent('Navigation', 'click-map-it', 'Clicked Map It Link');
4653    });
4654
4655    $('body').on('click', '.track-read-review', function (e) {
4656        trackEvent('Navigation', 'click-read-review', 'Clicked TripAdvisor Read Review Link');
4657    });
4658
4659    $('body').on('click', '.track-write-review', function (e) {
4660        trackEvent('Navigation', 'click-write-review', 'Clicked TripAdvisor Write Review Link');
4661    });
4662
4663    // TripAdvisor comment events
4664    $('body').on('click', '.track-read-more', function (e) {
4665        trackEvent('Navigation', 'click-ta-read-more', 'Clicked Read More in TripAdvisor Review');
4666    });
4667
4668// --------------------------------------------------------------------------
4669// -------------------------------------------------------------- Sliders
4670// --------------------------------------------------------------------------
4671
4672    // Home Page Slider
4673    // On init and afterChange, fade in any hidden custom slides
4674    $("#homepage-slider .slider").on("init", function (event, slick) {
4675        scrollingFeatures.updateHeight();
4676        $("#homepage-slider .slider .slick-active").find('.custom-slide').fadeIn();
4677    });
4678    $("#homepage-slider .slider").on("afterChange", function (event, slick, currentSlide) {
4679        $("#homepage-slider .slider .slick-active").find('.custom-slide').fadeIn();
4680    });
4681
4682    $(window).on('load', function () {
4683
4684        // Initialize Home Page Slider
4685        $("#homepage-slider .slider").slick({
4686            autoplay: true,
4687            loop: true,
4688            slidesToShow: 1,
4689            slidesToScroll: 1,
4690            arrows: false,
4691            dots: true,
4692            autoplaySpeed: 6000,
4693            speed: 1000,
4694            fade: true,
4695            accessibility: false,
4696            pauseOnHover: true
4697        });
4698
4699        $("#homepage-hero .slider .slick-active").addClass("loaded");
4700
4701    });
4702
4703    // Property Page Slider
4704    // On init and afterChange, fade in any hidden custom slides
4705    $("#property-slider .slider").on("init", function (event, slick) {
4706        scrollingFeatures.updateHeight();
4707        $("#property-slider .slider .slick-active").find('.custom-slide').fadeIn();
4708    });
4709    $("#property-slider .slider").on("afterChange", function (event, slick, currentSlide) {
4710        $("#property-slider .slider .slick-active").find('.custom-slide').fadeIn();
4711    });
4712
4713    $(window).on('load', function () {
4714
4715        // Initialize Property Page Slider
4716        $("#property-slider .slider").slick({
4717            autoplay: true,
4718            loop: true,
4719            slidesToShow: 1,
4720            slidesToScroll: 1,
4721            arrows: false,
4722            dots: true,
4723            autoplaySpeed: 6000,
4724            speed: 1000,
4725            fade: true,
4726            accessibility: false,
4727            pauseOnHover: true,
4728            useTransform: true
4729        });
4730
4731        $("#property-slider, #property-slider .slider .slick-active").addClass("loaded");
4732
4733    });
4734
4735// --------------------------------------------------------------------------
4736// ------------------------------------------------------ Coming Soon Slider
4737// --------------------------------------------------------------------------
4738
4739    var comingSoon = (function () {
4740
4741        function init() {
4742            if (typeof (jsData) !== "undefined" && jsData !== null) {
4743
4744                $.get(jsData.ajaxURL,
4745                    {
4746                        action: 'get_coming_soon',
4747                    },
4748                    function (response) {
4749                        createComingSoon(response);
4750                    });
4751            }
4752        }
4753
4754        function createComingSoon(response) {
4755
4756            $.each(response, function (i, property) {
4757
4758                var a = $('<a>').attr({
4759                    href: property.link
4760                });
4761
4762                var img = $('<img>').attr({
4763                    src: property.alt_logo,
4764                    alt: property.type
4765                }).appendTo(a);
4766
4767                var location = $('<span>').attr({
4768                    class: 'location cobble-font',
4769                }).text(property.city + ', ' + property.state).appendTo(a);
4770
4771                var description = $('<div>').attr({
4772                    class: 'description',
4773                }).text(property.status_description).appendTo(a);
4774
4775                $('<div>').html(a).appendTo('#coming-soon .slider');
4776
4777                $('<div class="coming-soon-property">').html(a.clone()).appendTo('#coming-soon .mobile-properties-list');
4778
4779            });
4780
4781            initSlider();
4782
4783        }
4784
4785        function initSlider() {
4786            $('#coming-soon .slider').slick({
4787                autoplay: true,
4788                loop: true,
4789                slidesToShow: 4,
4790                slidesToScroll: 1,
4791                arrows: true,
4792                dots: false,
4793                autoplaySpeed: 6000,
4794                speed: 1000,
4795                pauseOnHover: true
4796            });
4797        }
4798
4799        $('#coming-soon .slider').on('init', function (event, slick) {
4800            $('#coming-soon .loader').fadeOut(function () {
4801                $('#coming-soon .slider').css('visibility', 'visible').fadeIn();
4802            });
4803        });
4804
4805        // return
4806        return {
4807            init: init
4808        };
4809
4810    })(); // End comingSoon
4811
4812    if ($('#coming-soon').length) {
4813        comingSoon.init();
4814    }
4815
4816// triggering a tag for modify reservation
4817    if ($('a.modify.track-modify').attr('data-ivhtravel') == 'WebRezPro' || $('a.modify.track-modify').attr('data-ivhtravel') == 'IVHTravel') {
4818        $('a.track-modify').attr('href', '#');
4819        $('a.track-modify').on('click', function () {
4820            $('span.book-now-form').trigger('click');
4821            return false;
4822        });
4823    }
4824}); // End Document Ready

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.