PageSourceSearch

https://www.stych.fr/js/storeLocator/js/plugins/storeLocator/jquery.storelocator.js?v=1748339952

js stych.fr collected 2026-10-09 22:42:06 UTC 133,320 bytes, 3,995 lines download raw bytes

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