PageSourceSearch

https://corinthianmarine.com/wp-content/plugins/gwd-functions/assets/js/storelocator.js?ver=1791452774

js corinthianmarine.com collected 2026-10-08 09:47:17 UTC 122,853 bytes, 3,709 lines download raw bytes

vendor: 41,733 bytes, lines 1-1247
1/*! jQuery Google Maps Store Locator - v3.3.0 - 2023-12-10
2* http://www.bjornblog.com/web/jquery-store-locator-plugin
3* Copyright (c) 2023 Bjorn Holine; Licensed MIT */
4
5;(function ($, window, document, undefined) {
6	'use strict';
7
8	var pluginName = 'storeLocator';
9	var googleMapsScriptIsInjected = false;
10	var googleMapsAPIPromise = null;
11
12	// Only allow for one instantiation of this script
13	if (typeof $.fn[pluginName] !== 'undefined') {
14		return;
15	}
16
17	// Variables used across multiple methods
18	var $this, map, listTemplate, infowindowTemplate, dataTypeRead, originalOrigin, originalData, originalZoom, dataRequest, searchInput, addressInput, olat, olng, storeNum, directionsDisplay, directionsService, prevSelectedMarkerBefore, prevSelectedMarkerAfter, firstRun, reload, nameAttrs, originalFilterVals;
19	var featuredset = [], locationset = [], normalset = [], markers = [];
20	var filters = {}, locationData = {}, GeoCodeCalc = {}, mappingObj = {}, disabledFilterVals = {};
21
22	// Create the defaults once. DO NOT change these settings in this file - settings should be overridden in the plugin call
23    var defaults = {
24        'ajaxData'                   : null,
25        'altDistanceNoResult'        : false,
26        'autoComplete'               : false,
27        'autoCompleteDisableListener': false,
28        'autoCompleteOptions'        : {},
29        'autoGeocode'                : false,
30        'bounceMarker'               : true, // Deprecated.
31        'catMarkers'                 : null,
32        'dataLocation'               : '/wp-admin/admin-ajax.php?action=dealer_locations_list',
33        'dataRaw'                    : null,
34        'dataType'                   : 'json',
35        'debug'                      : false,
36        'defaultLat'                 : null,
37        'defaultLng'                 : null,
38        'defaultLoc'                 : false,
39        'disableAlphaMarkers'        : false,
40        'distanceAlert'              : 60,
41        'dragSearch'                 : false,
42        'exclusiveFiltering'         : false,
43        'exclusiveTax'               : null,
44        'featuredDistance'           : null,
45        'featuredLocations'          : false,
46        'fullMapStart'               : false,
47        'fullMapStartBlank'          : false,
48        'fullMapStartListLimit'      : false,
49        'infoBubble'                 : null,
50        'inlineDirections'           : false,
51        'lengthUnit'                 : 'm',
52        'listColor1'                 : '#ffffff',
53        'listColor2'                 : '#eeeeee',
54        'loading'                    : false,
55        'locationsPerPage'           : 1000,
56        'mapSettings'                : {
57            mapTypeId: google.maps.MapTypeId.ROADMAP, 
58            zoom     : 8
59        },
60        'markerCluster'              : null,
61        'markerImg'                  : null,
62        'markerDim'                  : null,
63        'maxDistance'                : false,
64        'modal'                      : false,
65        'nameAttribute'              : 'name',
66        'nameSearch'                 : false,
67        'noForm'                     : false,
68        'openNearest'                : false,
69        'originMarker'               : false,
70        'originMarkerDim'            : null,
71        'originMarkerImg'            : null,
72        'pagination'                 : false,
73        'querystringParams'          : false,
74        'selectedMarkerImg'          : null,
75        'selectedMarkerImgDim'       : null,
76        'sessionStorage'             : false,
77        'slideMap'                   : true,
78        'sortBy'                     : {method:'numeric', order:'asc', prop:'id'},
79        'storeLimit'                 : 26,
80        'taxonomyFilters'            : null,
81        'visibleMarkersList'         : false,
82        'xmlElement'                 : 'marker',
83        // HTML elements
84        'addressID'                  : 'bh-sl-address',
85        'closeIcon'                  : 'bh-sl-close-icon',
86        'formContainer'              : 'bh-sl-form-container',
87        'formID'                     : 'bh-sl-user-location',
88        'geocodeID'                  : null,
89        'lengthSwapID'               : 'bh-sl-length-swap',
90        'loadingContainer'           : 'bh-sl-loading',
91        'locationList'               : 'bh-sl-loc-list',
92        'mapID'                      : 'bh-sl-map',
93        'maxDistanceID'              : 'bh-sl-maxdistance',
94        'modalContent'               : 'bh-sl-modal-content',
95        'modalWindow'                : 'bh-sl-modal-window',
96        'orderID'                    : 'bh-sl-order',
97        'overlay'                    : 'bh-sl-overlay',
98        'regionID'                   : 'bh-sl-region',
99        'searchID'                   : 'bh-sl-search',
100        'sortID'                     : 'bh-sl-sort',
101        'taxonomyFiltersContainer'   : 'bh-sl-filters-container',
102        // Templates
103        'infowindowTemplatePath'     : '/wp-content/plugins/gwd-functions/assets/js/templates/standard/infowindow-description.html',
104        'listTemplatePath'           : '/wp-content/plugins/gwd-functions/assets/js/templates/standard/location-list-description.html',
105        'KMLinfowindowTemplatePath'  : '/wp-content/plugins/gwd-functions/assets/js/templates/kml/kml-infowindow-description.html',
106        'KMLlistTemplatePath'        : '/wp-content/plugins/gwd-functions/assets/js/templates/kml/kml-location-list-description.html',
107        'listTemplateID'             : null,
108        'infowindowTemplateID'       : null,
109        // Callbacks
110        'callbackAutoGeoSuccess'     : null,
111        'callbackBeforeSend'         : null,
112        'callbackCloseDirections'    : null,
113        'callbackCreateMarker'       : null,
114        'callbackDirectionsRequest'  : null,
115        'callbackFilters'            : null,
116        'callbackFormVals'           : null,
117        'callbackGeocodeRestrictions': null,
118        'callbackJsonp'              : null,
119        'callbackListClick'          : null,
120        'callbackMapSet'             : null,
121        'callbackMarkerClick'        : null,
122        'callbackModalClose'         : null,
123        'callbackModalOpen'          : null,
124        'callbackModalReady'         : null,
125        'callbackNearestLoc'         : null,
126        'callbackNoResults'          : null,
127        'callbackNotify'             : null,
128        'callbackOrder'              : null,
129        'callbackPageChange'         : null,
130        'callbackRegion'             : null,
131        'callbackSorting'            : null,
132        'callbackSuccess'            : null,
133        // Language options
134        'addressErrorAlert'          : 'Unable to find address',
135        'autoGeocodeErrorAlert'      : 'Automatic location detection failed. Please fill in your address or zip code.',
136        'distanceErrorAlert'         : 'Unfortunately, our closest location is more than ',
137        'kilometerLang'              : 'kilometer',
138        'kilometersLang'             : 'kilometers',
139        'mileLang'                   : 'mile',
140        'milesLang'                  : 'miles',
141        'noResultsTitle'             : 'No results',
142        'noResultsDesc'              : 'No locations were found with the given criteria. Please modify your selections or input.',
143        'nextPage'                   : 'Next »',
144        'prevPage'                   : '« Prev'
145    };
146
147	// Plugin constructor
148	function Plugin(element, options) {
149		$this = $(element);
150		this.element = element;
151		this.settings = $.extend({}, defaults, options);
152		this._defaults = defaults;
153		this._name = pluginName;
154
155		// Load Google Maps API when lazy load is enabled.
156		if (this.settings.lazyLoadMap && this.settings.apiKey !== null && typeof google === 'undefined') {
157			var _this = this;
158			var optionsQuery = {};
159			var loadMap = false;
160
161			// Autocomplete.
162			if (this.settings.autoComplete === true) {
163				optionsQuery.libraries = 'places';
164			}
165
166			// Allow callback to resolve map loading when set.
167			if (this.settings.callbackBeforeMapInject) {
168				new Promise(function (resolve, reject) {
169					_this.settings.callbackBeforeMapInject.call(this, options, resolve);
170				}).then(function () {
171					_this.triggerMapLoad(optionsQuery)
172				});
173			} else {
174				_this.triggerMapLoad(optionsQuery)
175			}
176		} else {
177			this.init();
178		}
179	}
180
181	// Avoid Plugin.prototype conflicts
182	$.extend(Plugin.prototype, {
183
184		/**
185		 * Init function
186		 */
187		init: function () {
188			var _this = this;
189			this.writeDebug('init');
190
191			// Calculate geocode distance functions
192			if (this.settings.lengthUnit === 'km') {
193				// Kilometers
194				GeoCodeCalc.EarthRadius = 6367.0;
195			}
196			else {
197				// Default is miles
198				GeoCodeCalc.EarthRadius = 3956.0;
199			}
200
201			// KML is read as XML
202			if (this.settings.dataType === 'kml') {
203				dataTypeRead = 'xml';
204			}
205			else {
206				dataTypeRead = this.settings.dataType;
207			}
208
209			// Add directions panel if enabled
210			if (this.settings.inlineDirections === true) {
211				$('.' + this.settings.locationList).prepend('<div class="bh-sl-directions-panel"></div>');
212			}
213
214			// Save the original zoom setting so it can be retrieved if taxonomy filtering resets it
215			originalZoom = this.settings.mapSettings.zoom;
216
217			// Add Handlebars helper for handling URL output
218			Handlebars.registerHelper('niceURL', function(url) {
219				if (url) {
220					return url.replace('https://', '').replace('http://', '');
221				}
222			});
223
224			// Handle distance changes on select
225			if (this.settings.maxDistance === true) {
226				this.distanceFiltering();
227			}
228
229			// Do taxonomy filtering if set
230			if (this.settings.taxonomyFilters !== null) {
231				this.taxonomyFiltering();
232			}
233
234			// Do sorting and ordering if set.
235			this.sorting();
236			this.order();
237
238			// Add modal window divs if set
239			if (this.settings.modal === true) {
240				// Clone the filters if there are any so they can be used in the modal
241				if (this.settings.taxonomyFilters !== null) {
242					// Clone the filters
243					$('.' + this.settings.taxonomyFiltersContainer).clone(true, true).prependTo($this);
244				}
245
246				$this.wrap('<div class="' + this.settings.overlay + '"><div class="' + this.settings.modalWindow + '"><div class="' + this.settings.modalContent + '">');
247				$('.' + this.settings.modalWindow).prepend('<div class="' + this.settings.closeIcon + '"></div>');
248				$('.' + this.settings.overlay).hide();
249			}
250
251			// Set up Google Places autocomplete if it's set to true
252			if (this.settings.autoComplete === true) {
253				var searchInput = document.getElementById(this.settings.addressID);
254				var autoPlaces = new google.maps.places.Autocomplete(searchInput, this.settings.autoCompleteOptions);
255
256				// Add listener when autoComplete selection changes.
257				if (this.settings.autoComplete === true && this.settings.autoCompleteDisableListener !== true) {
258					autoPlaces.addListener('place_changed', function(e) {
259						_this.processForm(e);
260					});
261				}
262			}
263
264			// Load the templates and continue from there
265			this._loadTemplates();
266		},
267
268		/**
269		 * Trigger async map loading
270		 */
271		triggerMapLoad: function(optionsQuery) {
272			this.writeDebug('triggerMapLoad');
273			var _this = this;
274
275			this.loadMapsAPI(this.settings.apiKey, optionsQuery)
276				.then(function (map) {
277					_this.map = map;
278					_this.init();
279				});
280		},
281
282		/**
283		 * Inject Google Maps script
284		 *
285		 * @param {Object} options Options query object to pass as query string parameters to Google Maps.
286		 */
287		injectGoogleMapsScript: function (options) {
288			this.writeDebug('injectGoogleMapsScript');
289			options = (typeof options !== 'undefined') ?  options : {};
290
291			if (googleMapsScriptIsInjected) {
292				throw new Error('Google Maps API is already loaded.');
293			}
294
295			var optionsQuery = Object.keys(options)
296				.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(options[k]))
297				.join('&');
298			var apiURL = 'https://maps.googleapis.com/maps/api/js?region=US&' + optionsQuery;
299			var mapScript = document.createElement('script');
300			mapScript.setAttribute('src', apiURL);
301			mapScript.setAttribute('async', '');
302			mapScript.setAttribute('defer', '');
303
304			// Append the script to the document head.
305			document.head.appendChild(mapScript);
306			googleMapsScriptIsInjected = true;
307		},
308
309		/**
310		 * Load Google Maps API
311		 *
312		 * @param {string} apiKey Google Maps JavaScript API key.
313		 * @param {Object} options Options query object to pass as query string parameters to Google Maps.
314		 *
315		 * @returns {Promise<Object,null>}
316		 */
317		loadMapsAPI: function (apiKey, options) {
318			this.writeDebug('loadMapsAPI');
319			options = (typeof options !== 'undefined') ?  options : {};
320			var _this = this;
321
322			if (!googleMapsAPIPromise) {
323				googleMapsAPIPromise = new Promise(function (resolve, reject) {
324					try {
325						window.onGoogleMapsAPILoaded = resolve;
326
327						_this.injectGoogleMapsScript({
328							key: apiKey,
329							callback: 'onGoogleMapsAPILoaded',
330							...options,
331						});
332					} catch (error) {
333						reject(error);
334					}
335				}).then(function () { window.google.maps });
336			}
337
338			return googleMapsAPIPromise;
339		},
340
341		/**
342		 * Destroy
343		 * Note: The Google map is not destroyed here because Google recommends using a single instance and reusing it
344		 * (it's not really supported)
345		 */
346		destroy: function () {
347			this.writeDebug('destroy');
348			// Reset
349			this.reset();
350			var $mapDiv = $('#' + this.settings.mapID);
351
352			// Remove marker event listeners
353			if (markers.length) {
354				for(var i = 0; i <= markers.length; i++) {
355					google.maps.event.removeListener(markers[i]);
356				}
357			}
358
359			// Remove markup
360			$('.' + this.settings.locationList + ' ul').empty();
361			if ($mapDiv.hasClass('bh-sl-map-open')) {
362				$mapDiv.empty().removeClass('bh-sl-map-open');
363			}
364
365			// Remove modal markup
366			if (this.settings.modal === true) {
367				$('. ' + this.settings.overlay).remove();
368			}
369
370			// Remove map style from container
371			$mapDiv.attr('style', '');
372
373			// Hide map container
374			$this.hide();
375			// Remove data
376			$.removeData($this.get(0));
377			// Remove namespaced events
378			$(document).off(pluginName);
379			// Unbind plugin
380			$this.unbind();
381		},
382
383		/**
384		 * Reset function
385		 * This method clears out all the variables and removes events. It does not reload the map.
386		 */
387		reset: function () {
388			this.writeDebug('reset');
389			locationset = [];
390			featuredset = [];
391			normalset = [];
392			markers = [];
393			firstRun = false;
394			$(document).off('click.'+pluginName, '.' + this.settings.locationList + ' li');
395
396			if ( $('.' + this.settings.locationList + ' .bh-sl-close-directions-container').length ) {
397				$('.bh-sl-close-directions-container').remove();
398			}
399
400			if (this.settings.inlineDirections === true) {
401				// Remove directions panel if it's there
402				var $adp = $('.' + this.settings.locationList + ' .adp');
403				if ( $adp.length > 0 ) {
404					$adp.remove();
405					$('.' + this.settings.locationList + ' ul').fadeIn();
406				}
407				$(document).off('click', '.' + this.settings.locationList + ' li .loc-directions a');
408			}
409
410			if (this.settings.pagination === true) {
411				$(document).off('click.'+pluginName, '.bh-sl-pagination li');
412			}
413		},
414
415		/**
416		 * Reset the form filters
417		 */
418		formFiltersReset: function () {
419			this.writeDebug('formFiltersReset');
420			if (this.settings.taxonomyFilters === null) {
421				return;
422			}
423
424			var $inputs = $('.' + this.settings.taxonomyFiltersContainer + ' input'),
425				$selects = $('.' + this.settings.taxonomyFiltersContainer + ' select');
426
427			if ( typeof($inputs) !== 'object') {
428				return;
429			}
430
431			// Loop over the input fields
432			$inputs.each(function() {
433				if ($(this).is('input[type="checkbox"]') || $(this).is('input[type="radio"]')) {
434					$(this).prop('checked',false);
435				}
436			});
437
438			// Loop over select fields
439			$selects.each(function() {
440				$(this).prop('selectedIndex',0);
441			});
442		},
443
444		/**
445		 * Reload everything
446		 * This method does a reset of everything and reloads the map as it would first appear.
447		 */
448		mapReload: function() {
449			this.writeDebug('mapReload');
450			this.reset();
451			reload = true;
452
453			if (this.settings.taxonomyFilters !== null) {
454				this.formFiltersReset();
455				this.resetDisabledFilterVals();
456				this.taxonomyFiltersInit();
457			}
458
459			if ((olat) && (olng)) {
460				this.settings.mapSettings.zoom = originalZoom;
461				this.processForm();
462			}
463			else {
464				this.mapping(mappingObj);
465			}
466		},
467
468		/**
469		 * Notifications
470		 * Some errors use alert by default. This is overridable with the callbackNotify option
471		 *
472		 * @param notifyText {string} the notification message
473		 */
474		notify: function (notifyText) {
475			this.writeDebug('notify',notifyText);
476			if (this.settings.callbackNotify) {
477				this.settings.callbackNotify.call(this, notifyText);
478			}
479			else {
480				alert(notifyText);
481			}
482		},
483
484		/**
485		 * Distance calculations
486		 */
487		geoCodeCalcToRadian: function (v) {
488			this.writeDebug('geoCodeCalcToRadian',v);
489			return v * (Math.PI / 180);
490		},
491		geoCodeCalcDiffRadian: function (v1, v2) {
492			this.writeDebug('geoCodeCalcDiffRadian',arguments);
493			return this.geoCodeCalcToRadian(v2) - this.geoCodeCalcToRadian(v1);
494		},
495		geoCodeCalcCalcDistance: function (lat1, lng1, lat2, lng2, radius) {
496			this.writeDebug('geoCodeCalcCalcDistance',arguments);
497			return radius * 2 * Math.asin(Math.min(1, Math.sqrt(( Math.pow(Math.sin((this.geoCodeCalcDiffRadian(lat1, lat2)) / 2.0), 2.0) + Math.cos(this.geoCodeCalcToRadian(lat1)) * Math.cos(this.geoCodeCalcToRadian(lat2)) * Math.pow(Math.sin((this.geoCodeCalcDiffRadian(lng1, lng2)) / 2.0), 2.0) ))));
498		},
499
500		/**
501		 * Range helper function for coordinate validation
502		 *
503		 * @param min {number} minimum number allowed
504		 * @param num {number} number to check
505		 * @param max {number} maximum number allowed
506		 *
507		 * @returns {boolean}
508		 */
509		inRange: function(min, num, max){
510			this.writeDebug('inRange',arguments);
511			num = Math.abs(num);
512			return isFinite(num) && (num >= min) && (num <= max);
513		},
514
515		/**
516		 * Coordinate validation
517		 *
518		 * @param lat {number} latitude
519		 * @param lng {number} longitude
520		 *
521		 * @returns {boolean}
522		 */
523		coordinatesInRange: function (lat, lng) {
524			this.writeDebug('coordinatesInRange',arguments);
525			return this.inRange(-90, lat, 90) && this.inRange(-180, lng, 180);
526		},
527
528		/**
529		 * Check for query string
530		 *
531		 * @param param {string} query string parameter to test
532		 *
533		 * @returns {string} query string value
534		 */
535		getQueryString: function(param) {
536			this.writeDebug('getQueryString',param);
537			if(param) {
538				param = param.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
539				var regex = new RegExp('[\\?&]' + param + '=([^&#]*)'),
540					results = regex.exec(location.search);
541				return (results === null) ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
542			}
543		},
544
545		/**
546		 * Get google.maps.Map instance
547		 *
548		 * @returns {Object} google.maps.Map instance
549		 */
550		getMap: function() {
551			return this.map;
552		},
553
554		/**
555		 * Load templates via Handlebars templates in /templates or inline via IDs - private
556		 */
557		_loadTemplates: function () {
558			this.writeDebug('_loadTemplates');
559			var source;
560			var _this = this;
561			var templateError = '<div class="bh-sl-error">Error: Could not load plugin templates. Check the paths and ensure they have been uploaded. Paths will be wrong if you do not run this from a web server.</div>';
562			// Get the KML templates
563			if (this.settings.dataType === 'kml' && this.settings.listTemplateID === null && this.settings.infowindowTemplateID === null) {
564
565				// Try loading the external template files
566				$.when(
567					// KML infowindows
568					$.get(this.settings.KMLinfowindowTemplatePath, function (template) {
569						source = template;
570						infowindowTemplate = Handlebars.compile(source);
571					}),
572
573					// KML locations list
574					$.get(this.settings.KMLlistTemplatePath, function (template) {
575						source = template;
576						listTemplate = Handlebars.compile(source);
577					})
578				).then(function () {
579					// Continue to the main script if templates are loaded successfully
580					_this.locator();
581
582				}, function () {
583					// KML templates not loaded
584					$('.' + _this.settings.formContainer).append(templateError);
585					throw new Error('Could not load storeLocator plugin templates');
586				});
587			}
588			// Handle script tag template method
589			else if (this.settings.listTemplateID !== null && this.settings.infowindowTemplateID !== null) {
590				// Infowindows
591				infowindowTemplate = Handlebars.compile($('#' + this.settings.infowindowTemplateID).html());
592
593				// Locations list
594				listTemplate = Handlebars.compile($('#' + this.settings.listTemplateID).html());
595
596				// Continue to the main script
597				_this.locator();
598			}
599			// Get the JSON/XML templates
600			else {
601				// Try loading the external template files
602				$.when(
603					// Infowindows
604					$.get(this.settings.infowindowTemplatePath, function (template) {
605						source = template;
606						infowindowTemplate = Handlebars.compile(source);
607					}),
608
609					// Locations list
610					$.get(this.settings.listTemplatePath, function (template) {
611						source = template;
612						listTemplate = Handlebars.compile(source);
613					})
614				).then(function () {
615					// Continue to the main script if templates are loaded successfully
616					_this.locator();
617
618				}, function () {
619					// JSON/XML templates not loaded
620					$('.' + _this.settings.formContainer).append(templateError);
621					throw new Error('Could not load storeLocator plugin templates');
622				});
623			}
624		},
625
626		/**
627		 * Primary locator function runs after the templates are loaded
628		 */
629		locator: function () {
630			this.writeDebug('locator');
631			if (this.settings.slideMap === true) {
632				// Let's hide the map container to begin
633				$this.hide();
634			}
635
636			this._start();
637			this._formEventHandler();
638		},
639
640		/**
641		 * Form event handler setup - private
642		 */
643		_formEventHandler: function () {
644			this.writeDebug('_formEventHandler');
645			var _this = this;
646			// ASP.net or regular submission?
647			if (this.settings.noForm === true) {
648				$(document).on('click.'+pluginName, '.' + this.settings.formContainer + ' button', function (e) {
649					_this.processForm(e);
650				});
651				$(document).on('keydown.'+pluginName, function (e) {
652					if (e.keyCode === 13 && $('#' + _this.settings.addressID).is(':focus')) {
653						_this.processForm(e);
654					}
655				});
656			}
657			else {
658				$(document).on('submit.'+pluginName, '#' + this.settings.formID, function (e) {
659					_this.processForm(e);
660				});
661			}
662
663			// Reset button trigger.
664			if ($('.bh-sl-reset').length && $('#' + this.settings.mapID).length) {
665				$(document).on('click.' + pluginName, '.bh-sl-reset', function () {
666					_this.mapReload();
667				});
668			}
669
670			// Track changes to the address search field.
671			$('#' + this.settings.addressID).on('change.'+pluginName, function () {
672				originalFilterVals = undefined;
673				disabledFilterVals = {};
674
675				// Unset origin tracking if input field is removed.
676				if (
677					$.trim($('#' + _this.settings.addressID).val()) === '' &&
678					(typeof searchInput === 'undefined' || searchInput === '')
679				) {
680
681						// Reset the origin, mapping object, and disabled filter values.
682						if (_this.settings.taxonomyFilters !== null && _this.settings.exclusiveFiltering === false) {
683							olat = undefined;
684							olng = undefined;
685							originalOrigin = undefined;
686							mappingObj = {};
687							_this.resetDisabledFilterVals();
688							_this.taxonomyFiltersInit();
689							_this.mapping(null);
690						}
691				}
692			});
693		},
694
695		/**
696		 * AJAX data request - private
697		 *
698		 * @param lat {number} latitude
699		 * @param lng {number} longitude
700		 * @param address {string} street address
701		 * @param geocodeData {object} full Google geocode results object
702		 * @param map (object} Google Maps object.
703		 *
704		 * @returns {Object} deferred object
705		 */
706		_getData: function (lat, lng, address, geocodeData, map) {
707			this.writeDebug('_getData',arguments);
708			var _this = this,
709				northEast = '',
710				southWest = '',
711				formattedAddress = '';
712
713			// Define extra geocode result info
714			if (typeof geocodeData !== 'undefined' && typeof geocodeData.geometry.bounds !== 'undefined') {
715				formattedAddress = geocodeData.formatted_address;
716				northEast = JSON.stringify( geocodeData.geometry.bounds.getNorthEast() );
717				southWest = JSON.stringify( geocodeData.geometry.bounds.getSouthWest() );
718			}
719
720			// Before send callback
721			if (this.settings.callbackBeforeSend) {
722				this.settings.callbackBeforeSend.call(this, lat, lng, address, formattedAddress, northEast, southWest, map);
723			}
724
725			// Raw data
726			if(_this.settings.dataRaw !== null) {
727				// XML
728				if( dataTypeRead === 'xml' ) {
729					return $.parseXML(_this.settings.dataRaw);
730				}
731
732				// JSON
733				else if (dataTypeRead === 'json') {
734					if (Array.isArray && Array.isArray(_this.settings.dataRaw)) {
735						return _this.settings.dataRaw;
736					}
737					else if (typeof _this.settings.dataRaw === 'string') {
738						return JSON.parse(_this.settings.dataRaw);
739					}
740					else {
741						return [];
742					}
743
744				}
745			}
746			// Remote data
747			else {
748				var d = $.Deferred();
749
750				// Loading
751				if (this.settings.loading === true) {
752					$('.' + this.settings.formContainer).append('<div class="' + this.settings.loadingContainer +'"></div>');
753				}
754
755				// Data to pass with the AJAX request
756				var ajaxData = {
757					'origLat' : lat,
758					'origLng' : lng,
759					'origAddress': address,
760					'formattedAddress': formattedAddress,
761					'boundsNorthEast' : northEast,
762					'boundsSouthWest' : southWest
763				};
764
765				// Set up extra object for custom extra data to be passed with the AJAX request
766				if (this.settings.ajaxData !== null && typeof this.settings.ajaxData === 'object') {
767					$.extend(ajaxData, this.settings.ajaxData);
768				}
769
770				// AJAX request
771				$.ajax({
772					type         : 'GET',
773					url          : this.settings.dataLocation + (this.settings.dataType === 'jsonp' ? (this.settings.dataLocation.match(/\?/) ? '&' : '?') + 'callback=?' : ''),
774					// Passing the lat, lng, address, formatted address and bounds with the AJAX request so they can optionally be used by back-end languages
775					data         : ajaxData,
776					dataType     : dataTypeRead,
777					jsonpCallback: (this.settings.dataType === 'jsonp' ? this.settings.callbackJsonp : null)
778				}).done(function(p) {
779					d.resolve(p);
780
781					// Loading remove
782					if (_this.settings.loading === true) {
783						$('.' + _this.settings.formContainer + ' .' + _this.settings.loadingContainer).remove();
784					}
785				}).fail(d.reject);
786				return d.promise();
787			}
788		},
789
790		/**
791		 * Checks for default location, full map, and HTML5 geolocation settings - private
792		 */
793		_start: function () {
794			this.writeDebug('_start');
795			var _this = this,
796					doAutoGeo = this.settings.autoGeocode,
797					latlng;
798
799			// Full map blank start
800			if (_this.settings.fullMapStartBlank !== false) {
801				var $mapDiv = $('#' + _this.settings.mapID);
802				$mapDiv.addClass('bh-sl-map-open');
803				var myOptions = _this.settings.mapSettings;
804				myOptions.zoom = _this.settings.fullMapStartBlank;
805
806				latlng = new google.maps.LatLng(this.settings.defaultLat, this.settings.defaultLng);
807				myOptions.center = latlng;
808
809				// Create the map
810				_this.map = new google.maps.Map(document.getElementById(_this.settings.mapID), myOptions);
811
812				// Re-center the map when the browser is re-sized
813        		window.addEventListener('resize', function() {
814					var center = _this.map.getCenter();
815					google.maps.event.trigger(_this.map, 'resize');
816					_this.map.setCenter(center);
817				});
818
819				// Only do this once
820				_this.settings.fullMapStartBlank = false;
821				myOptions.zoom = originalZoom;
822			} else {
823				// If a default location is set
824				if (this.settings.defaultLoc === true) {
825					this.defaultLocation();
826				}
827
828				// If there is already have a value in the address bar
829				if ($.trim($('#' + this.settings.addressID).val()) !== ''){
830					_this.writeDebug('Using Address Field');
831					_this.processForm(null);
832					doAutoGeo = false; // No need for additional processing
833				}
834				// If show full map option is true
835				else if (this.settings.fullMapStart === true && this.settings.defaultLoc === false) {
836					if ((this.settings.querystringParams === true && this.getQueryString(this.settings.addressID)) || (this.settings.querystringParams === true && this.getQueryString(this.settings.searchID)) || (this.settings.querystringParams === true && this.getQueryString(this.settings.maxDistanceID))) {
837						_this.writeDebug('Using Query String');
838						this.processForm(null);
839						doAutoGeo = false; // No need for additional processing
840					}
841					else {
842						this.mapping(null);
843					}
844				}
845			}
846
847			// HTML5 auto geolocation API option
848			if (this.settings.autoGeocode === true && doAutoGeo === true) {
849				_this.writeDebug('Auto Geo');
850
851				_this.htmlGeocode();
852			}
853
854			// HTML5 geolocation API button option
855			if (this.settings.autoGeocode !== null) {
856				_this.writeDebug('Button Geo');
857
858				$(document).on('click.'+pluginName, '#' + this.settings.geocodeID, function () {
859					_this.htmlGeocode();
860				});
861			}
862		},
863
864		/**
865		 * Geocode function used for auto geocode setting and geocodeID button
866		 */
867		htmlGeocode: function() {
868			this.writeDebug('htmlGeocode',arguments);
869			var _this = this;
870
871			if (_this.settings.sessionStorage === true && window.sessionStorage && window.sessionStorage.getItem('myGeo')){
872				_this.writeDebug('Using Session Saved Values for GEO');
873				_this.autoGeocodeQuery(JSON.parse(window.sessionStorage.getItem('myGeo')));
874				return false;
875			}
876			else if (navigator.geolocation) {
877				navigator.geolocation.getCurrentPosition(function(position){
878					_this.writeDebug('Current Position Result');
879					// To not break autoGeocodeQuery then we create the obj to match the geolocation format
880					var pos = {
881						coords: {
882							latitude : position.coords.latitude,
883							longitude: position.coords.longitude,
884							accuracy : position.coords.accuracy
885						}
886					};
887
888					// Have to do this to get around scope issues
889					if (_this.settings.sessionStorage === true && window.sessionStorage) {
890						window.sessionStorage.setItem('myGeo',JSON.stringify(pos));
891					}
892
893					// Callback
894					if (_this.settings.callbackAutoGeoSuccess) {
895						_this.settings.callbackAutoGeoSuccess.call(this, pos);
896					}
897
898					_this.autoGeocodeQuery(pos);
899				}, function(error){
900					_this._autoGeocodeError(error);
901				});
902			}
903		},
904
905		/**
906		 * Geocode function used to geocode the origin (entered location)
907		 */
908		googleGeocode: function (thisObj) {
909			thisObj.writeDebug('googleGeocode',arguments);
910			var geocoder = new google.maps.Geocoder();
911			this.geocode = function (request, callbackFunction) {
912				geocoder.geocode(request, function (results, status) {
913					if (status === google.maps.GeocoderStatus.OK) {
914						var result = {};
915						result.latitude = results[0].geometry.location.lat();
916						result.longitude = results[0].geometry.location.lng();
917						result.geocodeResult = results[0];
918						callbackFunction(result);
919					} else {
920						callbackFunction(null);
921						throw new Error('Geocode was not successful for the following reason: ' + status);
922					}
923				});
924			};
925		},
926
927		/**
928		 * Reverse geocode to get address for automatic options needed for directions link
929		 */
930		reverseGoogleGeocode: function (thisObj) {
931			thisObj.writeDebug('reverseGoogleGeocode',arguments);
932			var geocoder = new google.maps.Geocoder();
933			this.geocode = function (request, callbackFunction) {
934				geocoder.geocode(request, function (results, status) {
935					if (status === google.maps.GeocoderStatus.OK) {
936						if (results[0]) {
937							var result = {};
938							result.address = results[0].formatted_address;
939							result.fullResult = results[0];
940							callbackFunction(result);
941						}
942					} else {
943						callbackFunction(null);
944						throw new Error('Reverse geocode was not successful for the following reason: ' + status);
945					}
946				});
947			};
948		},
949
950		/**
951		 * Rounding function used for distances
952		 *
953		 * @param num {number} the full number
954		 * @param dec {number} the number of digits to show after the decimal
955		 *
956		 * @returns {number}
957		 */
958		roundNumber: function (num, dec) {
959			this.writeDebug('roundNumber',arguments);
960			return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
961		},
962
963		/**
964		 * Checks to see if the object is empty. Using this instead of $.isEmptyObject for legacy browser support
965		 *
966		 * @param obj {Object} the object to check
967		 *
968		 * @returns {boolean}
969		 */
970		isEmptyObject: function (obj) {
971			this.writeDebug('isEmptyObject',arguments);
972			for (var key in obj) {
973				if (obj.hasOwnProperty(key)) {
974					return false;
975				}
976			}
977			return true;
978		},
979
980		/**
981		 * Checks to see if all the property values in the object are empty
982		 *
983		 * @param obj {Object} the object to check
984		 *
985		 * @returns {boolean}
986		 */
987		hasEmptyObjectVals: function (obj) {
988			this.writeDebug('hasEmptyObjectVals',arguments);
989				var objTest = true;
990
991				for(var key in obj) {
992					if (obj.hasOwnProperty(key)) {
993						if (obj[key] !== '' && obj[key].length !== 0) {
994							objTest = false;
995						}
996					}
997				}
998
999				return objTest;
1000		},
1001
1002		/**
1003		 * Checks to see if only a single taxonomy group has a selected value
1004		 *
1005		 * @param obj {Object} the object to check
1006		 * @param key {string} Key value of current filter group
1007		 *
1008		 * @returns {boolean}
1009		 */
1010		hasSingleGroupFilterVal: function(obj, key) {
1011			this.writeDebug('hasSingleGroupFilterVal',arguments);
1012
1013			// Copy the object so the original doesn't change.
1014			var objCopy = Object.assign({}, obj);
1015
1016			return !this.hasEmptyObjectVals(objCopy[key]);
1017		},
1018
1019		/**
1020		 * Modal window close function
1021		 */
1022		modalClose: function () {
1023			this.writeDebug('modalClose');
1024			// Callback
1025			if (this.settings.callbackModalClose) {
1026				this.settings.callbackModalClose.call(this);
1027			}
1028
1029			// Reset the filters
1030			filters = {};
1031
1032			// Undo category selections
1033			$('.' + this.settings.overlay + ' select').prop('selectedIndex', 0);
1034			$('.' + this.settings.overlay + ' input').prop('checked', false);
1035
1036			// Hide the modal
1037			$('.' + this.settings.overlay).hide();
1038		},
1039
1040		/**
1041		 * Create the location variables - private
1042		 *
1043		 * @param loopcount {number} current marker id
1044		 */
1045		_createLocationVariables: function (loopcount) {
1046			this.writeDebug('_createLocationVariables',arguments);
1047			var value;
1048			locationData = {};
1049
1050			for (var key in locationset[loopcount]) {
1051				if (locationset[loopcount].hasOwnProperty(key)) {
1052					value = locationset[loopcount][key];
1053
1054					if (key === 'distance' || key === 'altdistance') {
1055						value = this.roundNumber(value, 2);
1056					}
1057
1058					locationData[key] = value;
1059				}
1060			}
1061		},
1062
1063		/**
1064		 * Location alphabetical sorting function
1065		 *
1066		 * @param locationsarray {array} locationset array
1067		 */
1068		sortAlpha: function(locationsarray) {
1069			this.writeDebug('sortAlpha',arguments);
1070			var property = (this.settings.sortBy.hasOwnProperty('prop') && typeof this.settings.sortBy.prop !== 'undefined') ?  this.settings.sortBy.prop : 'name';
1071
1072			if (this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
1073				locationsarray.sort(function (a, b) {
1074					return b[property].toLowerCase().localeCompare(a[property].toLowerCase());
1075				});
1076			} else {
1077				locationsarray.sort(function (a, b) {
1078					return a[property].toLowerCase().localeCompare(b[property].toLowerCase());
1079				});
1080			}
1081		},
1082
1083		/**
1084		 * Location date sorting function
1085		 *
1086		 * @param locationsarray {array} locationset array
1087		 */
1088		sortDate: function(locationsarray) {
1089			this.writeDebug('sortDate',arguments);
1090			var property = (this.settings.sortBy.hasOwnProperty('prop') && typeof this.settings.sortBy.prop !== 'undefined') ?  this.settings.sortBy.prop : 'date';
1091
1092			if (this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
1093				locationsarray.sort(function (a, b) {
1094					return new Date(b[property]).getTime() - new Date(a[property]).getTime();
1095				});
1096			} else {
1097				locationsarray.sort(function (a, b) {
1098					return new Date(a[property]).getTime() - new Date(b[property]).getTime();
1099				});
1100			}
1101		},
1102
1103		/**
1104		 * Location distance sorting function
1105		 *
1106		 * @param locationsarray {array} locationset array
1107		 * @param distanceOverride {boolean} Force sort by distance
1108		 */
1109		sortNumerically: function (locationsarray, distanceOverride) {
1110			this.writeDebug('sortNumerically',arguments);
1111			var property = (
1112				this.settings.sortBy !== null &&
1113				this.settings.sortBy.hasOwnProperty('prop') &&
1114				typeof this.settings.sortBy.prop !== 'undefined'
1115			) ?  this.settings.sortBy.prop : 'distance';
1116
1117			if (typeof distanceOverride !== 'undefined' && distanceOverride === true) {
1118				property = 'distance';
1119			}
1120
1121			if (this.settings.sortBy !== null && this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
1122				locationsarray.sort(function (a, b) {
1123					return ((b[property] < a[property]) ? -1 : ((b[property] > a[property]) ? 1 : 0));
1124				});
1125			} else {
1126				locationsarray.sort(function (a, b) {
1127					return ((a[property] < b[property]) ? -1 : ((a[property] > b[property]) ? 1 : 0));
1128				});
1129			}
1130		},
1131
1132		/**
1133		 * Alternative sorting setup
1134		 *
1135		 * @param locationsarray {array} locationset array
1136		 */
1137		sortCustom: function (locationsarray) {
1138			this.writeDebug('sortCustom',arguments);
1139
1140			// Alphabetically, date, or numeric
1141			if (this.settings.sortBy.hasOwnProperty('method') && this.settings.sortBy.method.toString() === 'alpha') {
1142				this.sortAlpha(locationsarray);
1143			} else if (this.settings.sortBy.hasOwnProperty('method') && this.settings.sortBy.method.toString() === 'date') {
1144				this.sortDate(locationsarray);
1145			} else {
1146				this.sortNumerically(locationsarray);
1147			}
1148		},
1149
1150		/**
1151		 * Run the matching between regular expression filters and string value
1152		 *
1153		 * @param filter {array} One or multiple filters to apply
1154		 * @param val {string} Value to compare
1155		 * @param inclusive {boolean} Inclusive (default) or exclusive
1156		 *
1157		 * @returns {boolean}
1158		 */
1159		filterMatching: function(filter, val, inclusive) {
1160			this.writeDebug('inclusiveFilter',arguments);
1161			inclusive = (typeof inclusive !== 'undefined') ?  inclusive : true;
1162			var applyFilters;
1163
1164			// Undefined check.
1165			if (typeof val === 'undefined') {
1166				return false;
1167			}
1168
1169			// Modify the join depending on inclusive (AND) vs exclusive (OR).
1170			if ( true === inclusive ) {
1171				applyFilters = filter.join('');
1172			} else {
1173				applyFilters = filter.join('|');
1174			}
1175
1176			if ((new RegExp(applyFilters, 'i').test(val.replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '')))) {
1177				return true;
1178			}
1179
1180			return false;
1181		},
1182
1183		/**
1184		 * Filter the data with Regex
1185		 *
1186		 * @param data {array} data array to check for filter values
1187		 * @param filters {Object} taxonomy filters object
1188		 *
1189		 * @returns {boolean}
1190		 */
1191		filterData: function (data, filters) {
1192			this.writeDebug('filterData', arguments);
1193			var filterTest = true;
1194
1195			for (var k in filters) {
1196				if (filters.hasOwnProperty(k)) {
1197					var testResults = [];
1198
1199					for (var l = 0; l < filters[k].length; l++) {
1200
1201						// Exclusive filtering
1202						if (this.settings.exclusiveFiltering === true || (this.settings.exclusiveTax !== null && Array.isArray(this.settings.exclusiveTax) && this.settings.exclusiveTax.indexOf(k) !== -1)) {
1203							testResults[l] = this.filterMatching(filters[k], data[k], false);
1204						}
1205						// Inclusive filtering
1206						else {
1207							testResults[l] = this.filterMatching(filters[k], data[k]);
1208						}
1209					}
1210
1211					// First handle name search, then standard filtering.
1212					if (typeof nameAttrs !== 'undefined' && nameAttrs.indexOf(k) !== -1 && testResults.indexOf(true) !== -1) {
1213						return true;
1214					} else {
1215						if (testResults.indexOf(true) === -1) {
1216							filterTest = false;
1217						}
1218					}
1219				}
1220			}
1221
1222			return filterTest;
1223		},
1224
1225		/**
1226		 * Build pagination numbers and next/prev links - private
1227		 *
1228		 * @param currentPage {number}
1229		 * @param totalPages {number}
1230		 *
1231		 * @returns {string}
1232		 */
1233		_paginationOutput: function(currentPage, totalPages) {
1234			this.writeDebug('_paginationOutput',arguments);
1235
1236			currentPage = parseFloat(currentPage);
1237			var output = '';
1238			var nextPage = currentPage + 1;
1239			var prevPage = currentPage - 1;
1240
1241			// Previous page
1242			if ( currentPage > 0 ) {
1243				output += '<li class="bh-sl-next-prev" data-page="' + prevPage + '">' + this.settings.prevPage + '</li>';
1244			}
1245
1246			// Add the numbers
1247			for (var p = 0;
1247 p < Math.ceil(totalPages); p++) {
1248				var n = p + 1;
1249
1250				if (p === currentPage) {
1251					output += '<li class="bh-sl-current" data-page="' + p + '">' + n + '</li>';
1252				}
1253				else {
1254					output += '<li data-page="' + p + '">' + n + '</li>';
1255				}
1256			}
1257
1258			// Next page
1259			if ( nextPage < totalPages ) {
1260				output += '<li class="bh-sl-next-prev" data-page="' + nextPage + '">' + this.settings.nextPage + '</li>';
1261			}
1262
1263			return output;
1264		},
1265
1266		/**
1267		 * Set up the pagination pages
1268		 *
1269		 * @param currentPage {number} optional current page
1270		 */
1271		paginationSetup: function (currentPage) {
1272			this.writeDebug('paginationSetup',arguments);
1273			var pagesOutput = '';
1274			var totalPages;
1275			var $paginationList = $('.bh-sl-pagination-container .bh-sl-pagination');
1276
1277			// Total pages
1278			if ( this.settings.storeLimit === -1 || locationset.length < this.settings.storeLimit ) {
1279				totalPages = locationset.length / this.settings.locationsPerPage;
1280			} else {
1281				totalPages = this.settings.storeLimit / this.settings.locationsPerPage;
1282			}
1283
1284			// Current page check
1285			if (typeof currentPage === 'undefined') {
1286				currentPage = 0;
1287			}
1288
1289			// Initial pagination setup
1290			if ($paginationList.length === 0) {
1291
1292				pagesOutput = this._paginationOutput(currentPage, totalPages);
1293			}
1294			// Update pagination on page change
1295			else {
1296				// Remove the old pagination
1297				$paginationList.empty();
1298
1299				// Add the numbers
1300				pagesOutput = this._paginationOutput(currentPage, totalPages);
1301			}
1302
1303			$paginationList.append(pagesOutput);
1304		},
1305
1306		/**
1307		 * Marker image setup
1308		 *
1309		 * @param markerUrl {string} path to marker image
1310		 * @param markerWidth {number} width of marker
1311		 * @param markerHeight {number} height of marker
1312		 *
1313		 * @returns {Object} Google Maps icon object
1314		 */
1315		markerImage: function (markerUrl, markerWidth, markerHeight) {
1316			this.writeDebug('markerImage',arguments);
1317			var markerImg;
1318
1319			// User defined marker dimensions
1320			if (typeof markerWidth !== 'undefined' && typeof markerHeight !== 'undefined') {
1321				markerImg = {
1322					url: markerUrl,
1323					size: new google.maps.Size(markerWidth, markerHeight),
1324					scaledSize: new google.maps.Size(markerWidth, markerHeight)
1325				};
1326			}
1327			// Default marker dimensions: 32px x 32px
1328			else {
1329				markerImg = {
1330					url: markerUrl,
1331					size: new google.maps.Size(32, 32),
1332					scaledSize: new google.maps.Size(32, 32)
1333				};
1334			}
1335
1336			return markerImg;
1337		},
1338
1339		/**
1340		 * Map marker setup
1341		 *
1342		 * @param point {Object} LatLng of current location
1343		 * @param name {string} location name
1344		 * @param address {string} location address
1345		 * @param letter {string} optional letter used for front-end identification and correlation between list and
1346		 *     points
1347		 * @param map {Object} the Google Map
1348		 * @param category {string} location category/categories
1349		 *
1350		 * @returns {Object} Google Maps marker
1351		 */
1352		createMarker: function (point, name, address, letter, map, category) {
1353			this.writeDebug('createMarker',arguments);
1354			var marker, markerImg, letterMarkerImg;
1355			var categories = [];
1356
1357			// Custom multi-marker image override (different markers for different categories
1358			if (this.settings.catMarkers !== null) {
1359				if (typeof category !== 'undefined') {
1360					// Multiple categories
1361					if (category.indexOf(',') !== -1) {
1362						// Break the category variable into an array if there are multiple categories for the location
1363						categories = category.split(',');
1364						// With multiple categories the color will be determined by the last matched category in the data
1365						for(var i = 0; i < categories.length; i++) {
1366							if (categories[i] in this.settings.catMarkers) {
1367								markerImg = this.markerImage(this.settings.catMarkers[categories[i]][0], parseInt(this.settings.catMarkers[categories[i]][1]), parseInt(this.settings.catMarkers[categories[i]][2]));
1368							}
1369						}
1370					}
1371					// Single category
1372					else {
1373						if (category in this.settings.catMarkers) {
vendor: 77,037 bytes, lines 1374-3709
1374							markerImg = this.markerImage(this.settings.catMarkers[category][0], parseInt(this.settings.catMarkers[category][1]), parseInt(this.settings.catMarkers[category][2]));
1375						}
1376					}
1377				}
1378			}
1379
1380			// Custom single marker image override
1381			if (this.settings.markerImg !== null) {
1382				if (this.settings.markerDim === null) {
1383					markerImg = this.markerImage(this.settings.markerImg);
1384				}
1385				else {
1386					markerImg = this.markerImage(this.settings.markerImg, this.settings.markerDim.width, this.settings.markerDim.height);
1387				}
1388			}
1389
1390			// Marker setup
1391			if (this.settings.callbackCreateMarker) {
1392				// Marker override callback
1393				marker = this.settings.callbackCreateMarker.call(this, map, point, letter, category);
1394			}
1395			else {
1396				// Create the default markers
1397				if (this.settings.disableAlphaMarkers === true || this.settings.storeLimit === -1 || this.settings.storeLimit > 26 || this.settings.catMarkers !== null || this.settings.markerImg !== null || (this.settings.fullMapStart === true && firstRun === true && (isNaN(this.settings.fullMapStartListLimit) || this.settings.fullMapStartListLimit > 26 || this.settings.fullMapStartListLimit === -1))) {
1398					marker = new google.maps.Marker({
1399						draggable: false,
1400						icon     : markerImg, // Reverts to default marker if nothing is passed
1401						map      : map,
1402						optimized: false,
1403						position : point,
1404						title    : name,
1405					});
1406				}
1407				else {
1408					// Letter markers
1409					marker = new google.maps.Marker({
1410						draggable: false,
1411						label    : letter,
1412						map      : map,
1413						optimized: false,
1414						position : point,
1415						title    : name,
1416					});
1417				}
1418			}
1419
1420			return marker;
1421		},
1422
1423		/**
1424		 * Define the location data for the templates - private
1425		 *
1426		 * @param currentMarker {Object} Google Maps marker
1427		 * @param storeStart {number} optional first location on the current page
1428		 * @param page {number} optional current page
1429		 *
1430		 * @returns {Object} extended location data object
1431		 */
1432		_defineLocationData: function (currentMarker, storeStart, page) {
1433			this.writeDebug('_defineLocationData',arguments);
1434			var indicator = '';
1435			this._createLocationVariables(currentMarker.get('id'));
1436
1437			var altDistLength,
1438				distLength;
1439
1440			if (locationData.distance <= 1) {
1441				if (this.settings.lengthUnit === 'km') {
1442					distLength = this.settings.kilometerLang;
1443					altDistLength = this.settings.mileLang;
1444				}
1445				else {
1446					distLength = this.settings.mileLang;
1447					altDistLength = this.settings.kilometerLang;
1448				}
1449			}
1450			else {
1451				if (this.settings.lengthUnit === 'km') {
1452					distLength = this.settings.kilometersLang;
1453					altDistLength = this.settings.milesLang;
1454				}
1455				else {
1456					distLength = this.settings.milesLang;
1457					altDistLength = this.settings.kilometersLang;
1458				}
1459			}
1460
1461			// Set up alpha character
1462			var markerId = currentMarker.get('id');
1463			// Use dot markers instead of alpha if there are more than 26 locations
1464			if (this.settings.disableAlphaMarkers === true || this.settings.storeLimit === -1 || this.settings.storeLimit > 26 || (this.settings.fullMapStart === true && firstRun === true && (isNaN(this.settings.fullMapStartListLimit) || this.settings.fullMapStartListLimit > 26 || this.settings.fullMapStartListLimit === -1))) {
1465				indicator = markerId + 1;
1466			}
1467			else {
1468				if (page > 0) {
1469					indicator = String.fromCharCode('A'.charCodeAt(0) + (storeStart + markerId));
1470				}
1471				else {
1472					indicator = String.fromCharCode('A'.charCodeAt(0) + markerId);
1473				}
1474			}
1475
1476			// Define location data
1477			return {
1478				location: [$.extend(locationData, {
1479					'markerid' : markerId,
1480					'marker'   : indicator,
1481					'altlength': altDistLength,
1482					'length'   : distLength,
1483					'origin'   : originalOrigin
1484				})]
1485			};
1486		},
1487
1488		/**
1489		 * Set up the list templates
1490		 *
1491		 * @param marker {Object} Google Maps marker
1492		 * @param storeStart {number} optional first location on the current page
1493		 * @param page {number} optional current page
1494		 */
1495		listSetup: function (marker, storeStart, page) {
1496			this.writeDebug('listSetup',arguments);
1497			// Define the location data
1498			var locations = this._defineLocationData(marker, storeStart, page);
1499
1500			// Set up the list template with the location data
1501			var listHtml = listTemplate(locations);
1502			$('.' + this.settings.locationList + ' > ul').append(listHtml);
1503		},
1504
1505		/**
1506		 * Change the selected marker image
1507		 *
1508		 * @param marker {Object} Google Maps marker object
1509		 */
1510		changeSelectedMarker: function (marker) {
1511			var markerImg;
1512
1513			// Reset the previously selected marker
1514			if ( typeof prevSelectedMarkerAfter !== 'undefined' ) {
1515				prevSelectedMarkerAfter.setIcon( prevSelectedMarkerBefore );
1516			}
1517
1518			// Change the selected marker icon
1519			if (this.settings.selectedMarkerImgDim === null) {
1520				markerImg = this.markerImage(this.settings.selectedMarkerImg);
1521			} else {
1522				markerImg = this.markerImage(this.settings.selectedMarkerImg, this.settings.selectedMarkerImgDim.width, this.settings.selectedMarkerImgDim.height);
1523			}
1524
1525			// Save the marker before switching it
1526			prevSelectedMarkerBefore = marker.icon;
1527
1528			marker.setIcon( markerImg );
1529
1530			// Save the marker to a variable so it can be reverted when another marker is clicked
1531			prevSelectedMarkerAfter = marker;
1532		},
1533
1534		/**
1535		 * Create the infowindow
1536		 *
1537		 * @param marker {Object} Google Maps marker object
1538		 * @param location {string} indicates if the list or a map marker was clicked
1539		 * @param infowindow Google Maps InfoWindow constructor
1540		 * @param storeStart {number}
1541		 * @param page {number}
1542		 */
1543		createInfowindow: function (marker, location, infowindow, storeStart, page) {
1544			this.writeDebug('createInfowindow',arguments);
1545			var _this = this;
1546			// Define the location data
1547			var locations = this._defineLocationData(marker, storeStart, page);
1548
1549			// Set up the infowindow template with the location data
1550			var formattedAddress = infowindowTemplate(locations);
1551
1552			// Opens the infowindow when list item is clicked
1553			if (location === 'left') {
1554				infowindow.setContent(formattedAddress);
1555				infowindow.open(marker.get('map'), marker);
1556			}
1557			// Opens the infowindow when the marker is clicked
1558			else {
1559				google.maps.event.addListener(marker, 'click', function () {
1560					infowindow.setContent(formattedAddress);
1561					infowindow.open(marker.get('map'), marker);
1562					// Focus on the list
1563					var markerId = marker.get('id');
1564					var $selectedLocation = $('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']');
1565
1566					if ($selectedLocation.length > 0) {
1567						// Marker click callback
1568						if (_this.settings.callbackMarkerClick) {
1569							_this.settings.callbackMarkerClick.call(this, marker, markerId, $selectedLocation, locationset[markerId], _this.map);
1570						}
1571
1572						$('.' + _this.settings.locationList + ' li').removeClass('list-focus');
1573						$selectedLocation.addClass('list-focus');
1574
1575						// Scroll list to selected marker
1576						var $container = $('.' + _this.settings.locationList);
1577						$container.animate({
1578							scrollTop: $selectedLocation.offset().top - $container.offset().top + $container.scrollTop()
1579						});
1580					}
1581
1582					// Custom selected marker override
1583					if (_this.settings.selectedMarkerImg !== null) {
1584						_this.changeSelectedMarker(marker);
1585					}
1586				});
1587			}
1588		},
1589
1590		/**
1591		 * HTML5 geocoding function for automatic location detection
1592		 *
1593		 * @param position {Object} coordinates
1594		 */
1595		autoGeocodeQuery: function (position) {
1596			this.writeDebug('autoGeocodeQuery',arguments);
1597			var _this = this,
1598				distance = null,
1599				$distanceInput = $('#' + this.settings.maxDistanceID),
1600				originAddress;
1601
1602			// Query string parameters
1603			if (this.settings.querystringParams === true) {
1604				// Check for distance query string parameters
1605				if (this.getQueryString(this.settings.maxDistanceID)){
1606					distance = this.getQueryString(this.settings.maxDistanceID);
1607
1608					if ($distanceInput.val() !== '') {
1609						distance = $distanceInput.val();
1610					}
1611				}
1612				else{
1613					// Get the distance if set
1614					if (this.settings.maxDistance === true) {
1615						distance = $distanceInput.val() || '';
1616					}
1617				}
1618			}
1619			else {
1620				// Get the distance if set
1621				if (this.settings.maxDistance === true) {
1622					distance = $distanceInput.val() || '';
1623				}
1624			}
1625
1626			// The address needs to be determined for the directions link
1627			var r = new this.reverseGoogleGeocode(this);
1628			var latlng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
1629			r.geocode({'latLng': latlng}, function (data) {
1630				if (data !== null) {
1631					originAddress = addressInput = data.address;
1632					olat = mappingObj.lat = position.coords.latitude;
1633					olng = mappingObj.lng = position.coords.longitude;
1634					mappingObj.origin = originAddress;
1635					mappingObj.distance = distance;
1636					_this.mapping(mappingObj);
1637
1638					// Fill in the search box.
1639					if (typeof originAddress !== 'undefined') {
1640						$('#' + _this.settings.addressID).val(originAddress);
1641					}
1642				} else {
1643					// Unable to geocode
1644					_this.notify(_this.settings.addressErrorAlert);
1645				}
1646			});
1647		},
1648
1649		/**
1650		 * Handle autoGeocode failure - private
1651		 *
1652		 */
1653		_autoGeocodeError: function () {
1654			this.writeDebug('_autoGeocodeError');
1655			// If automatic detection doesn't work show an error
1656			this.notify(this.settings.autoGeocodeErrorAlert);
1657		},
1658
1659		/**
1660		 * Default location method
1661		 */
1662		defaultLocation: function() {
1663			this.writeDebug('defaultLocation');
1664			var _this = this,
1665				distance = null,
1666				$distanceInput = $('#' + this.settings.maxDistanceID),
1667				originAddress;
1668
1669			// Query string parameters
1670			if (this.settings.querystringParams === true) {
1671				// Check for distance query string parameters
1672				if (this.getQueryString(this.settings.maxDistanceID)){
1673					distance = this.getQueryString(this.settings.maxDistanceID);
1674
1675					if ($distanceInput.val() !== '') {
1676						distance = $distanceInput.val();
1677					}
1678				}
1679				else {
1680					// Get the distance if set
1681					if (this.settings.maxDistance === true) {
1682						distance = $distanceInput.val() || '';
1683					}
1684				}
1685			}
1686			else {
1687				// Get the distance if set
1688				if (this.settings.maxDistance === true) {
1689					distance = $distanceInput.val() || '';
1690				}
1691			}
1692
1693			// The address needs to be determined for the directions link
1694			var r = new this.reverseGoogleGeocode(this);
1695			var latlng = new google.maps.LatLng(this.settings.defaultLat, this.settings.defaultLng);
1696			r.geocode({'latLng': latlng}, function (data) {
1697				if (data !== null) {
1698					originAddress = addressInput = data.address;
1699					olat = mappingObj.lat = _this.settings.defaultLat;
1700					olng = mappingObj.lng = _this.settings.defaultLng;
1701					mappingObj.distance = distance;
1702					mappingObj.origin = originAddress;
1703					_this.mapping(mappingObj);
1704				} else {
1705					// Unable to geocode
1706					_this.notify(_this.settings.addressErrorAlert);
1707				}
1708			});
1709		},
1710
1711		/**
1712		 * Change the page
1713		 *
1714		 * @param newPage {number} page to change to
1715		 */
1716		paginationChange: function (newPage) {
1717			this.writeDebug('paginationChange',arguments);
1718
1719			// Page change callback
1720			if (this.settings.callbackPageChange) {
1721				this.settings.callbackPageChange.call(this, newPage);
1722			}
1723
1724			mappingObj.page = newPage;
1725			this.mapping(mappingObj);
1726		},
1727
1728		/**
1729		 * Get the address by marker ID
1730		 *
1731		 * @param markerID {number} location ID
1732		 *
1733		 * @returns {string} formatted address
1734		 */
1735		getAddressByMarker: function(markerID) {
1736			this.writeDebug('getAddressByMarker',arguments);
1737			var formattedAddress = "";
1738			// Set up formatted address
1739			if(locationset[markerID].address){ formattedAddress += locationset[markerID].address + ' '; }
1740			if(locationset[markerID].address2){ formattedAddress += locationset[markerID].address2 + ' '; }
1741			if(locationset[markerID].city){ formattedAddress += locationset[markerID].city + ', '; }
1742			if(locationset[markerID].state){ formattedAddress += locationset[markerID].state + ' '; }
1743			if(locationset[markerID].postal){ formattedAddress += locationset[markerID].postal + ' '; }
1744			if(locationset[markerID].country){ formattedAddress += locationset[markerID].country + ' '; }
1745
1746			return formattedAddress;
1747		},
1748
1749		/**
1750		 * Clear the markers from the map
1751		 */
1752		clearMarkers: function() {
1753			this.writeDebug('clearMarkers');
1754			var locationsLimit = null;
1755
1756			if (locationset.length < this.settings.storeLimit) {
1757				locationsLimit = locationset.length;
1758			}
1759			else {
1760				locationsLimit = this.settings.storeLimit;
1761			}
1762
1763			for (var i = 0; i < locationsLimit; i++) {
1764				markers[i].setMap(null);
1765			}
1766		},
1767
1768		/**
1769		 * Handle inline direction requests
1770		 *
1771		 * @param origin {string} origin address
1772		 * @param locID {number} location ID
1773		 * @param map {Object} Google Map
1774		 */
1775		directionsRequest: function(origin, locID, map) {
1776			this.writeDebug('directionsRequest',arguments);
1777
1778			// Directions request callback
1779			if (this.settings.callbackDirectionsRequest) {
1780				this.settings.callbackDirectionsRequest.call(this, origin, locID, map, locationset[locID]);
1781			}
1782
1783			var destination = this.getAddressByMarker(locID);
1784
1785			if (destination) {
1786				// Hide the location list
1787				$('.' + this.settings.locationList + ' ul').hide();
1788				// Remove the markers
1789				this.clearMarkers();
1790
1791				// Clear the previous directions request
1792				if (directionsDisplay !== null && typeof directionsDisplay !== 'undefined') {
1793					directionsDisplay.setMap(null);
1794					directionsDisplay = null;
1795				}
1796
1797				directionsDisplay = new google.maps.DirectionsRenderer();
1798				directionsService = new google.maps.DirectionsService();
1799
1800				// Directions request
1801				directionsDisplay.setMap(map);
1802				directionsDisplay.setPanel($('.bh-sl-directions-panel').get(0));
1803
1804				var request = {
1805					origin: origin,
1806					destination: destination,
1807					travelMode: google.maps.TravelMode.DRIVING
1808				};
1809				directionsService.route(request, function(response, status) {
1810					if (status === google.maps.DirectionsStatus.OK) {
1811						directionsDisplay.setDirections(response);
1812					}
1813				});
1814
1815				$('.' + this.settings.locationList).prepend('<div class="bh-sl-close-directions-container"><div class="' + this.settings.closeIcon + '"></div></div>');
1816			}
1817
1818			$(document).off('click', '.' + this.settings.locationList + ' li .loc-directions a');
1819		},
1820
1821		/**
1822		 * Close the directions panel and reset the map with the original locationset and zoom
1823		 */
1824		closeDirections: function() {
1825			this.writeDebug('closeDirections');
1826
1827			// Close directions callback
1828			if (this.settings.callbackCloseDirections) {
1829				this.settings.callbackCloseDirections.call(this);
1830			}
1831
1832			// Remove the close icon, remove the directions, add the list back
1833			this.reset();
1834
1835			if ((olat) && (olng)) {
1836				if (this.countFilters() === 0) {
1837					this.settings.mapSettings.zoom = originalZoom;
1838				}
1839				else {
1840					this.settings.mapSettings.zoom = 0;
1841				}
1842				this.processForm(null);
1843			}
1844
1845			$(document).off('click.'+pluginName, '.' + this.settings.locationList + ' .bh-sl-close-icon');
1846		},
1847
1848		/**
1849		 * Handle length unit swap
1850		 *
1851		 * @param $lengthSwap
1852		 */
1853		lengthUnitSwap: function($lengthSwap) {
1854			this.writeDebug('lengthUnitSwap',arguments);
1855
1856			if ($lengthSwap.val() === 'alt-distance') {
1857				$('.' + this.settings.locationList + ' .loc-alt-dist').show();
1858				$('.' + this.settings.locationList + ' .loc-default-dist').hide();
1859			} else if ($lengthSwap.val() === 'default-distance') {
1860				$('.' + this.settings.locationList + ' .loc-default-dist').show();
1861				$('.' + this.settings.locationList + ' .loc-alt-dist').hide();
1862			}
1863		},
1864
1865		/**
1866		 * Process the form values and/or query string
1867		 *
1868		 * @param e {Object} event
1869		 */
1870		processForm: function (e) {
1871			this.writeDebug('processForm',arguments);
1872			var _this = this,
1873				distance = null,
1874				geocodeRestrictions = {},
1875				$addressInput = $('#' + this.settings.addressID),
1876				$searchInput = $('#' + this.settings.searchID + ' US') ,
1877				$distanceInput = $('#' + this.settings.maxDistanceID),
1878				region = '';
1879
1880			// Stop the form submission.
1881			if (typeof e !== 'undefined' && e !== null) {
1882				e.preventDefault();
1883			}
1884
1885			// Blur any form field to hide mobile keyboards.
1886			$('.' + _this.settings.formContainer +' input, .' + _this.settings.formContainer + ' select').blur();
1887
1888			// Query string parameters
1889			if (this.settings.querystringParams === true) {
1890				// Check for query string parameters
1891				if (this.getQueryString(this.settings.addressID) || this.getQueryString(this.settings.searchID) || this.getQueryString(this.settings.maxDistanceID)) {
1892					addressInput = this.getQueryString(this.settings.addressID);
1893					searchInput = this.getQueryString(this.settings.searchID);
1894					distance = this.getQueryString(this.settings.maxDistanceID);
1895
1896					// Max distance field.
1897					if (distance && $('#' + this.settings.maxDistanceID + ' option[value=' + distance + ']').length) {
1898						$distanceInput.val(distance);
1899					}
1900
1901					// Update zoom if origin coordinates are available and a distance query string value is set.
1902					if (addressInput && distance) {
1903						_this.settings.mapSettings.zoom = 0;
1904					}
1905
1906					// The form should override the query string parameters.
1907					if ($addressInput.val() !== '') {
1908						addressInput = $addressInput.val();
1909					}
1910					if ($searchInput.val() !== '') {
1911						searchInput = $searchInput.val();
1912					}
1913					if ($distanceInput.val() !== '') {
1914						distance = $distanceInput.val();
1915					}
1916				}
1917				else {
1918					// Get the user input and use it
1919					addressInput = $addressInput.val() || '';
1920					searchInput = $searchInput.val() || '';
1921
1922					// Get the distance if set
1923					if (this.settings.maxDistance === true) {
1924						distance = $distanceInput.val() || '';
1925					}
1926				}
1927			}
1928			else {
1929				// Get the user input and use it
1930				addressInput = $addressInput.val() || '';
1931				searchInput = $searchInput.val() || '';
1932				// Get the distance if set
1933				if (this.settings.maxDistance === true) {
1934					distance = $distanceInput.val() || '';
1935				}
1936			}
1937
1938			// Region
1939			if (this.settings.callbackRegion) {
1940				// Region override callback
1941				region = this.settings.callbackRegion.call(this, addressInput, searchInput, distance);
1942			} else {
1943				// Region setting
1944				region = $('#' + this.settings.regionID).val();
1945			}
1946
1947			// Form values callback
1948			if (this.settings.callbackFormVals) {
1949				this.settings.callbackFormVals.call(this, addressInput, searchInput, distance, region);
1950			}
1951
1952			// Add component restriction if the region has been set.
1953			if (typeof region !== 'undefined') {
1954				geocodeRestrictions = {
1955					country: region
1956				};
1957			}
1958
1959			// Component restriction value via callback.
1960			if (typeof this.settings.callbackGeocodeRestrictions === 'function') {
1961				// Component restriction override callback
1962				geocodeRestrictions = this.settings.callbackGeocodeRestrictions.call(this, addressInput, searchInput, distance);
1963			}
1964
1965			if (addressInput === '' && searchInput === '' && this.settings.autoGeocode !== true) {
1966				this._start();
1967			}
1968			else if (addressInput !== '') {
1969				// Check for existing name search and remove if address input is blank.
1970				if (searchInput === '' && filters.hasOwnProperty('name')) {
1971					delete filters.name;
1972				}
1973
1974				// Geocode the origin if needed
1975				if (typeof originalOrigin !== 'undefined' && typeof olat !== 'undefined' && typeof olng !== 'undefined' && (addressInput === originalOrigin)) {
1976					// Run the mapping function
1977					mappingObj.lat = olat;
1978					mappingObj.lng = olng;
1979					mappingObj.origin = addressInput;
1980					mappingObj.name = searchInput;
1981					mappingObj.distance = distance;
1982					_this.mapping(mappingObj);
1983				}
1984				else {
1985					var g = new this.googleGeocode(this);
1986					g.geocode({
1987						address: addressInput,
1988						componentRestrictions: geocodeRestrictions,
1989						region: region
1990					}, function (data) {
1991						if (data !== null) {
1992							olat = data.latitude;
1993							olng = data.longitude;
1994
1995							// Run the mapping function
1996							mappingObj.lat = olat;
1997							mappingObj.lng = olng;
1998							mappingObj.origin = addressInput;
1999							mappingObj.name = searchInput;
2000							mappingObj.distance = distance;
2001							mappingObj.geocodeResult = data.geocodeResult;
2002							_this.mapping(mappingObj);
2003						} else {
2004							// Unable to geocode
2005							_this.notify(_this.settings.addressErrorAlert);
2006						}
2007					});
2008				}
2009			}
2010			else if (searchInput !== '') {
2011				// Check for existing origin and remove if address input is blank.
2012				if ( addressInput === '' ) {
2013					delete mappingObj.origin;
2014				}
2015
2016				mappingObj.name = searchInput;
2017				_this.mapping(mappingObj);
2018			}
2019			else if (this.settings.autoGeocode === true) {
2020				// Run the mapping function
2021				mappingObj.lat = olat;
2022				mappingObj.lng = olng;
2023				mappingObj.origin = addressInput;
2024				mappingObj.name = searchInput;
2025				mappingObj.distance = distance;
2026				_this.mapping(mappingObj);
2027			}
2028		},
2029
2030		/**
2031		 * Checks distance of each location and sets up the locationset array
2032		 *
2033		 * @param data {Object} location data object
2034		 * @param lat {number} origin latitude
2035		 * @param lng {number} origin longitude
2036		 * @param origin {string} origin address
2037		 * @param maxDistance {number} maximum distance if set
2038		 */
2039		locationsSetup: function (data, lat, lng, origin, maxDistance) {
2040			this.writeDebug('locationsSetup',arguments);
2041			if (typeof origin !== 'undefined') {
2042				if (!data.distance) {
2043					data.distance = this.geoCodeCalcCalcDistance(lat, lng, data.lat, data.lng, GeoCodeCalc.EarthRadius);
2044
2045					// Alternative distance length unit
2046					if (this.settings.lengthUnit === 'm') {
2047						// Miles to kilometers
2048						data.altdistance = parseFloat(data.distance)*1.609344;
2049					} else if (this.settings.lengthUnit === 'km') {
2050						// Kilometers to miles
2051						data.altdistance = parseFloat(data.distance)/1.609344;
2052					}
2053				}
2054			}
2055
2056			// Make sure the location coordinates are valid.
2057			if (!this.coordinatesInRange(data.lat, data.lng)) {
2058				this.writeDebug('locationsSetup', "location ignored because coordinates out of range: " + maxDistance, data);
2059				return;
2060			}
2061
2062			// Create the array
2063			if (this.settings.maxDistance === true && typeof maxDistance !== 'undefined' && maxDistance !== null) {
2064				if (data.distance <= maxDistance) {
2065					locationset.push( data );
2066				} else {
2067					this.writeDebug('locationsSetup', "location ignored because it is out of maxDistance: " + maxDistance, data);
2068					return;
2069				}
2070			} else if (this.settings.maxDistance === true && this.settings.querystringParams === true && typeof maxDistance !== 'undefined' && maxDistance !== null) {
2071				if (data.distance <= maxDistance) {
2072					locationset.push( data );
2073				} else {
2074					this.writeDebug('locationsSetup', "location ignored because it is out of maxDistance: " + maxDistance, data);
2075					return;
2076				}
2077			} else {
2078				locationset.push( data );
2079			}
2080		},
2081
2082		/**
2083		 * Set up front-end sorting functionality
2084		 */
2085		sorting: function() {
2086			this.writeDebug('sorting',arguments);
2087			var _this = this,
2088				$mapDiv = $('#' + _this.settings.mapID),
2089				$sortSelect = $('#' + _this.settings.sortID);
2090
2091			if ($sortSelect.length === 0) {
2092				return;
2093			}
2094
2095			$sortSelect.on('change.'+pluginName, function (e) {
2096				e.stopPropagation();
2097
2098				// Reset pagination.
2099				if (_this.settings.pagination === true) {
2100					_this.paginationChange(0);
2101				}
2102
2103				var sortMethod,
2104					sortVal;
2105
2106				sortMethod = (typeof $(this).find(':selected').attr('data-method') !== 'undefined') ?  $(this).find(':selected').attr('data-method') : 'distance';
2107				sortVal = $(this).val();
2108
2109				_this.settings.sortBy.method = sortMethod;
2110				_this.settings.sortBy.prop = sortVal;
2111
2112				// Callback
2113				if (_this.settings.callbackSorting) {
2114					_this.settings.callbackSorting.call(this, _this.settings.sortBy);
2115				}
2116
2117				if ($mapDiv.hasClass('bh-sl-map-open')) {
2118					_this.mapping(mappingObj);
2119				}
2120			});
2121		},
2122
2123		/**
2124		 * Set up front-end ordering functionality - this ties in to sorting and that has to be enabled for this to
2125		 * work.
2126		 */
2127		order: function() {
2128			this.writeDebug('order',arguments);
2129			var _this = this,
2130				$mapDiv = $('#' + _this.settings.mapID),
2131				$orderSelect = $('#' + _this.settings.orderID);
2132
2133			if ($orderSelect.length === 0) {
2134				return;
2135			}
2136
2137			$orderSelect.on('change.'+pluginName, function (e) {
2138				e.stopPropagation();
2139
2140				// Reset pagination.
2141				if (_this.settings.pagination === true) {
2142					_this.paginationChange(0);
2143				}
2144
2145				_this.settings.sortBy.order = $(this).val();
2146
2147				// Callback
2148				if (_this.settings.callbackOrder) {
2149					_this.settings.callbackOrder.call(this, _this.settings.order);
2150				}
2151
2152				if ($mapDiv.hasClass('bh-sl-map-open')) {
2153					_this.mapping(mappingObj);
2154				}
2155			});
2156		},
2157
2158		/**
2159		 * Distance filtering
2160		 */
2161		distanceFiltering: function () {
2162			this.writeDebug('distanceFiltering');
2163			var _this = this;
2164			var $distanceInput = $('#' + this.settings.maxDistanceID);
2165
2166			// Add event listener
2167			$distanceInput.on('change.'+pluginName, function (e) {
2168				e.stopPropagation();
2169
2170				// Query string parameter value updates on change.
2171				if (_this.settings.querystringParams === true) {
2172					var currentUrl = window.location.href;
2173					var url = new URL(currentUrl);
2174
2175					// Update the distance in the URL.
2176					url.searchParams.set(_this.settings.maxDistanceID, this.value);
2177
2178					// Update the query string param to match the new value.
2179					if (history.pushState) {
2180						window.history.pushState({path: url.href}, '', url.href);
2181					} else {
2182						window.location.replace(url.href);
2183					}
2184				}
2185
2186				if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
2187					if ((olat) && (olng)) {
2188						_this.settings.mapSettings.zoom = 0;
2189						_this.processForm();
2190					} else {
2191						_this.mapping(mappingObj);
2192					}
2193				}
2194			});
2195		},
2196
2197		/**
2198		 * Count the selected filters
2199		 *
2200		 * @returns {number}
2201		 */
2202		countFilters: function () {
2203			this.writeDebug('countFilters');
2204			var filterCount = 0;
2205
2206			if (!this.isEmptyObject(filters)) {
2207				for (var key in filters) {
2208					if (filters.hasOwnProperty(key)) {
2209						filterCount += filters[key].length;
2210					}
2211				}
2212			}
2213
2214			return filterCount;
2215		},
2216
2217		/**
2218		 * Find the existing checked boxes for each checkbox filter - private
2219		 *
2220		 * @param key {string} object key
2221		 */
2222		_existingCheckedFilters: function(key) {
2223			this.writeDebug('_existingCheckedFilters',arguments);
2224			$('#' + this.settings.taxonomyFilters[key] + ' input[type=checkbox]').each(function () {
2225				if ($(this).prop('checked')) {
2226					var filterVal = $(this).val();
2227
2228					// Only add the taxonomy id if it doesn't already exist
2229					if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
2230						filters[key].push(filterVal);
2231					}
2232				}
2233			});
2234		},
2235
2236		/**
2237		 * Find the existing selected value for each select filter - private
2238		 *
2239		 * @param key {string} object key
2240		 */
2241		_existingSelectedFilters: function(key) {
2242			this.writeDebug('_existingSelectedFilters',arguments);
2243			$('#' + this.settings.taxonomyFilters[key] + ' select').each(function () {
2244				var filterVal = $(this).val();
2245
2246				// Only add the taxonomy id if it doesn't already exist
2247				if (typeof filterVal !== 'undefined' && filterVal !== '' &&  filters[key].indexOf(filterVal) === -1) {
2248					filters[key] = [filterVal];
2249				}
2250			});
2251		},
2252
2253		/**
2254		 * Find the existing selected value for each radio button filter - private
2255		 *
2256		 * @param key {string} object key
2257		 */
2258		_existingRadioFilters: function(key) {
2259			this.writeDebug('_existingRadioFilters',arguments);
2260			$('#' + this.settings.taxonomyFilters[key] + ' input[type=radio]').each(function () {
2261				if ($(this).prop('checked')) {
2262					var filterVal = $(this).val();
2263
2264					// Only add the taxonomy id if it doesn't already exist
2265					if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
2266						filters[key] = [filterVal];
2267					}
2268				}
2269			});
2270		},
2271
2272		/**
2273		 * Check for existing filter selections
2274		 */
2275		checkFilters: function () {
2276			this.writeDebug('checkFilters');
2277			for(var key in this.settings.taxonomyFilters) {
2278
2279				if (this.settings.taxonomyFilters.hasOwnProperty(key)) {
2280					// Find the existing checked boxes for each checkbox filter
2281					this._existingCheckedFilters(key);
2282
2283					// Find the existing selected value for each select filter
2284					this._existingSelectedFilters(key);
2285
2286					// Find the existing value for each radio button filter
2287					this._existingRadioFilters(key);
2288				}
2289			}
2290		},
2291
2292		/**
2293		 * Select the indicated values from query string parameters.
2294		 *
2295		 * @param taxonomy {string} Current taxonomy.
2296		 * @param value {array} Query string array values.
2297		 */
2298		selectQueryStringFilters: function( taxonomy, value ) {
2299			this.writeDebug('selectQueryStringFilters', arguments);
2300
2301			var $taxGroupContainer = $('#' + this.settings.taxonomyFilters[taxonomy]);
2302
2303			// Handle checkboxes.
2304			if ( $taxGroupContainer.find('input[type="checkbox"]').length ) {
2305
2306				for ( var i = 0; i < value.length; i++ ) {
2307					$taxGroupContainer.find('input:checkbox[value="' + value[i] + '"]').prop('checked', true);
2308				}
2309			}
2310
2311			// Handle select fields.
2312			if ( $taxGroupContainer.find('select').length ) {
2313				// Only expecting one value for select fields.
2314				$taxGroupContainer.find('option[value="' + value[0] + '"]').prop('selected', true);
2315			}
2316
2317			// Handle radio buttons.
2318			if ( $taxGroupContainer.find('input[type="radio"]').length ) {
2319				// Only one value for radio button.
2320				$taxGroupContainer.find('input:radio[value="' + value[0] + '"]').prop('checked', true);
2321			}
2322		},
2323
2324		/**
2325		 * Check query string parameters for filter values.
2326		 */
2327		checkQueryStringFilters: function () {
2328			this.writeDebug('checkQueryStringFilters',arguments);
2329
2330			// Loop through the filters.
2331			for(var key in filters) {
2332				if (filters.hasOwnProperty(key)) {
2333					var filterVal = this.getQueryString(key);
2334
2335					// Check for multiple values separated by comma.
2336					if ( filterVal.indexOf( ',' ) !== -1 ) {
2337						filterVal = filterVal.split( ',' );
2338					}
2339
2340					// Only add the taxonomy id if it doesn't already exist
2341					if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
2342						if ( Array.isArray( filterVal ) ) {
2343							filters[key] = filterVal;
2344						} else {
2345							filters[key] = [filterVal];
2346						}
2347					}
2348
2349					// Select the filters indicated in the query string.
2350					if ( filters[key].length ) {
2351						this.selectQueryStringFilters( key, filters[key] );
2352					}
2353				}
2354			}
2355		},
2356
2357		/**
2358		 * Get the filter key from the taxonomyFilter setting
2359		 *
2360		 * @param filterContainer {string} ID of the changed filter's container
2361		 */
2362		getFilterKey: function (filterContainer) {
2363			this.writeDebug('getFilterKey',arguments);
2364			for (var key in this.settings.taxonomyFilters) {
2365				if (this.settings.taxonomyFilters.hasOwnProperty(key)) {
2366					for (var i = 0; i < this.settings.taxonomyFilters[key].length; i++) {
2367						if (this.settings.taxonomyFilters[key] === filterContainer) {
2368							return key;
2369						}
2370					}
2371				}
2372			}
2373		},
2374
2375		/**
2376		 * Initialize or reset the filters object to its original state
2377		 */
2378		taxonomyFiltersInit: function () {
2379			this.writeDebug('taxonomyFiltersInit');
2380
2381			// Set up the filters
2382			for(var key in this.settings.taxonomyFilters) {
2383				if (this.settings.taxonomyFilters.hasOwnProperty(key)) {
2384					filters[key] = [];
2385				}
2386			}
2387		},
2388
2389		/**
2390		 * Taxonomy filtering
2391		 */
2392		taxonomyFiltering: function() {
2393			this.writeDebug('taxonomyFiltering');
2394			var _this = this;
2395
2396			// Set up the filters
2397			_this.taxonomyFiltersInit();
2398
2399			// Check query string for taxonomy parameter keys.
2400			_this.checkQueryStringFilters();
2401
2402			// Handle filter updates
2403			$('.' + this.settings.taxonomyFiltersContainer).on('change.'+pluginName, 'input, select', function (e) {
2404				e.stopPropagation();
2405
2406				var filterVal, filterContainer, filterKey;
2407
2408				// Reset pagination.
2409				if (_this.settings.pagination === true) {
2410					_this.paginationChange(0);
2411				}
2412
2413				// Handle checkbox filters
2414				if ($(this).is('input[type="checkbox"]')) {
2415					// First check for existing selections
2416					_this.checkFilters();
2417
2418					filterVal = $(this).val();
2419					filterContainer = $(this).closest('.bh-sl-filters').attr('id');
2420					filterKey = _this.getFilterKey(filterContainer);
2421
2422					if (filterKey) {
2423						// Add or remove filters based on checkbox values
2424						if ($(this).prop('checked')) {
2425							// Add ids to the filter arrays as they are checked
2426							if (filters[filterKey].indexOf(filterVal) === -1) {
2427								filters[filterKey].push(filterVal);
2428							}
2429
2430							if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
2431								if ((olat) && (olng)) {
2432									_this.settings.mapSettings.zoom = 0;
2433									_this.processForm();
2434								}
2435								else {
2436									_this.mapping(mappingObj);
2437								}
2438							}
2439						}
2440						else {
2441							// Remove ids from the filter arrays as they are unchecked
2442							var filterIndex = filters[filterKey].indexOf(filterVal);
2443							if (filterIndex > -1) {
2444								filters[filterKey].splice(filterIndex, 1);
2445								if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
2446									if ((olat) && (olng)) {
2447										if (_this.countFilters() === 0) {
2448											_this.settings.mapSettings.zoom = originalZoom;
2449										} else {
2450											_this.settings.mapSettings.zoom = 0;
2451										}
2452
2453										_this.processForm();
2454									}
2455									else {
2456										_this.mapping(mappingObj);
2457									}
2458								}
2459							}
2460						}
2461					}
2462				}
2463				// Handle select or radio filters
2464				else if ($(this).is('select') || $(this).is('input[type="radio"]')) {
2465					// First check for existing selections
2466					_this.checkFilters();
2467
2468					filterVal = $(this).val();
2469					filterContainer = $(this).closest('.bh-sl-filters').attr('id');
2470					filterKey = _this.getFilterKey(filterContainer);
2471
2472					// Check for blank filter on select since default val could be empty
2473					if (filterVal) {
2474						if (filterKey) {
2475							filters[filterKey] = [filterVal];
2476							if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
2477								if ((olat) && (olng)) {
2478									_this.settings.mapSettings.zoom = 0;
2479									_this.processForm();
2480								} else {
2481									_this.mapping(mappingObj);
2482								}
2483							}
2484						}
2485					}
2486					// Reset if the default option is selected
2487					else {
2488						if (filterKey) {
2489							filters[filterKey] = [];
2490						}
2491						_this.reset();
2492						if ((olat) && (olng)) {
2493							_this.settings.mapSettings.zoom = originalZoom;
2494							_this.processForm();
2495						}
2496						else {
2497							_this.mapping(mappingObj);
2498						}
2499					}
2500				}
2501			});
2502		},
2503
2504		/**
2505		 * Updates the location list to reflect the markers that are displayed on the map
2506		 *
2507		 * @param markers {Object} Map markers
2508		 * @param map {Object} Google map
2509		 */
2510		checkVisibleMarkers: function(markers, map) {
2511			this.writeDebug('checkVisibleMarkers',arguments);
2512			var _this = this;
2513			var locations, listHtml;
2514
2515			// Empty the location list
2516			$('.' + this.settings.locationList + ' ul').empty();
2517
2518			// Set up the new list
2519			$(markers).each(function(x, marker){
2520				if (map.getBounds().contains(marker.getPosition())) {
2521					// Define the location data
2522					_this.listSetup(marker, 0, 0);
2523
2524					// Set up the list template with the location data
2525					listHtml = listTemplate(locations);
2526					$('.' + _this.settings.locationList + ' > ul').append(listHtml);
2527				}
2528			});
2529
2530			// Re-add the list background colors
2531			$('.' + this.settings.locationList + ' ul li:even').css('background', this.settings.listColor1);
2532			$('.' + this.settings.locationList + ' ul li:odd').css('background', this.settings.listColor2);
2533		},
2534
2535		/**
2536		 * Performs a new search when the map is dragged to a new position
2537		 *
2538		 * @param map {Object} Google map
2539		 */
2540		dragSearch: function(map) {
2541			this.writeDebug('dragSearch',arguments);
2542			var newCenter = map.getCenter(),
2543				newCenterCoords,
2544				_this = this;
2545
2546			// Save the new zoom setting
2547			this.settings.mapSettings.zoom = map.getZoom();
2548
2549			olat = mappingObj.lat = newCenter.lat();
2550			olng = mappingObj.lng = newCenter.lng();
2551
2552			// Determine the new origin address
2553			var newAddress = new this.reverseGoogleGeocode(this);
2554			newCenterCoords = new google.maps.LatLng(mappingObj.lat, mappingObj.lng);
2555			newAddress.geocode({'latLng': newCenterCoords}, function (data) {
2556				if (data !== null) {
2557					mappingObj.origin = addressInput = data.address;
2558					_this.mapping(mappingObj);
2559				} else {
2560					// Unable to geocode
2561					_this.notify(_this.settings.addressErrorAlert);
2562				}
2563			});
2564		},
2565
2566		/**
2567		 * Handle no results
2568		 */
2569		emptyResult: function() {
2570			this.writeDebug('emptyResult',arguments);
2571			var center,
2572				locList =  $('.' + this.settings.locationList + ' ul'),
2573				myOptions = this.settings.mapSettings,
2574				noResults;
2575
2576			// Create the map
2577			this.map = new google.maps.Map(document.getElementById(this.settings.mapID), myOptions);
2578
2579			// Callback
2580			if (this.settings.callbackNoResults) {
2581				this.settings.callbackNoResults.call(this, this.map, myOptions);
2582			}
2583
2584			// Empty the location list
2585			locList.empty();
2586
2587			// Append the no results message
2588			noResults = $('<li><div class="bh-sl-noresults-title">' + this.settings.noResultsTitle +  '</div><br><div class="bh-sl-noresults-desc">' + this.settings.noResultsDesc + '</li>').hide().fadeIn();
2589			locList.append(noResults);
2590
2591			// Center on the original origin or 0,0 if not available
2592			if ((olat) && (olng)) {
2593				center = new google.maps.LatLng(olat, olng);
2594			} else {
2595				center = new google.maps.LatLng(0, 0);
2596			}
2597
2598			this.map.setCenter(center);
2599
2600			if (originalZoom) {
2601				this.map.setZoom(originalZoom);
2602			}
2603		},
2604
2605
2606		/**
2607		 * Origin marker setup
2608		 *
2609		 * @param map {Object} Google map
2610		 * @param origin {string} Origin address
2611		 * @param originPoint {Object} LatLng of origin point
2612		 */
2613		originMarker: function(map, origin, originPoint) {
2614			this.writeDebug('originMarker',arguments);
2615
2616			if (this.settings.originMarker !== true) {
2617				return;
2618			}
2619
2620			var marker,
2621				originImg = '';
2622
2623			if (typeof origin !== 'undefined') {
2624				if (this.settings.originMarkerImg !== null) {
2625					if (this.settings.originMarkerDim === null) {
2626						originImg = this.markerImage(this.settings.originMarkerImg);
2627					}
2628					else {
2629						originImg = this.markerImage(this.settings.originMarkerImg, this.settings.originMarkerDim.width, this.settings.originMarkerDim.height);
2630					}
2631				}
2632				else {
2633					originImg = {
2634						url: 'https://mt.googleapis.com/vt/icon/name=icons/spotlight/spotlight-waypoint-a.png'
2635					};
2636				}
2637
2638				marker = new google.maps.Marker({
2639					position : originPoint,
2640					map      : map,
2641					icon     : originImg,
2642					draggable: false
2643				});
2644			}
2645		},
2646
2647		/**
2648		 * Modal window setup
2649		 */
2650		modalWindow: function() {
2651			this.writeDebug('modalWindow');
2652
2653			if (this.settings.modal !== true) {
2654				return;
2655			}
2656
2657			var _this = this;
2658
2659			// Callback
2660			if (_this.settings.callbackModalOpen) {
2661				_this.settings.callbackModalOpen.call(this);
2662			}
2663
2664			// Pop up the modal window
2665			$('.' + _this.settings.overlay).fadeIn();
2666			// Close modal when close icon is clicked and when background overlay is clicked
2667			$(document).on('click.'+pluginName, '.' + _this.settings.closeIcon + ', .' + _this.settings.overlay, function () {
2668				_this.modalClose();
2669			});
2670			// Prevent clicks within the modal window from closing the entire thing
2671			$(document).on('click.'+pluginName, '.' + _this.settings.modalWindow, function (e) {
2672				e.stopPropagation();
2673			});
2674			// Close modal when escape key is pressed
2675			$(document).on('keyup.'+pluginName, function (e) {
2676				if (e.keyCode === 27) {
2677					_this.modalClose();
2678				}
2679			});
2680		},
2681
2682		/**
2683		 * Open and select the location closest to the origin
2684		 *
2685		 * @param nearestLoc {Object} Details for the nearest location
2686		 * @param infowindow {Object} Info window object
2687		 * @param storeStart {number} Starting point of current page when pagination is enabled
2688		 * @param page {number} Current page number when pagination is enabled
2689		 */
2690		openNearestLocation: function(nearestLoc, infowindow, storeStart, page) {
2691			this.writeDebug('openNearestLocation',arguments);
2692
2693			if (
2694				this.settings.openNearest !== true ||
2695				typeof nearestLoc === 'undefined' ||
2696        		typeof originalOrigin === 'undefined' ||
2697				(this.settings.fullMapStart === true && firstRun === true && this.settings.querystringParams === false) ||
2698				(this.settings.defaultLoc === true && firstRun === true && this.settings.querystringParams === false)
2699			) {
2700				return;
2701			}
2702
2703			var _this = this;
2704
2705			// Callback
2706			if (_this.settings.callbackNearestLoc) {
2707				_this.settings.callbackNearestLoc.call(this, _this.map, nearestLoc, infowindow, storeStart, page);
2708			}
2709
2710			var markerId = (nearestLoc.hasOwnProperty('markerid')) ? nearestLoc.markerid : 0;
2711			var selectedMarker = markers[markerId];
2712
2713			_this.createInfowindow(selectedMarker, 'left', infowindow, storeStart, page);
2714
2715			// Scroll list to selected marker
2716			var $container = $('.' + _this.settings.locationList);
2717			var $selectedLocation = $('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']');
2718
2719			// Focus on the list
2720			$('.' + _this.settings.locationList + ' li').removeClass('list-focus');
2721			$selectedLocation.addClass('list-focus');
2722
2723			$container.animate({
2724				scrollTop: $selectedLocation.offset().top - $container.offset().top + $container.scrollTop()
2725			});
2726		},
2727
2728		/**
2729		 * Handle clicks from the location list
2730		 *
2731		 * @param map {Object} Google map
2732		 * @param infowindow {Object} Info window object
2733		 * @param storeStart {number} Starting point of current page when pagination is enabled
2734		 * @param page {number} Current page number when pagination is enabled
2735		 */
2736		listClick: function(map, infowindow, storeStart, page) {
2737			this.writeDebug('listClick',arguments);
2738			var _this = this;
2739
2740			$(document).on('click.' + pluginName, '.' + _this.settings.locationList + ' li', function () {
2741				var markerId = $(this).data('markerid');
2742				var selectedMarker = markers[markerId];
2743
2744				// List click callback
2745				if (_this.settings.callbackListClick) {
2746					_this.settings.callbackListClick.call(this, markerId, selectedMarker, locationset[markerId], map);
2747				}
2748
2749				map.panTo(selectedMarker.getPosition());
2750				var listLoc = 'left';
2751				_this.createInfowindow(selectedMarker, listLoc, infowindow, storeStart, page);
2752
2753				// Custom selected marker override
2754				if (_this.settings.selectedMarkerImg !== null) {
2755					_this.changeSelectedMarker(selectedMarker);
2756				}
2757
2758				// Focus on the list
2759				$('.' + _this.settings.locationList + ' li').removeClass('list-focus');
2760				$('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']').addClass('list-focus');
2761			});
2762
2763			// Prevent bubbling from list content links
2764			$(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' li a', function(e) {
2765				e.stopPropagation();
2766			});
2767		},
2768
2769		/**
2770		 * Output total results count if HTML element with .bh-sl-total-results class exists
2771		 *
2772		 * @param locCount
2773		 */
2774		resultsTotalCount: function(locCount) {
2775			this.writeDebug('resultsTotalCount',arguments);
2776
2777			var $resultsContainer = $('.bh-sl-total-results');
2778
2779			if (typeof locCount === 'undefined' || locCount <= 0 || $resultsContainer.length === 0) {
2780				return;
2781			}
2782
2783			$resultsContainer.text(locCount);
2784		},
2785
2786		/**
2787		 * Inline directions setup
2788		 *
2789		 * @param map {Object} Google map
2790		 * @param origin {string} Origin address
2791		 */
2792		inlineDirections: function(map, origin) {
2793			this.writeDebug('inlineDirections',arguments);
2794
2795			if (this.settings.inlineDirections !== true || typeof origin === 'undefined') {
2796				return;
2797			}
2798
2799			var _this = this;
2800
2801			// Open directions
2802			$(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' li .loc-directions a', function (e) {
2803				e.preventDefault();
2804				var locID = $(this).closest('li').attr('data-markerid');
2805				_this.directionsRequest(origin, parseInt(locID), map);
2806
2807				// Close directions
2808				$(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' .bh-sl-close-icon', function () {
2809					_this.closeDirections();
2810				});
2811			});
2812		},
2813
2814		/**
2815		 * Visible markers list setup
2816		 *
2817		 * @param map {Object} Google map
2818		 * @param markers {Object} Map markers
2819		 */
2820		visibleMarkersList: function(map, markers) {
2821			this.writeDebug('visibleMarkersList',arguments);
2822
2823			if (this.settings.visibleMarkersList !== true) {
2824				return;
2825			}
2826
2827			var _this = this;
2828
2829			// Add event listener to filter the list when the map is fully loaded
2830			google.maps.event.addListenerOnce(map, 'idle', function(){
2831				_this.checkVisibleMarkers(markers, map);
2832			});
2833
2834			// Add event listener for center change
2835			google.maps.event.addListener(map, 'center_changed', function() {
2836				_this.checkVisibleMarkers(markers, map);
2837			});
2838
2839			// Add event listener for zoom change
2840			google.maps.event.addListener(map, 'zoom_changed', function() {
2841				_this.checkVisibleMarkers(markers, map);
2842			});
2843		},
2844
2845		/**
2846		 * Restrict featured locations from displaying in results by a specific distance
2847		 *
2848		 * @returns {Array}
2849		 */
2850		featuredDistanceRestriction: function() {
2851			this.writeDebug('featuredDistanceRestriction',arguments);
2852			var _this = this;
2853
2854			featuredset = $.grep(featuredset, function (val) {
2855
2856				if (val.hasOwnProperty('distance')) {
2857					return parseFloat(val.distance) <= parseFloat(_this.settings.featuredDistance);
2858				}
2859			});
2860
2861			return featuredset;
2862		},
2863
2864		/**
2865		 * Restrict featured locations by distance.
2866		 *
2867		 * @returns {Array}
2868		 */
2869		featuredRestrictions: function(mappingObject) {
2870			this.writeDebug('featuredRestrictions',arguments);
2871
2872			if (this.settings.featuredDistance === null) {
2873				return featuredset;
2874			}
2875
2876			// Featured locations radius restriction.
2877			if (this.settings.featuredDistance !== null) {
2878					featuredset = this.featuredDistanceRestriction(mappingObject);
2879			}
2880
2881			return featuredset;
2882		},
2883
2884		/**
2885		 * The primary mapping function that runs everything
2886		 *
2887		 * @param mappingObject {Object} all the potential mapping properties - latitude, longitude, origin, name, max
2888		 *     distance, page
2889		 */
2890		mapping: function (mappingObject) {
2891			this.writeDebug('mapping',arguments);
2892			var _this = this;
2893			var orig_lat, orig_lng, geocodeData, origin, originPoint, page;
2894			if (!this.isEmptyObject(mappingObject)) {
2895				orig_lat = mappingObject.lat;
2896				orig_lng = mappingObject.lng;
2897				geocodeData = mappingObject.geocodeResult;
2898				origin = mappingObject.origin;
2899				page = mappingObject.page;
2900			}
2901
2902			// Set the initial page to zero if not set
2903			if ( _this.settings.pagination === true ) {
2904				if (typeof page === 'undefined' || originalOrigin !== addressInput ) {
2905					page = 0;
2906				}
2907			}
2908
2909			// Data request
2910			if (typeof origin === 'undefined' && this.settings.nameSearch === true) {
2911				dataRequest = _this._getData();
2912			}
2913			else {
2914				// Set up the origin point
2915				originPoint = new google.maps.LatLng(orig_lat, orig_lng);
2916
2917				// If the origin hasn't changed use the existing data so we aren't making unneeded AJAX requests
2918				if ((typeof originalOrigin !== 'undefined') && (origin === originalOrigin) && (typeof originalData !== 'undefined')) {
2919					origin = originalOrigin;
2920					dataRequest = originalData;
2921				}
2922				else {
2923					// Do the data request - doing this in mapping so the lat/lng and address can be passed over and used if needed
2924					dataRequest = _this._getData(olat, olng, origin, geocodeData, mappingObject);
2925				}
2926			}
2927
2928			// Check filters here to handle selected filtering after page reload
2929			if (_this.settings.taxonomyFilters !== null && _this.hasEmptyObjectVals(filters)) {
2930				_this.checkFilters();
2931			}
2932			/**
2933			 * Process the location data
2934			 */
2935			// Raw data
2936			if ( _this.settings.dataRaw !== null ) {
2937				_this.processData(mappingObject, originPoint, dataRequest, page);
2938			}
2939			// Remote data
2940			else {
2941				dataRequest.done(function (data) {
2942					_this.processData(mappingObject, originPoint, data, page);
2943				});
2944			}
2945		},
2946
2947		/**
2948		 * Reset disabled form fields
2949		 */
2950		resetDisabledFilterVals: function() {
2951			this.writeDebug('resetDisabledFilterVals');
2952
2953			for (var taxKey in this.settings.taxonomyFilters) {
2954				if (this.settings.taxonomyFilters.hasOwnProperty(taxKey)) {
2955					for (var x = 0; x < this.settings.taxonomyFilters[taxKey].length; x++) {
2956						$('#' + this.settings.taxonomyFilters[taxKey] + ' input,option').each(function () {
2957							var disabled = $(this).attr('disabled');
2958
2959							if (typeof disabled !== 'undefined') {
2960								$(this).removeAttr('disabled');
2961							}
2962						});
2963					}
2964				}
2965			}
2966		},
2967
2968		/**
2969		 * Get available filter values
2970		 *
2971		 * @param callback
2972		 */
2973		getAvailableFilters: function(callback) {
2974			this.writeDebug('getAvailableFilters');
2975			var availableValues = [];
2976
2977			for (var location in locationset) {
2978				if (locationset.hasOwnProperty(location)) {
2979					// Loop through the location values.
2980					for (var locationKey in locationset[location]) {
2981						if (filters.hasOwnProperty(locationKey) && locationset[location][locationKey] !== '') {
2982							if (availableValues.hasOwnProperty(locationKey)) {
2983								var availableVal = availableValues[locationKey].concat(',', locationset[location][locationKey].replace(', ', ',').trim());
2984								availableValues[locationKey] = Array.from(new Set(availableVal.split(','))).toString();
2985							} else {
2986								availableValues[locationKey] = locationset[location][locationKey].replace(', ', ',').trim();
2987							}
2988						}
2989					}
2990				}
2991			}
2992
2993			// Account for missing filter properties in location set.
2994			for (var keyName in filters) {
2995				if (!availableValues.hasOwnProperty(keyName)) {
2996					availableValues[keyName] = '';
2997				}
2998			}
2999
3000			callback(availableValues);
3001		},
3002
3003		/**
3004		 * Disable input fields that aren't available within the current location set
3005		 */
3006		maybeDisableFilterOptions: function() {
3007			this.writeDebug('maybeDisableFilterOptions');
3008			var availableValues = [];
3009			var _this = this;
3010
3011			// Initially reset any input/option fields that were previously disabled.
3012			this.resetDisabledFilterVals();
3013
3014			// Loop through current location set to determine what filter values are still available.
3015			this.getAvailableFilters( function(values) {
3016				availableValues = values;
3017
3018				// Save the original filter values for reference.
3019				if (typeof originalFilterVals === 'undefined') {
3020					originalFilterVals = availableValues;
3021				}
3022
3023				// Update input and option fields to disabled if they're not available.
3024				for (var key in _this.settings.taxonomyFilters) {
3025					if (_this.settings.taxonomyFilters.hasOwnProperty(key)) {
3026
3027						// Loop through the taxonomy filter group items.
3028						for (var i = 0; i < _this.settings.taxonomyFilters[key].length; i++) {
3029							if (_this.settings.taxonomyFilters.hasOwnProperty(key)) {
3030								$('#' + _this.settings.taxonomyFilters[key] + ' input, #' + _this.settings.taxonomyFilters[key] + ' option').each(function () {
3031
3032									// Initial determination of values that should be disabled.
3033									if ($(this).val() !== '' && ! Array.from(new Set(availableValues[key].split(','))).includes($(this).val())) {
3034										if (! disabledFilterVals.hasOwnProperty(key)) {
3035											disabledFilterVals[key] = [];
3036										}
3037
3038										// Handle select options and radio button values when there is no address input.
3039										if (
3040											(typeof addressInput === 'undefined' || addressInput === '') &&
3041											($(this).prop('tagName') === 'OPTION' || $(this).prop('type') === 'radio') &&
3042											_this.hasSingleGroupFilterVal(filters, key) &&
3043											Array.from(new Set(originalFilterVals[key].split(','))).includes($(this).val())
3044										) {
3045											return;
3046										}
3047
3048										// Handle select options and radio button values when there is address input.
3049										if (
3050											(typeof addressInput !== 'undefined' || addressInput !== '') &&
3051											($(this).prop('tagName') === 'OPTION' || $(this).prop('type') === 'radio') &&
3052											_this.hasSingleGroupFilterVal(filters, key) &&
3053											Array.from(new Set(originalFilterVals[key].split(','))).includes($(this).val()) &&
3054											_this.countFilters() === 1
3055										) {
3056											return;
3057										}
3058
3059										// Keep select options and radio button available values after one filter has been selected.
3060										if (
3061											($(this).prop('tagName') === 'OPTION' || $(this).prop('type') === 'radio') &&
3062											_this.hasSingleGroupFilterVal(filters, key) &&
3063											_this.countFilters() > 1 &&
3064											Array.from(new Set(originalFilterVals[key].split(','))).includes($(this).val()) &&
3065											! disabledFilterVals[key].includes($(this).val())
3066										) {
3067											return;
3068										}
3069
3070										// Track disabled values.
3071										if (
3072											disabledFilterVals.hasOwnProperty(key) &&
3073											Array.isArray(disabledFilterVals[key]) &&
3074											! disabledFilterVals[key].includes($(this).val())
3075										) {
3076											disabledFilterVals[key].push($(this).val());
3077										}
3078
3079										$(this).attr('disabled', true);
3080									}
3081								});
3082							}
3083						}
3084					}
3085				}
3086			});
3087		},
3088
3089		/**
3090		 * Processes the location data
3091		 *
3092		 * @param mappingObject {Object} all the potential mapping properties - latitude, longitude, origin, name, max
3093		 *     distance, page
3094		 * @param originPoint {Object} LatLng of origin point
3095		 * @param data {Object} location data
3096		 * @param page {number} current page number
3097		 */
3098		processData: function (mappingObject, originPoint, data, page) {
3099			this.writeDebug('processData',arguments);
3100			var _this = this;
3101			var i = 0;
3102			var orig_lat, orig_lng, origin, name, maxDistance, marker, bounds, storeStart, storeNumToShow, myOptions, distError, openMap, infowindow, nearestLoc;
3103			var taxFilters = {};
3104			var $lengthSwap = $('#' + _this.settings.lengthSwapID);
3105
3106			if (!this.isEmptyObject(mappingObject)) {
3107				orig_lat = mappingObject.lat;
3108				orig_lng = mappingObject.lng;
3109				origin = mappingObject.origin;
3110				name = mappingObject.name;
3111				maxDistance = mappingObject.distance;
3112			}
3113
3114			var $mapDiv = $('#' + _this.settings.mapID);
3115			// Get the length unit
3116			var distUnit = (_this.settings.lengthUnit === 'km') ? _this.settings.kilometersLang : _this.settings.milesLang;
3117
3118			// Save data and origin separately so we can potentially avoid multiple AJAX requests
3119			originalData = dataRequest;
3120			if ( typeof origin !== 'undefined' ) {
3121				originalOrigin = origin;
3122			}
3123
3124			// Callback
3125			if (_this.settings.callbackSuccess) {
3126				_this.settings.callbackSuccess.call(this, mappingObject, originPoint, data, page);
3127			}
3128
3129			openMap = $mapDiv.hasClass('bh-sl-map-open');
3130
3131			// Set a variable for fullMapStart, so we can detect the first run
3132			if (
3133				(_this.settings.fullMapStart === true && openMap === false) ||
3134				(_this.settings.autoGeocode === true && openMap === false) ||
3135				(_this.settings.defaultLoc === true && openMap === false)
3136			) {
3137				firstRun = true;
3138			} else if (
3139				(_this.settings.fullMapStart === true && reload === true) ||
3140				(_this.settings.autoGeocode === true && reload === true) ||
3141				(_this.settings.defaultLoc === true && reload === true)
3142			) {
3143				_this.reset();
3144			} else {
3145				_this.reset();
3146			}
3147
3148			$mapDiv.addClass('bh-sl-map-open');
3149
3150			// Process the location data depending on the data format type
3151			if (_this.settings.dataType === 'json' || _this.settings.dataType === 'jsonp') {
3152
3153				// Process JSON
3154				for(var x = 0; i < data.length; x++){
3155					var obj = data[x];
3156					var locationData = {};
3157
3158					// Parse each data variable
3159					for (var key in obj) {
3160						if (obj.hasOwnProperty(key)) {
3161							locationData[key] = obj[key];
3162						}
3163					}
3164
3165					_this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
3166
3167					i++;
3168				}
3169			}
3170			else if (_this.settings.dataType === 'kml') {
3171				// Process KML
3172				$(data).find('Placemark').each(function () {
3173					var locationData = {
3174						'name'       : $(this).find('name').text(),
3175						'lat'        : $(this).find('coordinates').text().split(',')[1],
3176						'lng'        : $(this).find('coordinates').text().split(',')[0],
3177						'description': $(this).find('description').text()
3178					};
3179
3180					_this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
3181
3182					i++;
3183				});
3184			}
3185			else {
3186				// Process XML
3187				$(data).find(_this.settings.xmlElement).each(function () {
3188					var locationData = {};
3189
3190					for (var key in this.attributes) {
3191						if (this.attributes.hasOwnProperty(key)) {
3192							locationData[this.attributes[key].name] = this.attributes[key].value;
3193						}
3194					}
3195
3196					_this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
3197
3198					i++;
3199				});
3200			}
3201
3202			// Name search - using taxonomy filter to handle
3203			if (_this.settings.nameSearch === true) {
3204				if (typeof searchInput !== 'undefined' && '' !== searchInput) {
3205
3206					if (_this.settings.nameAttribute.indexOf(',')) {
3207						nameAttrs = _this.settings.nameAttribute.split(',');
3208
3209						// Multiple name attributes should swap to exclusive filtering.
3210						if (_this.settings.exclusiveTax !== null) {
3211							_this.settings.exclusiveTax.concat(nameAttrs);
3212						} else {
3213							_this.settings.exclusiveTax = nameAttrs;
3214						}
3215
3216						for (var a = 0; a < nameAttrs.length; a++) {
3217							filters[nameAttrs[a].trim()] = [searchInput];
3218						}
3219					} else {
3220						filters[_this.settings.nameAttribute] = [searchInput];
3221					}
3222				}
3223
3224				// Check for a previous value.
3225				if (
3226					typeof searchInput !== 'undefined' &&
3227					'' === searchInput
3228				) {
3229					if (typeof nameAttrs !== 'undefined') {
3230						for (var pa = 0; pa < nameAttrs.length; pa++) {
3231							if (nameAttrs[pa] in filters) {
3232								delete filters[nameAttrs[pa]];
3233							}
3234						}
3235					} else {
3236						delete filters[_this.settings.nameAttribute];
3237					}
3238				}
3239			}
3240
3241			// Taxonomy filtering setup
3242			if (_this.settings.taxonomyFilters !== null || _this.settings.nameSearch === true) {
3243
3244				for(var k in filters) {
3245					if (filters.hasOwnProperty(k) && filters[k].length > 0) {
3246						// Let's use regex
3247						for (var z = 0; z < filters[k].length; z++) {
3248							// Creating a new object so we don't mess up the original filters
3249							if (!taxFilters[k]) {
3250								taxFilters[k] = [];
3251							}
3252
3253							// Swap pattern matching depending on name search vs. taxonomy filtering.
3254							if (typeof nameAttrs !== 'undefined') {
3255								if (nameAttrs.indexOf(k) !== -1) {
3256									taxFilters[k][z] = '(?:^|\\s)' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '');
3257								} else {
3258									taxFilters[k][z] = '(?=.*' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '') + '(?!\\s))';
3259								}
3260							} else {
3261								if (k === _this.settings.nameAttribute) {
3262									taxFilters[k][z] = '(?:^|\\s)' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '');
3263								} else {
3264									taxFilters[k][z] = '(?=.*' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '') + '(?!\\s))';
3265								}
3266							}
3267						}
3268					}
3269				}
3270
3271				// Filter the data
3272				if (!_this.isEmptyObject(taxFilters)) {
3273					locationset = $.grep(locationset, function (val) {
3274						return _this.filterData(val, taxFilters);
3275					});
3276				}
3277			}
3278
3279			// Sorting
3280			if (_this.settings.sortBy !== null && typeof _this.settings.sortBy === 'object') {
3281
3282				// Sort the multi-dimensional array by distance to get the nearest location first when enabled
3283				if (_this.settings.openNearest === true && typeof originalOrigin !== 'undefined') {
3284					this.sortNumerically(locationset, true);
3285
3286					// Save the closest location to a variable for openNearest setting
3287					if (typeof locationset[0] !== 'undefined') {
3288
3289						if (this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
3290							nearestLoc = locationset[locationset.length - 1];
3291						} else {
3292							nearestLoc = locationset[0];
3293						}
3294					}
3295				}
3296
3297				// Custom sorting
3298				_this.sortCustom(locationset);
3299			} else {
3300				// Sort the multi-dimensional array by distance
3301				if (typeof origin !== 'undefined') {
3302					_this.sortNumerically(locationset);
3303				}
3304
3305				// Check the closest marker
3306				if (_this.isEmptyObject(taxFilters)) {
3307					if (_this.settings.maxDistance === true && maxDistance) {
3308						if (typeof locationset[0] === 'undefined' || locationset[0].distance > maxDistance) {
3309							_this.notify(_this.settings.distanceErrorAlert + maxDistance + ' ' + distUnit);
3310						}
3311					}
3312					else {
3313						if (typeof locationset[0] !== 'undefined') {
3314							if (_this.settings.distanceAlert !== -1 && locationset[0].distance > _this.settings.distanceAlert) {
3315								_this.notify(_this.settings.distanceErrorAlert + _this.settings.distanceAlert + ' ' + distUnit);
3316								distError = true;
3317							}
3318						}
3319						else {
3320							_this.emptyResult();
3321							throw new Error('No locations found. Please check the dataLocation setting and path.');
3322							return;
3323						}
3324					}
3325				}
3326
3327				// Save the closest location to a variable for openNearest setting
3328				if (typeof locationset[0] !== 'undefined') {
3329					nearestLoc = locationset[0];
3330				}
3331			}
3332
3333			// Featured locations filtering
3334			if (_this.settings.featuredLocations === true) {
3335
3336				// Create array for featured locations
3337				featuredset = $.grep(locationset, function (val) {
3338						if (val.hasOwnProperty('featured')) {
3339								return val.featured === 'true';
3340						}
3341				});
3342
3343				// Featured location restrictions.
3344				featuredset = _this.featuredRestrictions(mappingObject);
3345
3346				// Create array for normal locations
3347				normalset = $.grep(locationset, function (val) {
3348					if (val.hasOwnProperty('featured')) {
3349						return val.featured !== 'true';
3350					}
3351				});
3352
3353				// Combine the arrays
3354				locationset = [];
3355				locationset = featuredset.concat(normalset);
3356			}
3357
3358			// Disable filter inputs if there are no locations with the values left.
3359			if (
3360				(firstRun !== true && _this.settings.exclusiveFiltering === false) ||
3361				(_this.settings.fullMapStart === true && _this.settings.exclusiveFiltering === false) ||
3362				(_this.settings.defaultLoc === true && _this.settings.exclusiveFiltering === false)
3363			) {
3364				_this.maybeDisableFilterOptions();
3365			}
3366
3367			// Slide in the map container
3368			if (_this.settings.slideMap === true) {
3369				$this.slideDown();
3370			}
3371
3372			// Output page numbers if pagination setting is true
3373			if (_this.settings.pagination === true) {
3374				_this.paginationSetup(page);
3375			}
3376
3377			// Alternative method to display no results if locations are too far away instead of all locations.
3378			if (_this.settings.altDistanceNoResult === true && nearestLoc.distance > _this.settings.distanceAlert) {
3379				_this.emptyResult();
3380				return;
3381			}
3382
3383			// Handle no results
3384			if (_this.isEmptyObject(locationset) || locationset[0].result === 'none') {
3385				_this.emptyResult();
3386				return;
3387			}
3388
3389			// Set up the modal window
3390			_this.modalWindow();
3391
3392			// Avoid error if number of locations is less than the default of 26
3393			if (_this.settings.storeLimit === -1 || locationset.length < _this.settings.storeLimit || (this.settings.fullMapStart === true && firstRun === true && (!isNaN(this.settings.fullMapStartListLimit) || this.settings.fullMapStartListLimit > 26 || this.settings.fullMapStartListLimit === -1))) {
3394				storeNum = locationset.length;
3395			}
3396			else {
3397				storeNum = _this.settings.storeLimit;
3398			}
3399
3400      // If fullMapStart is enabled and taxFilters is reset and name search and origin are empty, swap back to the original length.
3401      if (
3402        _this.settings.fullMapStart === true &&
3403        _this.isEmptyObject(taxFilters) &&
3404        (searchInput === '' || typeof searchInput === 'undefined') &&
3405        (addressInput === '' || typeof addressInput === 'undefined')
3406      ) {
3407        storeNum = locationset.length;
3408      }
3409
3410			// If pagination is on, change the store limit to the setting and slice the locationset array
3411			if (_this.settings.pagination === true) {
3412				storeNumToShow = _this.settings.locationsPerPage;
3413				storeStart = page * _this.settings.locationsPerPage;
3414
3415				if ( (storeStart + storeNumToShow) > locationset.length ) {
3416					storeNumToShow = _this.settings.locationsPerPage - ((storeStart + storeNumToShow) - locationset.length);
3417				}
3418
3419				locationset = locationset.slice(storeStart, storeStart + storeNumToShow);
3420				storeNum = locationset.length;
3421			}
3422			else {
3423				storeNumToShow = storeNum;
3424				storeStart = 0;
3425			}
3426
3427			// Output location results count
3428			_this.resultsTotalCount(locationset.length);
3429
3430			// Google maps settings
3431			if (
3432				(_this.settings.fullMapStart === true && firstRun === true && _this.settings.querystringParams !== true) ||
3433				(_this.settings.mapSettings.zoom === 0) ||
3434				(typeof origin === 'undefined') ||
3435				(distError === true) ||
3436				((_this.settings.maxDistance === true && firstRun === false) && this.countFilters() > 0)
3437			) {
3438				myOptions = _this.settings.mapSettings;
3439				bounds = new google.maps.LatLngBounds();
3440			}
3441			else if (_this.settings.pagination === true) {
3442				// Update the map to focus on the first point in the new set
3443				var nextPoint = new google.maps.LatLng(locationset[0].lat, locationset[0].lng);
3444
3445				if (page === 0) {
3446					_this.settings.mapSettings.center = originPoint;
3447					myOptions = _this.settings.mapSettings;
3448				}
3449				else {
3450					_this.settings.mapSettings.center = nextPoint;
3451					myOptions = _this.settings.mapSettings;
3452				}
3453			}
3454			else {
3455				_this.settings.mapSettings.center = originPoint;
3456				myOptions = _this.settings.mapSettings;
3457			}
3458
3459			// Create the map
3460			_this.map = new google.maps.Map(document.getElementById(_this.settings.mapID), myOptions);
3461
3462			// Re-center the map when the browser is re-sized
3463      		window.addEventListener('resize', function() {
3464				var center = _this.map.getCenter();
3465				google.maps.event.trigger(_this.map, 'resize');
3466				_this.map.setCenter(center);
3467			});
3468
3469			// Add map drag listener if setting is enabled and re-search on drag end
3470			if (_this.settings.dragSearch === true ) {
3471				_this.map.addListener('dragend', function() {
3472					_this.dragSearch(_this.map);
3473				});
3474			}
3475
3476			// Load the map
3477			$this.data(_this.settings.mapID.replace('#', ''), _this.map);
3478
3479			// Map set callback.
3480			if (_this.settings.callbackMapSet) {
3481				_this.settings.callbackMapSet.call(this, _this.map, originPoint, originalZoom, myOptions);
3482			}
3483
3484			// Initialize the infowindow
3485			if ( typeof InfoBubble !== 'undefined' && _this.settings.infoBubble !== null ) {
3486				var infoBubbleSettings = _this.settings.infoBubble;
3487				infoBubbleSettings.map = _this.map;
3488
3489				infowindow = new InfoBubble(infoBubbleSettings);
3490			} else {
3491				infowindow = new google.maps.InfoWindow();
3492			}
3493
3494			// Add origin marker if the setting is set
3495			_this.originMarker(_this.map, origin, originPoint);
3496
3497			// Handle pagination
3498			$(document).on('click.'+pluginName, '.bh-sl-pagination li', function (e) {
3499				e.preventDefault();
3500				// Run paginationChange
3501				_this.paginationChange($(this).attr('data-page'));
3502			});
3503
3504			// Inline directions
3505			_this.inlineDirections(_this.map, origin);
3506
3507			// Add markers and infowindows loop
3508			for (var y = 0; y <= storeNumToShow - 1; y++) {
3509				var letter = '';
3510
3511				if (page > 0) {
3512					letter = String.fromCharCode('A'.charCodeAt(0) + (storeStart + y));
3513				}
3514				else {
3515					letter = String.fromCharCode('A'.charCodeAt(0) + y);
3516				}
3517
3518				var point = new google.maps.LatLng(locationset[y].lat, locationset[y].lng);
3519				marker = _this.createMarker(point, locationset[y].name, locationset[y].address, letter, _this.map, locationset[y].category);
3520				marker.set('id', y);
3521				markers[y] = marker;
3522
3523				// Add marker ID to location data
3524				locationset[y].markerid = marker.get('id');
3525
3526				if (this.settings.dataRaw !== null) {
3527					for (var l = 0; l < this.settings.dataRaw.length; l++) {
3528						if (this.settings.dataRaw[l] && this.settings.dataRaw[l].hasOwnProperty('id') && this.settings.dataRaw[l].id === locationset[y].id) {
3529							this.settings.dataRaw[l].markerid = locationset[y].markerid;
3530						}
3531					}
3532				}
3533
3534				if (
3535					(_this.settings.fullMapStart === true && firstRun === true && _this.settings.querystringParams !== true) ||
3536					(_this.settings.mapSettings.zoom === 0) ||
3537					(typeof origin === 'undefined') ||
3538					(distError === true) ||
3539					((_this.settings.maxDistance === true && firstRun === false) && this.countFilters() > 0)
3540				) {
3541					bounds.extend(point);
3542				}
3543				// Pass variables to the pop-up infowindows
3544				_this.createInfowindow(marker, null, infowindow, storeStart, page);
3545			}
3546
3547			// Center and zoom if no origin or zoom was provided, or distance of first marker is greater than distanceAlert
3548			if (
3549				(_this.settings.fullMapStart === true && firstRun === true && _this.settings.querystringParams !== true) ||
3550				(_this.settings.mapSettings.zoom === 0) ||
3551				(typeof origin === 'undefined') ||
3552				(distError === true) ||
3553				((_this.settings.maxDistance === true && firstRun === false) && this.countFilters() > 0)
3554			) {
3555				_this.map.fitBounds(bounds);
3556
3557				// Prevent zooming in too far after fitBounds
3558				var zoomListener = google.maps.event.addListener(_this.map, 'idle', function() {
3559					if (_this.map.getZoom() > 16) {
3560						_this.map.setZoom(16);
3561					}
3562					google.maps.event.removeListener(zoomListener);
3563				});
3564			}
3565
3566			// Create the links that focus on the related marker
3567			var locList =  $('.' + _this.settings.locationList + ' ul');
3568			locList.empty();
3569
3570			// Set up the location list markup
3571			if (
3572				firstRun &&
3573				_this.settings.fullMapStartListLimit !== false &&
3574				!isNaN(_this.settings.fullMapStartListLimit) &&
3575				_this.settings.fullMapStartListLimit !== -1 &&
3576				markers.length > _this.settings.fullMapStartListLimit
3577			) {
3578				for (var m = 0; m < _this.settings.fullMapStartListLimit; m++) {
3579					var currentMarker = markers[m];
3580					_this.listSetup(currentMarker, storeStart, page);
3581				}
3582			} else {
3583				$(markers).each(function (x) {
3584					var currentMarker = markers[x];
3585					_this.listSetup(currentMarker, storeStart, page);
3586				});
3587			}
3588
3589			// Length unit swap setup
3590			if ($lengthSwap.length) {
3591				_this.lengthUnitSwap($lengthSwap);
3592
3593				$lengthSwap.on('change.'+pluginName, function (e) {
3594					e.stopPropagation();
3595					_this.lengthUnitSwap($lengthSwap);
3596				});
3597			}
3598
3599			// Open nearest location.
3600			_this.openNearestLocation(nearestLoc, infowindow, storeStart, page);
3601
3602			// MarkerClusterer setup
3603			if ( typeof MarkerClusterer !== 'undefined' && _this.settings.markerCluster !== null ) {
3604				var markerCluster = new MarkerClusterer(_this.map, markers, _this.settings.markerCluster);
3605			}
3606
3607			// Handle clicks from the list
3608			_this.listClick(_this.map, infowindow, storeStart, page);
3609
3610			// Add the list li background colors - this wil be dropped in a future version in favor of CSS
3611			$('.' + _this.settings.locationList + ' ul > li:even').css('background', _this.settings.listColor1);
3612			$('.' + _this.settings.locationList + ' ul > li:odd').css('background', _this.settings.listColor2);
3613
3614			// Visible markers list
3615			_this.visibleMarkersList(_this.map, markers);
3616
3617			// Fill in form values from query string parameters.
3618			if (_this.settings.querystringParams === true) {
3619				var $addressInput = $('#' + _this.settings.addressID);
3620				var $searchInput = $('#' + _this.settings.searchID);
3621
3622				// Address field.
3623				if (typeof mappingObj !== 'undefined' && mappingObj.hasOwnProperty('origin') && $addressInput.val() === '') {
3624					$addressInput.val(mappingObj.origin);
3625				}
3626
3627				// Name search field.
3628				if (typeof mappingObj !== 'undefined' && mappingObj.hasOwnProperty('name') && $searchInput.val() === '') {
3629					$searchInput.val(mappingObj.name);
3630				}
3631			}
3632
3633			// Modal ready callback
3634			if (_this.settings.modal === true && _this.settings.callbackModalReady) {
3635				_this.settings.callbackModalReady.call(this, mappingObject);
3636			}
3637
3638			// Filters callback
3639			if (_this.settings.callbackFilters) {
3640				_this.settings.callbackFilters.call(this, filters, mappingObject);
3641			}
3642		},
3643
3644		/**
3645		 * console.log helper function
3646		 *
3647		 * http://www.briangrinstead.com/blog/console-log-helper-function
3648		 */
3649		writeDebug: function () {
3650			if (window.console && this.settings.debug) {
3651				// Only run on the first time through - reset this function to the appropriate console.log helper
3652				if (Function.prototype.bind) {
3653					this.writeDebug = Function.prototype.bind.call(console.log, console, 'StoreLocator :');
3654				} else {
3655					this.writeDebug = function () {
3656						arguments[0] = 'StoreLocator : ' + arguments[0];
3657						Function.prototype.apply.call(console.log, console, arguments);
3658					};
3659				}
3660				this.writeDebug.apply(this, arguments);
3661			}
3662		}
3663
3664	});
3665
3666	// A really lightweight plugin wrapper around the constructor,
3667	// preventing against multiple instantiations and allowing any
3668	// public function (ie. a function whose name doesn't start
3669	// with an underscore) to be called via the jQuery plugin,
3670	// e.g. $(element).defaultPluginName('functionName', arg1, arg2)
3671	$.fn[ pluginName ] = function (options) {
3672		var args = arguments;
3673		// Is the first parameter an object (options), or was omitted, instantiate a new instance of the plugin
3674		if (options === undefined || typeof options === 'object') {
3675			return this.each(function () {
3676				// Only allow the plugin to be instantiated once, so we check that the element has no plugin instantiation yet
3677				if (!$.data(this, 'plugin_' + pluginName)) {
3678					// If it has no instance, create a new one, pass options to our plugin constructor, and store the plugin instance in the elements jQuery data object.
3679					$.data(this, 'plugin_' + pluginName, new Plugin( this, options ));
3680				}
3681			});
3682			// Treat this as a call to a public method
3683		} else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
3684			// Cache the method call to make it possible to return a value
3685			var returns;
3686
3687			this.each(function () {
3688				var instance = $.data(this, 'plugin_' + pluginName);
3689
3690				// Tests that there's already a plugin-instance and checks that the requested public method exists
3691				if (instance instanceof Plugin && typeof instance[options] === 'function') {
3692
3693					// Call the method of our plugin instance, and pass it the supplied arguments.
3694					returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
3695				}
3696
3697				// Allow instances to be destroyed via the 'destroy' method
3698				if (options === 'destroy') {
3699					$.data(this, 'plugin_' + pluginName, null);
3700				}
3701			});
3702
3703			// If the earlier cached method gives a value back return the value, otherwise return this to preserve chainability.
3704			return returns !== undefined ? returns : this;
3705		}
3706	};
3707
3708
3709})(jQuery, window, document);

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.