Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
20544 amit.gupta 1
/*! jQuery Google Maps Store Locator - v2.7.2 - 2016-12-03
2
* http://www.bjornblog.com/web/jquery-store-locator-plugin
3
* Copyright (c) 2016 Bjorn Holine; Licensed MIT */
4
 
5
;(function ($, window, document, undefined) {
6
	'use strict';
7
 
8
	var pluginName = 'storeLocator';
9
 
10
	// Only allow for one instantiation of this script and make sure Google Maps API is included
11
	if (typeof $.fn[pluginName] !== 'undefined' || typeof google === 'undefined') {
12
		return;
13
	}
14
 
15
	// Variables used across multiple methods
16
	var $this, map, listTemplate, infowindowTemplate, dataTypeRead, originalOrigin, originalData, originalZoom, dataRequest, searchInput, addressInput, olat, olng, storeNum, directionsDisplay, directionsService, prevSelectedMarkerBefore, prevSelectedMarkerAfter, firstRun;
17
	var featuredset = [], locationset = [], normalset = [], markers = [];
18
	var filters = {}, locationData = {}, GeoCodeCalc = {}, mappingObj = {};
19
 
20
	// Create the defaults once. DO NOT change these settings in this file - settings should be overridden in the plugin call
21
	var defaults = {
22
		'mapID'                    : 'bh-sl-map',
23
		'locationList'             : 'bh-sl-loc-list',
24
		'formContainer'            : 'bh-sl-form-container',
25
		'formID'                   : 'bh-sl-user-location',
26
		'addressID'                : 'bh-sl-address',
27
		'regionID'                 : 'bh-sl-region',
28
		'mapSettings'              : {
29
			zoom     : 12,
30
			mapTypeId: google.maps.MapTypeId.ROADMAP
31
		},
32
		'markerImg'                : null,
33
		'markerDim'                : null,
34
		'catMarkers'               : null,
35
		'selectedMarkerImg'        : null,
36
		'selectedMarkerImgDim'     : null,
37
		'disableAlphaMarkers'      : false,
38
		'lengthUnit'               : 'm',
39
		'storeLimit'               : 26,
40
		'distanceAlert'            : 60,
41
		'dataType'                 : 'xml',
42
		'dataLocation'             : 'data/locations.xml',
43
		'dataRaw'                  : null,
44
		'xmlElement'               : 'marker',
45
		'listColor1'               : '#ffffff',
46
		'listColor2'               : '#eeeeee',
47
		'originMarker'             : false,
48
		'originMarkerImg'          : null,
49
		'originMarkerDim'          : null,
50
		'bounceMarker'             : true,
51
		'slideMap'                 : true,
52
		'modal'                    : false,
53
		'overlay'                  : 'bh-sl-overlay',
54
		'modalWindow'              : 'bh-sl-modal-window',
55
		'modalContent'             : 'bh-sl-modal-content',
56
		'closeIcon'                : 'bh-sl-close-icon',
57
		'defaultLoc'               : false,
58
		'defaultLat'               : null,
59
		'defaultLng'               : null,
60
		'autoComplete'             : false,
61
		'autoCompleteOptions'      : {},
62
		'autoGeocode'              : false,
63
		'geocodeID'                : null,
64
		'maxDistance'              : false,
65
		'maxDistanceID'            : 'bh-sl-maxdistance',
66
		'fullMapStart'             : false,
67
		'fullMapStartBlank'        : false,
68
		'fullMapStartListLimit'    : false,
69
		'noForm'                   : false,
70
		'loading'                  : false,
71
		'loadingContainer'         : 'bh-sl-loading',
72
		'featuredLocations'        : false,
73
		'pagination'               : false,
74
		'locationsPerPage'         : 10,
75
		'inlineDirections'         : false,
76
		'nameSearch'               : false,
77
		'searchID'                 : 'bh-sl-search',
78
		'nameAttribute'            : 'name',
79
		'visibleMarkersList'       : false,
80
		'dragSearch'               : false,
81
		'infowindowTemplatePath'   : 'assets/js/plugins/storeLocator/templates/infowindow-description.html',
82
		'listTemplatePath'         : 'assets/js/plugins/storeLocator/templates/location-list-description.html',
83
		'KMLinfowindowTemplatePath': 'assets/js/plugins/storeLocator/templates/kml-infowindow-description.html',
84
		'KMLlistTemplatePath'      : 'assets/js/plugins/storeLocator/templates/kml-location-list-description.html',
85
		'listTemplateID'           : null,
86
		'infowindowTemplateID'     : null,
87
		'taxonomyFilters'          : null,
88
		'taxonomyFiltersContainer' : 'bh-sl-filters-container',
89
		'exclusiveFiltering'       : false,
90
		'querystringParams'        : false,
91
		'debug'                    : false,
92
		'sessionStorage'           : false,
93
		'markerCluster'            : null,
94
		'infoBubble'               : null,
95
		// Callbacks
96
		'callbackNotify'           : null,
97
		'callbackRegion'           : null,
98
		'callbackBeforeSend'       : null,
99
		'callbackSuccess'          : null,
100
		'callbackModalOpen'        : null,
101
		'callbackModalReady'       : null,
102
		'callbackModalClose'       : null,
103
		'callbackJsonp'            : null,
104
		'callbackCreateMarker'     : null,
105
		'callbackPageChange'       : null,
106
		'callbackDirectionsRequest': null,
107
		'callbackCloseDirections'  : null,
108
		'callbackNoResults'        : null,
109
		'callbackListClick'        : null,
110
		'callbackMarkerClick'      : null,
111
		'callbackFilters'          : null,
112
		'callbackMapSet'           : null,
113
		// Language options
114
		'addressErrorAlert'        : 'Unable to find address',
115
		'autoGeocodeErrorAlert'    : 'Automatic location detection failed. Please fill in your address or zip code.',
116
		'distanceErrorAlert'       : 'Unfortunately, our closest location is more than ',
117
		'mileLang'                 : 'mile',
118
		'milesLang'                : 'miles',
119
		'kilometerLang'            : 'kilometer',
120
		'kilometersLang'           : 'kilometers',
121
		'noResultsTitle'           : 'No results',
122
		'noResultsDesc'            : 'No locations were found with the given criteria. Please modify your selections or input.',
123
		'nextPage'                 : 'Next »',
124
		'prevPage'                 : '« Prev'
125
	};
126
 
127
	// Plugin constructor
128
	function Plugin(element, options) {
129
		$this = $(element);
130
		this.element = element;
131
		this.settings = $.extend({}, defaults, options);
132
		this._defaults = defaults;
133
		this._name = pluginName;
134
		this.init();
135
	}
136
 
137
	// Avoid Plugin.prototype conflicts
138
	$.extend(Plugin.prototype, {
139
 
140
		/**
141
		 * Init function
142
		 */
143
		init: function () {
144
			var _this = this;
145
			this.writeDebug('init');
146
			// Calculate geocode distance functions
147
			if (this.settings.lengthUnit === 'km') {
148
				// Kilometers
149
				GeoCodeCalc.EarthRadius = 6367.0;
150
			}
151
			else {
152
				// Default is miles
153
				GeoCodeCalc.EarthRadius = 3956.0;
154
			}
155
 
156
			// KML is read as XML
157
			if (this.settings.dataType === 'kml') {
158
				dataTypeRead = 'xml';
159
			}
160
			else {
161
				dataTypeRead = this.settings.dataType;
162
			}
163
 
164
			// Add directions panel if enabled
165
			if(this.settings.inlineDirections === true) {
166
				$('.' + this.settings.locationList).prepend('<div class="bh-sl-directions-panel"></div>');
167
			}
168
 
169
			// Save the original zoom setting so it can be retrieved if taxonomy filtering resets it
170
			originalZoom = this.settings.mapSettings.zoom;
171
 
172
			// Add Handlebars helper for handling URL output
173
			Handlebars.registerHelper('niceURL', function(url) {
174
				if(url){
175
					return url.replace('https://', '').replace('http://', '');
176
				}
177
			});
178
 
179
			// Do taxonomy filtering if set
180
			if (this.settings.taxonomyFilters !== null) {
181
				this.taxonomyFiltering();
182
			}
183
 
184
			// Add modal window divs if set
185
			if (this.settings.modal === true) {
186
				// Clone the filters if there are any so they can be used in the modal
187
				if (this.settings.taxonomyFilters !== null) {
188
					// Clone the filters
189
					$('.' + this.settings.taxonomyFiltersContainer).clone(true, true).prependTo($this);
190
				}
191
 
192
				$this.wrap('<div class="' + this.settings.overlay + '"><div class="' + this.settings.modalWindow + '"><div class="' + this.settings.modalContent + '">');
193
				$('.' + this.settings.modalWindow).prepend('<div class="' + this.settings.closeIcon + '"></div>');
194
				$('.' + this.settings.overlay).hide();
195
			}
196
 
197
			// Set up Google Places autocomplete if it's set to true
198
			if (this.settings.autoComplete === true) {
199
				var searchInput = document.getElementById(this.settings.addressID);
200
				var autoPlaces = new google.maps.places.Autocomplete(searchInput, this.settings.autoCompleteOptions);
201
 
202
				// Add listener when autoComplete selection changes.
203
				if (this.settings.autoComplete === true) {
204
					autoPlaces.addListener('place_changed', function(e) {
205
						_this.processForm(e);
206
					});
207
				}
208
			}
209
 
210
			// Load the templates and continue from there
211
			this._loadTemplates();
212
		},
213
 
214
		/**
215
		 * Destroy
216
		 * Note: The Google map is not destroyed here because Google recommends using a single instance and reusing it (it's not really supported)
217
		 */
218
		destroy: function () {
219
			this.writeDebug('destroy');
220
			// Reset
221
			this.reset();
222
			var $mapDiv = $('#' + this.settings.mapID);
223
 
224
			// Remove marker event listeners
225
			if(markers.length) {
226
				for(var i = 0; i <= markers.length; i++) {
227
					google.maps.event.removeListener(markers[i]);
228
				}
229
			}
230
 
231
			// Remove markup
232
			$('.' + this.settings.locationList + ' ul').empty();
233
			if($mapDiv.hasClass('bh-sl-map-open')) {
234
				$mapDiv.empty().removeClass('bh-sl-map-open');
235
			}
236
 
237
			// Remove modal markup
238
			if (this.settings.modal === true) {
239
				$('. ' + this.settings.overlay).remove();
240
			}
241
 
242
			// Remove map style from container
243
			$mapDiv.attr('style', '');
244
 
245
			// Hide map container
246
			$this.hide();
247
			// Remove data
248
			$.removeData($this.get(0));
249
			// Remove namespaced events
250
			$(document).off(pluginName);
251
			// Unbind plugin
252
			$this.unbind();
253
		},
254
 
255
		/**
256
		 * Reset function
257
		 * This method clears out all the variables and removes events. It does not reload the map.
258
		 */
259
		reset: function () {
260
			this.writeDebug('reset');
261
			locationset = [];
262
			featuredset = [];
263
			normalset = [];
264
			markers = [];
265
			firstRun = false;
266
			$(document).off('click.'+pluginName, '.' + this.settings.locationList + ' li');
267
			if( $('.' + this.settings.locationList + ' .bh-sl-close-directions-container').length ) {
268
				$('.bh-sl-close-directions-container').remove();
269
			}
270
			if(this.settings.inlineDirections === true) {
271
				// Remove directions panel if it's there
272
				var $adp = $('.' + this.settings.locationList + ' .adp');
273
				if ( $adp.length > 0 ) {
274
					$adp.remove();
275
					$('.' + this.settings.locationList + ' ul').fadeIn();
276
				}
277
				$(document).off('click', '.' + this.settings.locationList + ' li .loc-directions a');
278
			}
279
			if(this.settings.pagination === true) {
280
				$(document).off('click.'+pluginName, '.bh-sl-pagination li');
281
			}
282
		},
283
 
284
		/**
285
		 * Reset the form filters
286
		 */
287
		formFiltersReset: function () {
288
			this.writeDebug('formFiltersReset');
289
			if (this.settings.taxonomyFilters === null) {
290
				return;
291
			}
292
 
293
			var $inputs = $('.' + this.settings.taxonomyFiltersContainer + ' input'),
294
				$selects = $('.' + this.settings.taxonomyFiltersContainer + ' select');
295
 
296
			if ( typeof($inputs) !== 'object') {
297
				return;
298
			}
299
 
300
			// Loop over the input fields
301
			$inputs.each(function() {
302
				if ($(this).is('input[type="checkbox"]') || $(this).is('input[type="radio"]')) {
303
					$(this).prop('checked',false);
304
				}
305
			});
306
 
307
			// Loop over select fields
308
			$selects.each(function() {
309
				$(this).prop('selectedIndex',0);
310
			});
311
		},
312
 
313
		/**
314
		 * Reload everything
315
		 * This method does a reset of everything and reloads the map as it would first appear.
316
		 */
317
		mapReload: function() {
318
			this.writeDebug('mapReload');
319
			this.reset();
320
 
321
			if ( this.settings.taxonomyFilters !== null ) {
322
				this.formFiltersReset();
323
				this.taxonomyFiltersInit();
324
			}
325
 
326
			if ((olat) && (olng)) {
327
				this.settings.mapSettings.zoom = originalZoom;
328
				this.processForm();
329
			}
330
			else {
331
				this.mapping(mappingObj);
332
			}
333
		},
334
 
335
		/**
336
		 * Notifications
337
		 * Some errors use alert by default. This is overridable with the callbackNotify option
338
		 *
339
		 * @param notifyText {string} the notification message
340
		 */
341
		notify: function (notifyText) {
342
			this.writeDebug('notify',notifyText);
343
			if (this.settings.callbackNotify) {
344
				this.settings.callbackNotify.call(this, notifyText);
345
			}
346
			else {
347
				alert(notifyText);
348
			}
349
		},
350
 
351
		/**
352
		 * Distance calculations
353
		 */
354
		geoCodeCalcToRadian: function (v) {
355
			this.writeDebug('geoCodeCalcToRadian',v);
356
			return v * (Math.PI / 180);
357
		},
358
		geoCodeCalcDiffRadian: function (v1, v2) {
359
			this.writeDebug('geoCodeCalcDiffRadian',arguments);
360
			return this.geoCodeCalcToRadian(v2) - this.geoCodeCalcToRadian(v1);
361
		},
362
		geoCodeCalcCalcDistance: function (lat1, lng1, lat2, lng2, radius) {
363
			this.writeDebug('geoCodeCalcCalcDistance',arguments);
364
			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) ))));
365
		},
366
 
367
		/**
368
		 * Check for query string
369
		 *
370
		 * @param param {string} query string parameter to test
371
		 * @returns {string} query string value
372
		 */
373
		getQueryString: function(param) {
374
			this.writeDebug('getQueryString',param);
375
			if(param) {
376
				param = param.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
377
				var regex = new RegExp('[\\?&]' + param + '=([^&#]*)'),
378
					results = regex.exec(location.search);
379
				return (results === null) ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
380
			}
381
		},
382
 
383
		/**
384
		 * Get google.maps.Map instance
385
		 *
386
		 * @returns {Object} google.maps.Map instance
387
		 */
388
		getMap: function() {
389
			return this.map;
390
		},
391
 
392
		/**
393
		 * Load templates via Handlebars templates in /templates or inline via IDs - private
394
		 */
395
		_loadTemplates: function () {
396
			this.writeDebug('_loadTemplates');
397
			var source;
398
			var _this = this;
399
			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>';
400
			// Get the KML templates
401
			if (this.settings.dataType === 'kml' && this.settings.listTemplateID === null && this.settings.infowindowTemplateID === null) {
402
 
403
				// Try loading the external template files
404
				$.when(
405
					// KML infowindows
406
					$.get(this.settings.KMLinfowindowTemplatePath, function (template) {
407
						source = template;
408
						infowindowTemplate = Handlebars.compile(source);
409
					}),
410
 
411
					// KML locations list
412
					$.get(this.settings.KMLlistTemplatePath, function (template) {
413
						source = template;
414
						listTemplate = Handlebars.compile(source);
415
					})
416
				).then(function () {
417
					// Continue to the main script if templates are loaded successfully
418
					_this.locator();
419
 
420
				}, function () {
421
					// KML templates not loaded
422
					$('.' + _this.settings.formContainer).append(templateError);
423
					throw new Error('Could not load storeLocator plugin templates');
424
				});
425
			}
426
			// Handle script tag template method
427
			else if (this.settings.listTemplateID !== null && this.settings.infowindowTemplateID !== null) {
428
				// Infowindows
429
				infowindowTemplate = Handlebars.compile($('#' + this.settings.infowindowTemplateID).html());
430
 
431
				// Locations list
432
				listTemplate = Handlebars.compile($('#' + this.settings.listTemplateID).html());
433
 
434
				// Continue to the main script
435
				_this.locator();
436
			}
437
			// Get the JSON/XML templates
438
			else {
439
				// Try loading the external template files
440
				$.when(
441
					// Infowindows
442
					$.get(this.settings.infowindowTemplatePath, function (template) {
443
						source = template;
444
						infowindowTemplate = Handlebars.compile(source);
445
					}),
446
 
447
					// Locations list
448
					$.get(this.settings.listTemplatePath, function (template) {
449
						source = template;
450
						listTemplate = Handlebars.compile(source);
451
					})
452
				).then(function () {
453
					// Continue to the main script if templates are loaded successfully
454
					_this.locator();
455
 
456
				}, function () {
457
					// JSON/XML templates not loaded
458
					$('.' + _this.settings.formContainer).append(templateError);
459
					throw new Error('Could not load storeLocator plugin templates');
460
				});
461
			}
462
		},
463
 
464
		/**
465
		 * Primary locator function runs after the templates are loaded
466
		 */
467
		locator: function () {
468
			this.writeDebug('locator');
469
			if (this.settings.slideMap === true) {
470
				// Let's hide the map container to begin
471
				$this.hide();
472
			}
473
 
474
			this._start();
475
			this._formEventHandler();
476
		},
477
 
478
		/**
479
		 * Form event handler setup - private
480
		 */
481
		_formEventHandler: function () {
482
			this.writeDebug('_formEventHandler');
483
			var _this = this;
484
			// ASP.net or regular submission?
485
			if (this.settings.noForm === true) {
486
				$(document).on('click.'+pluginName, '.' + this.settings.formContainer + ' button', function (e) {
487
					_this.processForm(e);
488
				});
489
				$(document).on('keydown.'+pluginName, function (e) {
490
					if (e.keyCode === 13 && $('#' + _this.settings.addressID).is(':focus')) {
491
						_this.processForm(e);
492
					}
493
				});
494
			}
495
			else {
496
				$(document).on('submit.'+pluginName, '#' + this.settings.formID, function (e) {
497
					_this.processForm(e);
498
				});
499
			}
500
 
501
			// Reset button trigger
502
			if ($('.bh-sl-reset').length && $('#' + this.settings.mapID).length) {
503
				$(document).on('click.' + pluginName, '.bh-sl-reset', function () {
504
					_this.mapReload();
505
				});
506
			}
507
		},
508
 
509
		/**
510
		 * AJAX data request - private
511
		 *
512
		 * @param lat {number} latitude
513
		 * @param lng {number} longitude
514
		 * @param address {string} street address
515
		 * @param geocodeData {object} full Google geocode results object
516
		 * @returns {Object} deferred object
517
		 */
518
		_getData: function (lat, lng, address, geocodeData ) {
519
			this.writeDebug('_getData',arguments);
520
			var _this = this,
521
				northEast = '',
522
				southWest = '',
523
				formattedAddress = '';
524
 
525
			// Define extra geocode result info
526
			if ( typeof geocodeData !== 'undefined' && typeof geocodeData.geometry.bounds !== 'undefined') {
527
				formattedAddress = geocodeData.formatted_address;
528
				northEast = JSON.stringify( geocodeData.geometry.bounds.getNorthEast() );
529
				southWest = JSON.stringify( geocodeData.geometry.bounds.getSouthWest() );
530
			}
531
 
532
			// Before send callback
533
			if (this.settings.callbackBeforeSend) {
534
				this.settings.callbackBeforeSend.call(this, lat, lng, address, formattedAddress, northEast, southWest);
535
			}
536
 
537
			// Raw data
538
			if( _this.settings.dataRaw !== null ) {
539
				// XML
540
				if( dataTypeRead === 'xml' ) {
541
					return $.parseXML(_this.settings.dataRaw);
542
				}
543
 
544
				// JSON
545
				else if( dataTypeRead === 'json' ) {
546
					if (Array.isArray && Array.isArray(_this.settings.dataRaw)) {
547
						return _this.settings.dataRaw;
548
					}
549
					else if (typeof _this.settings.dataRaw === 'string') {
550
						return $.parseJSON(_this.settings.dataRaw);
551
					}
552
					else {
553
						return [];
554
					}
555
 
556
				}
557
			}
558
			// Remote data
559
			else {
560
				var d = $.Deferred();
561
 
562
				// Loading
563
				if(this.settings.loading === true){
564
					$('.' + this.settings.formContainer).append('<div class="' + this.settings.loadingContainer +'"></div>');
565
				}
566
 
567
				// AJAX request
568
				$.ajax({
569
					type         : 'GET',
570
					url          : this.settings.dataLocation + (this.settings.dataType === 'jsonp' ? (this.settings.dataLocation.match(/\?/) ? '&' : '?') + 'callback=?' : ''),
571
					// Passing the lat, lng, address, formatted address and bounds with the AJAX request so they can optionally be used by back-end languages
572
					data: {
573
						'origLat' : lat,
574
						'origLng' : lng,
575
						'origAddress': address,
576
						'formattedAddress': formattedAddress,
577
						'boundsNorthEast' : northEast,
578
						'boundsSouthWest' : southWest
579
					},
580
					dataType     : dataTypeRead,
581
					jsonpCallback: (this.settings.dataType === 'jsonp' ? this.settings.callbackJsonp : null)
582
				}).done(function(p) {
583
					d.resolve(p);
584
 
585
					// Loading remove
586
					if(_this.settings.loading === true){
587
						$('.' + _this.settings.formContainer + ' .' + _this.settings.loadingContainer).remove();
588
					}
589
				}).fail(d.reject);
590
				return d.promise();
591
			}
592
		},
593
 
594
		/**
595
		 * Checks for default location, full map, and HTML5 geolocation settings - private
596
		 */
597
		_start: function () {
598
			this.writeDebug('_start');
599
			var _this = this,
600
					doAutoGeo = this.settings.autoGeocode,
601
					latlng,
602
					originAddress;
603
 
604
			// Full map blank start
605
			if (_this.settings.fullMapStartBlank !== false) {
606
				var $mapDiv = $('#' + _this.settings.mapID);
607
				$mapDiv.addClass('bh-sl-map-open');
608
				var myOptions = _this.settings.mapSettings;
609
				myOptions.zoom = _this.settings.fullMapStartBlank;
610
 
611
				latlng = new google.maps.LatLng(this.settings.defaultLat, this.settings.defaultLng);
612
				myOptions.center = latlng;
613
 
614
				// Create the map
615
				_this.map = new google.maps.Map(document.getElementById(_this.settings.mapID), myOptions);
616
 
617
				// Re-center the map when the browser is re-sized
618
				google.maps.event.addDomListener(window, 'resize', function() {
619
					var center = _this.map.getCenter();
620
					google.maps.event.trigger(_this.map, 'resize');
621
					_this.map.setCenter(center);
622
				});
623
 
624
				// Only do this once
625
				_this.settings.fullMapStartBlank = false;
626
				myOptions.zoom = originalZoom;
627
			}
628
			else {
629
				// If a default location is set
630
				if (this.settings.defaultLoc === true) {
631
					this.defaultLocation();
632
				}
633
 
634
				// If there is already have a value in the address bar
635
				if ($.trim($('#' + this.settings.addressID).val()) !== ''){
636
					_this.writeDebug('Using Address Field');
637
					_this.processForm(null);
638
					doAutoGeo = false; // No need for additional processing
639
				}
640
				// If show full map option is true
641
				else if (this.settings.fullMapStart === true) {
642
					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))) {
643
						_this.writeDebug('Using Query String');
644
						this.processForm(null);
645
						doAutoGeo = false; // No need for additional processing
646
					}
647
					else {
648
						this.mapping(null);
649
					}
650
				}
651
 
652
				// HTML5 auto geolocation API option
653
				if (this.settings.autoGeocode === true && doAutoGeo === true) {
654
					_this.writeDebug('Auto Geo');
655
 
656
					_this.htmlGeocode();
657
				}
658
 
659
				// HTML5 geolocation API button option
660
				if (this.settings.autoGeocode !== null) {
661
					_this.writeDebug('Button Geo');
662
 
663
					$(document).on('click.'+pluginName, '#' + this.settings.geocodeID, function () {
664
						_this.htmlGeocode();
665
					});
666
				}
667
			}
668
		},
669
 
670
		/**
671
		 * Geocode function used for auto geocode setting and geocodeID button
672
         */
673
		htmlGeocode: function() {
674
			this.writeDebug('htmlGeocode',arguments);
675
			var _this = this;
676
 
677
			if (this.settings.sessionStorage === true && window.sessionStorage && window.sessionStorage.getItem('myGeo')){
678
				this.writeDebug('Using Session Saved Values for GEO');
679
				this.autoGeocodeQuery(JSON.parse(window.sessionStorage.getItem('myGeo')));
680
				return false;
681
			}
682
			else if (navigator.geolocation) {
683
				navigator.geolocation.getCurrentPosition(function(position){
684
					_this.writeDebug('Current Position Result');
685
					// To not break autoGeocodeQuery then we create the obj to match the geolocation format
686
					var pos = {
687
						coords: {
688
							latitude : position.coords.latitude,
689
							longitude: position.coords.longitude,
690
							accuracy : position.coords.accuracy
691
						}
692
					};
693
					// Have to do this to get around scope issues
694
					if (_this.settings.sessionStorage === true && window.sessionStorage) {
695
						window.sessionStorage.setItem('myGeo',JSON.stringify(pos));
696
					}
697
					_this.autoGeocodeQuery(pos);
698
				}, function(error){
699
					_this._autoGeocodeError(error);
700
				});
701
			}
702
		},
703
 
704
		/**
705
		 * Geocode function used to geocode the origin (entered location)
706
		 */
707
		googleGeocode: function (thisObj) {
708
			thisObj.writeDebug('googleGeocode',arguments);
709
			var _this = thisObj;
710
			var geocoder = new google.maps.Geocoder();
711
			this.geocode = function (request, callbackFunction) {
712
				geocoder.geocode(request, function (results, status) {
713
					if (status === google.maps.GeocoderStatus.OK) {
714
						var result = {};
715
						result.latitude = results[0].geometry.location.lat();
716
						result.longitude = results[0].geometry.location.lng();
717
						result.geocodeResult = results[0];
718
						callbackFunction(result);
719
					} else {
720
						callbackFunction(null);
721
						throw new Error('Geocode was not successful for the following reason: ' + status);
722
					}
723
				});
724
			};
725
		},
726
 
727
		/**
728
		 * Reverse geocode to get address for automatic options needed for directions link
729
		 */
730
		reverseGoogleGeocode: function (thisObj) {
731
			thisObj.writeDebug('reverseGoogleGeocode',arguments);
732
			var _this = thisObj;
733
			var geocoder = new google.maps.Geocoder();
734
			this.geocode = function (request, callbackFunction) {
735
				geocoder.geocode(request, function (results, status) {
736
					if (status === google.maps.GeocoderStatus.OK) {
737
						if (results[0]) {
738
							var result = {};
739
							result.address = results[0].formatted_address;
740
							callbackFunction(result);
741
						}
742
					} else {
743
						callbackFunction(null);
744
						throw new Error('Reverse geocode was not successful for the following reason: ' + status);
745
					}
746
				});
747
			};
748
		},
749
 
750
		/**
751
		 * Rounding function used for distances
752
		 *
753
		 * @param num {number} the full number
754
		 * @param dec {number} the number of digits to show after the decimal
755
		 * @returns {number}
756
		 */
757
		roundNumber: function (num, dec) {
758
			this.writeDebug('roundNumber',arguments);
759
			return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
760
		},
761
 
762
		/**
763
		 * Checks to see if the object is empty. Using this instead of $.isEmptyObject for legacy browser support
764
		 *
765
		 * @param obj {Object} the object to check
766
		 * @returns {boolean}
767
		 */
768
		isEmptyObject: function (obj) {
769
			this.writeDebug('isEmptyObject',arguments);
770
			for (var key in obj) {
771
				if (obj.hasOwnProperty(key)) {
772
					return false;
773
				}
774
			}
775
			return true;
776
		},
777
 
778
		/**
779
		 * Checks to see if all the property values in the object are empty
780
		 *
781
		 * @param obj {Object} the object to check
782
		 * @returns {boolean}
783
		 */
784
		hasEmptyObjectVals: function (obj) {
785
			this.writeDebug('hasEmptyObjectVals',arguments);
786
				var objTest = true;
787
 
788
				for(var key in obj) {
789
					if(obj.hasOwnProperty(key)) {
790
						if(obj[key] !== '' && obj[key].length !== 0) {
791
							objTest = false;
792
						}
793
					}
794
				}
795
 
796
				return objTest;
797
		},
798
 
799
		/**
800
		 * Modal window close function
801
		 */
802
		modalClose: function () {
803
			this.writeDebug('modalClose');
804
			// Callback
805
			if (this.settings.callbackModalClose) {
806
				this.settings.callbackModalClose.call(this);
807
			}
808
 
809
			// Reset the filters
810
			filters = {};
811
 
812
			// Undo category selections
813
			$('.' + this.settings.overlay + ' select').prop('selectedIndex', 0);
814
			$('.' + this.settings.overlay + ' input').prop('checked', false);
815
 
816
			// Hide the modal
817
			$('.' + this.settings.overlay).hide();
818
		},
819
 
820
		/**
821
		 * Create the location variables - private
822
		 *
823
		 * @param loopcount {number} current marker id
824
		 */
825
		_createLocationVariables: function (loopcount) {
826
			this.writeDebug('_createLocationVariables',arguments);
827
			var value;
828
			locationData = {};
829
 
830
			for (var key in locationset[loopcount]) {
831
				if (locationset[loopcount].hasOwnProperty(key)) {
832
					value = locationset[loopcount][key];
833
 
834
					if (key === 'distance') {
835
						value = this.roundNumber(value, 2);
836
					}
837
 
838
					locationData[key] = value;
839
				}
840
			}
841
		},
842
 
843
		/**
844
		 * Location distance sorting function
845
		 *
846
		 * @param locationsarray {array} locationset array
847
		 */
848
		sortNumerically: function (locationsarray) {
849
			this.writeDebug('sortNumerically',arguments);
850
			locationsarray.sort(function (a, b) {
851
				return ((a.distance < b.distance) ? -1 : ((a.distance > b.distance) ? 1 : 0));
852
			});
853
		},
854
 
855
		/**
856
		 * Filter the data with Regex
857
		 *
858
		 * @param data {array} data array to check for filter values
859
		 * @param filters {Object} taxonomy filters object
860
		 * @returns {boolean}
861
		 */
862
		filterData: function (data, filters) {
863
			this.writeDebug('filterData',arguments);
864
			var filterTest = true;
865
 
866
			for (var k in filters) {
867
				if (filters.hasOwnProperty(k)) {
868
 
869
					// Exclusive filtering
870
					if(this.settings.exclusiveFiltering === true) {
871
						var filterTests = filters[k];
872
						var exclusiveTest = [];
873
 
874
						for(var l = 0; l < filterTests.length; l++) {
875
							exclusiveTest[l] = new RegExp(filterTests[l], 'i').test(data[k].replace(/[^\x00-\x7F]/g, ''));
876
						}
877
 
878
						if(exclusiveTest.indexOf(true) === -1) {
879
							filterTest = false;
880
						}
881
					}
882
					// Inclusive filtering
883
					else {
884
						if (typeof data[k] === 'undefined' || !(new RegExp(filters[k].join(''), 'i').test(data[k].replace(/[^\x00-\x7F]/g, '')))) {
885
							filterTest = false;
886
						}
887
					}
888
				}
889
			}
890
 
891
			if (filterTest) {
892
				return true;
893
			}
894
		},
895
 
896
		/**
897
		 * Build pagination numbers and next/prev links - private
898
		 *
899
		 * @param currentPage {number}
900
		 * @param totalPages {number}
901
		 * @returns {string}
902
		 */
903
		_paginationOutput: function(currentPage, totalPages) {
904
			this.writeDebug('_paginationOutput',arguments);
905
 
906
			currentPage = parseFloat(currentPage);
907
			var output = '';
908
			var nextPage = currentPage + 1;
909
			var prevPage = currentPage - 1;
910
 
911
			// Previous page
912
			if( currentPage > 0 ) {
913
				output += '<li class="bh-sl-next-prev" data-page="' + prevPage + '">' + this.settings.prevPage + '</li>';
914
			}
915
 
916
			// Add the numbers
917
			for (var p = 0; p < Math.ceil(totalPages); p++) {
918
				var n = p + 1;
919
 
920
				if (p === currentPage) {
921
					output += '<li class="bh-sl-current" data-page="' + p + '">' + n + '</li>';
922
				}
923
				else {
924
					output += '<li data-page="' + p + '">' + n + '</li>';
925
				}
926
			}
927
 
928
			// Next page
929
			if( nextPage < totalPages ) {
930
				output += '<li class="bh-sl-next-prev" data-page="' + nextPage + '">' + this.settings.nextPage + '</li>';
931
			}
932
 
933
			return output;
934
		},
935
 
936
		/**
937
		 * Set up the pagination pages
938
		 *
939
		 * @param currentPage {number} optional current page
940
		 */
941
		paginationSetup: function (currentPage) {
942
			this.writeDebug('paginationSetup',arguments);
943
			var pagesOutput = '';
944
			var totalPages;
945
			var $paginationList = $('.bh-sl-pagination-container .bh-sl-pagination');
946
 
947
			// Total pages
948
			if ( this.settings.storeLimit === -1 || locationset.length < this.settings.storeLimit ) {
949
				totalPages = locationset.length / this.settings.locationsPerPage;
950
			} else {
951
				totalPages = this.settings.storeLimit / this.settings.locationsPerPage;
952
			}
953
 
954
			// Current page check
955
			if (typeof currentPage === 'undefined') {
956
				currentPage = 0;
957
			}
958
 
959
			// Initial pagination setup
960
			if ($paginationList.length === 0) {
961
 
962
				pagesOutput = this._paginationOutput(currentPage, totalPages);
963
			}
964
			// Update pagination on page change
965
			else {
966
				// Remove the old pagination
967
				$paginationList.empty();
968
 
969
				// Add the numbers
970
				pagesOutput = this._paginationOutput(currentPage, totalPages);
971
			}
972
 
973
			$paginationList.append(pagesOutput);
974
		},
975
 
976
		/**
977
		 * Marker image setup
978
		 *
979
		 * @param markerUrl {string} path to marker image
980
		 * @param markerWidth {number} width of marker
981
		 * @param markerHeight {number} height of marker
982
		 * @returns {Object} Google Maps icon object
983
		 */
984
		markerImage: function (markerUrl, markerWidth, markerHeight) {
985
			this.writeDebug('markerImage',arguments);
986
			var markerImg;
987
 
988
			// User defined marker dimensions
989
			if(typeof markerWidth !== 'undefined' && typeof markerHeight !== 'undefined') {
990
				markerImg = {
991
					url: markerUrl,
992
					size: new google.maps.Size(markerWidth, markerHeight),
993
					scaledSize: new google.maps.Size(markerWidth, markerHeight)
994
				};
995
			}
996
			// Default marker dimensions: 32px x 32px
997
			else {
998
				markerImg = {
999
					url: markerUrl,
1000
					size: new google.maps.Size(32, 32),
1001
					scaledSize: new google.maps.Size(32, 32)
1002
				};
1003
			}
1004
 
1005
			return markerImg;
1006
		},
1007
 
1008
		/**
1009
		 * Map marker setup
1010
		 *
1011
		 * @param point {Object} LatLng of current location
1012
		 * @param name {string} location name
1013
		 * @param address {string} location address
1014
		 * @param letter {string} optional letter used for front-end identification and correlation between list and points
1015
		 * @param map {Object} the Google Map
1016
		 * @param category {string} location category/categories
1017
		 * @returns {Object} Google Maps marker
1018
		 */
1019
		createMarker: function (point, name, address, letter, map, category) {
1020
			this.writeDebug('createMarker',arguments);
1021
			var marker, markerImg, letterMarkerImg;
1022
			var categories = [];
1023
 
1024
			// Custom multi-marker image override (different markers for different categories
1025
			if(this.settings.catMarkers !== null) {
1026
				if(typeof category !== 'undefined') {
1027
					// Multiple categories
1028
					if(category.indexOf(',') !== -1) {
1029
						// Break the category variable into an array if there are multiple categories for the location
1030
						categories = category.split(',');
1031
						// With multiple categories the color will be determined by the last matched category in the data
1032
						for(var i = 0; i < categories.length; i++) {
1033
							if(categories[i] in this.settings.catMarkers) {
1034
								markerImg = this.markerImage(this.settings.catMarkers[categories[i]][0], parseInt(this.settings.catMarkers[categories[i]][1]), parseInt(this.settings.catMarkers[categories[i]][2]));
1035
							}
1036
						}
1037
					}
1038
					// Single category
1039
					else {
1040
						if(category in this.settings.catMarkers) {
1041
							markerImg = this.markerImage(this.settings.catMarkers[category][0], parseInt(this.settings.catMarkers[category][1]), parseInt(this.settings.catMarkers[category][2]));
1042
						}
1043
					}
1044
				}
1045
			}
1046
 
1047
			// Custom single marker image override
1048
			if(this.settings.markerImg !== null) {
1049
				if(this.settings.markerDim === null) {
1050
					markerImg = this.markerImage(this.settings.markerImg);
1051
				}
1052
				else {
1053
					markerImg = this.markerImage(this.settings.markerImg, this.settings.markerDim.width, this.settings.markerDim.height);
1054
				}
1055
			}
1056
 
1057
			// Marker setup
1058
			if (this.settings.callbackCreateMarker) {
1059
				// Marker override callback
1060
				marker = this.settings.callbackCreateMarker.call(this, map, point, letter, category);
1061
			}
1062
			else {
1063
				// Create the default markers
1064
				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))) {
1065
					marker = new google.maps.Marker({
1066
						position : point,
1067
						map      : map,
1068
						draggable: false,
1069
						icon: markerImg // Reverts to default marker if nothing is passed
1070
					});
1071
				}
1072
				else {
1073
					// Letter markers image
1074
					letterMarkerImg = {
1075
						url: 'https://mt.googleapis.com/vt/icon/name=icons/spotlight/spotlight-waypoint-b.png&text=' + letter + '&psize=16&font=fonts/Roboto-Regular.ttf&color=ff333333&ax=44&ay=48'
1076
					};
1077
 
1078
					// Letter markers
1079
					marker = new google.maps.Marker({
1080
						position : point,
1081
						map      : map,
1082
						icon     : letterMarkerImg,
1083
						draggable: false
1084
					});
1085
				}
1086
			}
1087
 
1088
			return marker;
1089
		},
1090
 
1091
		/**
1092
		 * Define the location data for the templates - private
1093
		 *
1094
		 * @param currentMarker {Object} Google Maps marker
1095
		 * @param storeStart {number} optional first location on the current page
1096
		 * @param page {number} optional current page
1097
		 * @returns {Object} extended location data object
1098
		 */
1099
		_defineLocationData: function (currentMarker, storeStart, page) {
1100
			this.writeDebug('_defineLocationData',arguments);
1101
			var indicator = '';
1102
			this._createLocationVariables(currentMarker.get('id'));
1103
 
1104
			var distLength;
1105
			if (locationData.distance <= 1) {
1106
				if (this.settings.lengthUnit === 'km') {
1107
					distLength = this.settings.kilometerLang;
1108
				}
1109
				else {
1110
					distLength = this.settings.mileLang;
1111
				}
1112
			}
1113
			else {
1114
				if (this.settings.lengthUnit === 'km') {
1115
					distLength = this.settings.kilometersLang;
1116
				}
1117
				else {
1118
					distLength = this.settings.milesLang;
1119
				}
1120
			}
1121
 
1122
			// Set up alpha character
1123
			var markerId = currentMarker.get('id');
1124
			// Use dot markers instead of alpha if there are more than 26 locations
1125
			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))) {
1126
				indicator = markerId + 1;
1127
			}
1128
			else {
1129
				if (page > 0) {
1130
					indicator = String.fromCharCode('A'.charCodeAt(0) + (storeStart + markerId));
1131
				}
1132
				else {
1133
					indicator = String.fromCharCode('A'.charCodeAt(0) + markerId);
1134
				}
1135
			}
1136
 
1137
			// Define location data
1138
			return {
1139
				location: [$.extend(locationData, {
1140
					'markerid': markerId,
1141
					'marker'  : indicator,
1142
					'length'  : distLength,
1143
					'origin'  : originalOrigin
1144
				})]
1145
			};
1146
		},
1147
 
1148
		/**
1149
		 * Set up the list templates
1150
		 *
1151
		 * @param marker {Object} Google Maps marker
1152
		 * @param storeStart {number} optional first location on the current page
1153
		 * @param page {number} optional current page
1154
		 */
1155
		listSetup: function (marker, storeStart, page) {
1156
			this.writeDebug('listSetup',arguments);
1157
			// Define the location data
1158
			var locations = this._defineLocationData(marker, storeStart, page);
1159
 
1160
			// Set up the list template with the location data
1161
			var listHtml = listTemplate(locations);
1162
			$('.' + this.settings.locationList + ' ul').append(listHtml);
1163
		},
1164
 
1165
		/**
1166
		 * Change the selected marker image
1167
		 *
1168
		 * @param marker {Object} Google Maps marker object
1169
		 */
1170
		changeSelectedMarker: function (marker) {
1171
			var markerImg;
1172
 
1173
			// Reset the previously selected marker
1174
			if ( typeof prevSelectedMarkerAfter !== 'undefined' ) {
1175
				prevSelectedMarkerAfter.setIcon( prevSelectedMarkerBefore );
1176
			}
1177
 
1178
			// Change the selected marker icon
1179
			if(this.settings.selectedMarkerImgDim === null) {
1180
				markerImg = this.markerImage(this.settings.selectedMarkerImg);
1181
			} else {
1182
				markerImg = this.markerImage(this.settings.selectedMarkerImg, this.settings.selectedMarkerImgDim.width, this.settings.selectedMarkerImgDim.height);
1183
			}
1184
 
1185
			// Save the marker before switching it
1186
			prevSelectedMarkerBefore = marker.icon;
1187
 
1188
			marker.setIcon( markerImg );
1189
 
1190
			// Save the marker to a variable so it can be reverted when another marker is clicked
1191
			prevSelectedMarkerAfter = marker;
1192
		},
1193
 
1194
		/**
1195
		 * Create the infowindow
1196
		 *
1197
		 * @param marker {Object} Google Maps marker object
1198
		 * @param location {string} indicates if the list or a map marker was clicked
1199
		 * @param infowindow Google Maps InfoWindow constructor
1200
		 * @param storeStart {number}
1201
		 * @param page {number}
1202
		 */
1203
		createInfowindow: function (marker, location, infowindow, storeStart, page) {
1204
			this.writeDebug('createInfowindow',arguments);
1205
			var _this = this;
1206
			// Define the location data
1207
			var locations = this._defineLocationData(marker, storeStart, page);
1208
 
1209
			// Set up the infowindow template with the location data
1210
			var formattedAddress = infowindowTemplate(locations);
1211
 
1212
			// Opens the infowindow when list item is clicked
1213
			if (location === 'left') {
1214
				infowindow.setContent(formattedAddress);
1215
				infowindow.open(marker.get('map'), marker);
1216
			}
1217
			// Opens the infowindow when the marker is clicked
1218
			else {
1219
				google.maps.event.addListener(marker, 'click', function () {
1220
					infowindow.setContent(formattedAddress);
1221
					infowindow.open(marker.get('map'), marker);
1222
					// Focus on the list
1223
					var markerId = marker.get('id');
1224
					var $selectedLocation = $('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']');
1225
 
1226
					if ($selectedLocation.length > 0) {
1227
						// Marker click callback
1228
						if (_this.settings.callbackMarkerClick) {
1229
							_this.settings.callbackMarkerClick.call(this, marker, markerId, $selectedLocation);
1230
						}
1231
 
1232
						$('.' + _this.settings.locationList + ' li').removeClass('list-focus');
1233
						$selectedLocation.addClass('list-focus');
1234
 
1235
						// Scroll list to selected marker
1236
						var $container = $('.' + _this.settings.locationList);
1237
						$container.animate({
1238
							scrollTop: $selectedLocation.offset().top - $container.offset().top + $container.scrollTop()
1239
						});
1240
					}
1241
 
1242
					// Custom selected marker override
1243
					if(_this.settings.selectedMarkerImg !== null) {
1244
						_this.changeSelectedMarker(marker);
1245
					}
1246
				});
1247
			}
1248
		},
1249
 
1250
		/**
1251
		 * HTML5 geocoding function for automatic location detection
1252
		 *
1253
		 * @param position {Object} coordinates
1254
		 */
1255
		autoGeocodeQuery: function (position) {
1256
			this.writeDebug('autoGeocodeQuery',arguments);
1257
			var _this = this,
1258
				distance = null,
1259
				$distanceInput = $('#' + this.settings.maxDistanceID),
1260
				originAddress;
1261
 
1262
			// Query string parameters
1263
			if(this.settings.querystringParams === true) {
1264
				// Check for distance query string parameters
1265
				if(this.getQueryString(this.settings.maxDistanceID)){
1266
					distance = this.getQueryString(this.settings.maxDistanceID);
1267
 
1268
					if($distanceInput.val() !== '') {
1269
						distance = $distanceInput.val();
1270
					}
1271
				}
1272
				else{
1273
					// Get the distance if set
1274
					if (this.settings.maxDistance === true) {
1275
						distance = $distanceInput.val() || '';
1276
					}
1277
				}
1278
			}
1279
			else {
1280
				// Get the distance if set
1281
				if (this.settings.maxDistance === true) {
1282
					distance = $distanceInput.val() || '';
1283
				}
1284
			}
1285
 
1286
			// The address needs to be determined for the directions link
1287
			var r = new this.reverseGoogleGeocode(this);
1288
			var latlng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
1289
			r.geocode({'latLng': latlng}, function (data) {
1290
				if (data !== null) {
1291
					originAddress = addressInput = data.address;
1292
					olat = mappingObj.lat = position.coords.latitude;
1293
					olng = mappingObj.lng = position.coords.longitude;
1294
					mappingObj.origin = originAddress;
1295
					mappingObj.distance = distance;
1296
					_this.mapping(mappingObj);
1297
				} else {
1298
					// Unable to geocode
1299
					_this.notify(_this.settings.addressErrorAlert);
1300
				}
1301
			});
1302
		},
1303
 
1304
		/**
1305
		 * Handle autoGeocode failure - private
1306
		 *
1307
		 */
1308
		_autoGeocodeError: function () {
1309
			this.writeDebug('_autoGeocodeError');
1310
			// If automatic detection doesn't work show an error
1311
			this.notify(this.settings.autoGeocodeErrorAlert);
1312
		},
1313
 
1314
		/**
1315
		 * Default location method
1316
		 */
1317
		defaultLocation: function() {
1318
			this.writeDebug('defaultLocation');
1319
			var _this = this,
1320
				distance = null,
1321
				$distanceInput = $('#' + this.settings.maxDistanceID),
1322
				originAddress;
1323
 
1324
			// Query string parameters
1325
			if(this.settings.querystringParams === true) {
1326
				// Check for distance query string parameters
1327
				if(this.getQueryString(this.settings.maxDistanceID)){
1328
					distance = this.getQueryString(this.settings.maxDistanceID);
1329
 
1330
					if($distanceInput.val() !== '') {
1331
						distance = $distanceInput.val();
1332
					}
1333
				}
1334
				else{
1335
					// Get the distance if set
1336
					if (this.settings.maxDistance === true) {
1337
						distance = $distanceInput.val() || '';
1338
					}
1339
				}
1340
			}
1341
			else {
1342
				// Get the distance if set
1343
				if (this.settings.maxDistance === true) {
1344
					distance = $distanceInput.val() || '';
1345
				}
1346
			}
1347
 
1348
			// The address needs to be determined for the directions link
1349
			var r = new this.reverseGoogleGeocode(this);
1350
			var latlng = new google.maps.LatLng(this.settings.defaultLat, this.settings.defaultLng);
1351
			r.geocode({'latLng': latlng}, function (data) {
1352
				if (data !== null) {
1353
					originAddress = addressInput = data.address;
1354
					olat = mappingObj.lat = _this.settings.defaultLat;
1355
					olng = mappingObj.lng = _this.settings.defaultLng;
1356
					mappingObj.distance = distance;
1357
					mappingObj.origin = originAddress;
1358
					_this.mapping(mappingObj);
1359
				} else {
1360
					// Unable to geocode
1361
					_this.notify(_this.settings.addressErrorAlert);
1362
				}
1363
			});
1364
		},
1365
 
1366
		/**
1367
		 * Change the page
1368
		 *
1369
		 * @param newPage {number} page to change to
1370
		 */
1371
		paginationChange: function (newPage) {
1372
			this.writeDebug('paginationChange',arguments);
1373
 
1374
			// Page change callback
1375
			if (this.settings.callbackPageChange) {
1376
				this.settings.callbackPageChange.call(this, newPage);
1377
			}
1378
 
1379
			mappingObj.page = newPage;
1380
			this.mapping(mappingObj);
1381
		},
1382
 
1383
		/**
1384
		 * Get the address by marker ID
1385
		 *
1386
		 * @param markerID {number} location ID
1387
		 * @returns {string} formatted address
1388
		 */
1389
		getAddressByMarker: function(markerID) {
1390
			this.writeDebug('getAddressByMarker',arguments);
1391
			var formattedAddress = "";
1392
			// Set up formatted address
1393
			if(locationset[markerID].address){ formattedAddress += locationset[markerID].address + ' '; }
1394
			if(locationset[markerID].address2){ formattedAddress += locationset[markerID].address2 + ' '; }
1395
			if(locationset[markerID].city){ formattedAddress += locationset[markerID].city + ', '; }
1396
			if(locationset[markerID].state){ formattedAddress += locationset[markerID].state + ' '; }
1397
			if(locationset[markerID].postal){ formattedAddress += locationset[markerID].postal + ' '; }
1398
			if(locationset[markerID].country){ formattedAddress += locationset[markerID].country + ' '; }
1399
 
1400
			return formattedAddress;
1401
		},
1402
 
1403
		/**
1404
		 * Clear the markers from the map
1405
		 */
1406
		clearMarkers: function() {
1407
			this.writeDebug('clearMarkers');
1408
			var locationsLimit = null;
1409
 
1410
			if (locationset.length < this.settings.storeLimit) {
1411
				locationsLimit = locationset.length;
1412
			}
1413
			else {
1414
				locationsLimit = this.settings.storeLimit;
1415
			}
1416
 
1417
			for (var i = 0; i < locationsLimit; i++) {
1418
				markers[i].setMap(null);
1419
			}
1420
		},
1421
 
1422
		/**
1423
		 * Handle inline direction requests
1424
		 *
1425
		 * @param origin {string} origin address
1426
		 * @param locID {number} location ID
1427
		 * @param map {Object} Google Map
1428
		 */
1429
		directionsRequest: function(origin, locID, map) {
1430
			this.writeDebug('directionsRequest',arguments);
1431
 
1432
			// Directions request callback
1433
			if (this.settings.callbackDirectionsRequest) {
1434
				this.settings.callbackDirectionsRequest.call(this, origin, locID, map);
1435
			}
1436
 
1437
			var destination = this.getAddressByMarker(locID);
1438
 
1439
			if(destination) {
1440
				// Hide the location list
1441
				$('.' + this.settings.locationList + ' ul').hide();
1442
				// Remove the markers
1443
				this.clearMarkers();
1444
 
1445
				// Clear the previous directions request
1446
				if(directionsDisplay !== null && typeof directionsDisplay !== 'undefined') {
1447
					directionsDisplay.setMap(null);
1448
					directionsDisplay = null;
1449
				}
1450
 
1451
				directionsDisplay = new google.maps.DirectionsRenderer();
1452
				directionsService = new google.maps.DirectionsService();
1453
 
1454
				// Directions request
1455
				directionsDisplay.setMap(map);
1456
				directionsDisplay.setPanel($('.bh-sl-directions-panel').get(0));
1457
 
1458
				var request = {
1459
					origin: origin,
1460
					destination: destination,
1461
					travelMode: google.maps.TravelMode.DRIVING
1462
				};
1463
				directionsService.route(request, function(response, status) {
1464
					if (status === google.maps.DirectionsStatus.OK) {
1465
						directionsDisplay.setDirections(response);
1466
					}
1467
				});
1468
 
1469
				$('.' + this.settings.locationList).prepend('<div class="bh-sl-close-directions-container"><div class="' + this.settings.closeIcon + '"></div></div>');
1470
			}
1471
 
1472
			$(document).off('click', '.' + this.settings.locationList + ' li .loc-directions a');
1473
		},
1474
 
1475
		/**
1476
		 * Close the directions panel and reset the map with the original locationset and zoom
1477
		 */
1478
		closeDirections: function() {
1479
			this.writeDebug('closeDirections');
1480
 
1481
			// Close directions callback
1482
			if (this.settings.callbackCloseDirections) {
1483
				this.settings.callbackCloseDirections.call(this);
1484
			}
1485
 
1486
			// Remove the close icon, remove the directions, add the list back
1487
			this.reset();
1488
 
1489
			if ((olat) && (olng)) {
1490
				if (this.countFilters() === 0) {
1491
					this.settings.mapSettings.zoom = originalZoom;
1492
				}
1493
				else {
1494
					this.settings.mapSettings.zoom = 0;
1495
				}
1496
				this.processForm(null);
1497
			}
1498
 
1499
			$(document).off('click.'+pluginName, '.' + this.settings.locationList + ' .bh-sl-close-icon');
1500
		},
1501
 
1502
		/**
1503
		 * Process the form values and/or query string
1504
		 *
1505
		 * @param e {Object} event
1506
		 */
1507
		processForm: function (e) {
1508
			this.writeDebug('processForm',arguments);
1509
			var _this = this,
1510
				distance = null,
1511
				$addressInput = $('#' + this.settings.addressID),
1512
				$searchInput = $('#' + this.settings.searchID),
1513
				$distanceInput = $('#' + this.settings.maxDistanceID),
1514
				region = '';
1515
 
1516
			// Stop the form submission
1517
			if(typeof e !== 'undefined' && e !== null) {
1518
				e.preventDefault();
1519
			}
1520
 
1521
			// Query string parameters
1522
			if(this.settings.querystringParams === true) {
1523
				// Check for query string parameters
1524
				if(this.getQueryString(this.settings.addressID) || this.getQueryString(this.settings.searchID) || this.getQueryString(this.settings.maxDistanceID)){
1525
					addressInput = this.getQueryString(this.settings.addressID);
1526
					searchInput = this.getQueryString(this.settings.searchID);
1527
					distance = this.getQueryString(this.settings.maxDistanceID);
1528
 
1529
					// The form should override the query string parameters
1530
					if($addressInput.val() !== '') {
1531
						addressInput = $addressInput.val();
1532
					}
1533
					if($searchInput.val() !== '') {
1534
						searchInput = $searchInput.val();
1535
					}
1536
					if($distanceInput.val() !== '') {
1537
						distance = $distanceInput.val();
1538
					}
1539
				}
1540
				else{
1541
					// Get the user input and use it
1542
					addressInput = $addressInput.val() || '';
1543
					searchInput = $searchInput.val() || '';
1544
					// Get the distance if set
1545
					if (this.settings.maxDistance === true) {
1546
						distance = $distanceInput.val() || '';
1547
					}
1548
				}
1549
			}
1550
			else {
1551
				// Get the user input and use it
1552
				addressInput = $addressInput.val() || '';
1553
				searchInput = $searchInput.val() || '';
1554
				// Get the distance if set
1555
				if (this.settings.maxDistance === true) {
1556
					distance = $distanceInput.val() || '';
1557
				}
1558
			}
1559
 
1560
			// Region
1561
			if (this.settings.callbackRegion) {
1562
				// Region override callback
1563
				region = this.settings.callbackRegion.call(this, addressInput, searchInput, distance);
1564
			} else {
1565
				// Region setting
1566
				region = $('#' + this.settings.regionID).val();
1567
			}
1568
 
1569
			if (addressInput === '' && searchInput === '') {
1570
				this._start();
1571
			}
1572
			else if(addressInput !== '') {
1573
 
1574
				// Geocode the origin if needed
1575
				if(typeof originalOrigin !== 'undefined' && typeof olat !== 'undefined' && typeof olng !== 'undefined' && (addressInput === originalOrigin)) {
1576
					// Run the mapping function
1577
					mappingObj.lat = olat;
1578
					mappingObj.lng = olng;
1579
					mappingObj.origin = addressInput;
1580
					mappingObj.name = searchInput;
1581
					mappingObj.distance = distance;
1582
					_this.mapping(mappingObj);
1583
				}
1584
				else {
1585
					var g = new this.googleGeocode(this);
1586
					g.geocode({'address': addressInput, 'region': region}, function (data) {
1587
						if (data !== null) {
1588
							olat = data.latitude;
1589
							olng = data.longitude;
1590
 
1591
							// Run the mapping function
1592
							mappingObj.lat = olat;
1593
							mappingObj.lng = olng;
1594
							mappingObj.origin = addressInput;
1595
							mappingObj.name = searchInput;
1596
							mappingObj.distance = distance;
1597
							mappingObj.geocodeResult = data.geocodeResult;
1598
							_this.mapping(mappingObj);
1599
						} else {
1600
							// Unable to geocode
1601
							_this.notify(_this.settings.addressErrorAlert);
1602
						}
1603
					});
1604
				}
1605
			}
1606
			else if(searchInput !== '') {
1607
				// Check for existing origin and remove if address input is blank.
1608
				if ( addressInput === '' ) {
1609
					delete mappingObj.origin;
1610
				}
1611
 
1612
				mappingObj.name = searchInput;
1613
				_this.mapping(mappingObj);
1614
			}
1615
		},
1616
 
1617
		/**
1618
		 * Checks distance of each location and sets up the locationset array
1619
		 *
1620
		 * @param data {Object} location data object
1621
		 * @param lat {number} origin latitude
1622
		 * @param lng {number} origin longitude
1623
		 * @param origin {string} origin address
1624
		 * @param maxDistance {number} maximum distance if set
1625
		 */
1626
		locationsSetup: function (data, lat, lng, origin, maxDistance) {
1627
			this.writeDebug('locationsSetup',arguments);
1628
			if (typeof origin !== 'undefined') {
1629
				if (!data.distance) {
1630
					data.distance = this.geoCodeCalcCalcDistance(lat, lng, data.lat, data.lng, GeoCodeCalc.EarthRadius);
1631
				}
1632
			}
1633
 
1634
			// Create the array
1635
			if (this.settings.maxDistance === true && typeof maxDistance !== 'undefined' && maxDistance !== null) {
1636
				if (data.distance <= maxDistance) {
1637
					locationset.push( data );
1638
				}
1639
				else {
1640
					return;
1641
				}
1642
			}
1643
			else if(this.settings.maxDistance === true && this.settings.querystringParams === true && typeof maxDistance !== 'undefined' && maxDistance !== null) {
1644
				if (data.distance <= maxDistance) {
1645
					locationset.push( data );
1646
				}
1647
				else {
1648
					return;
1649
				}
1650
			}
1651
			else {
1652
				locationset.push( data );
1653
			}
1654
		},
1655
 
1656
		/**
1657
		 * Count the selected filters
1658
		 *
1659
		 * @returns {number}
1660
		 */
1661
		countFilters: function () {
1662
			this.writeDebug('countFilters');
1663
			var filterCount = 0;
1664
 
1665
			if (!this.isEmptyObject(filters)) {
1666
				for (var key in filters) {
1667
					if (filters.hasOwnProperty(key)) {
1668
						filterCount += filters[key].length;
1669
					}
1670
				}
1671
			}
1672
 
1673
			return filterCount;
1674
		},
1675
 
1676
		/**
1677
		 * Find the existing checked boxes for each checkbox filter - private
1678
		 *
1679
		 * @param key {string} object key
1680
		 */
1681
		_existingCheckedFilters: function(key) {
1682
			this.writeDebug('_existingCheckedFilters',arguments);
1683
			$('#' + this.settings.taxonomyFilters[key] + ' input[type=checkbox]').each(function () {
1684
				if ($(this).prop('checked')) {
1685
					var filterVal = $(this).val();
1686
 
1687
					// Only add the taxonomy id if it doesn't already exist
1688
					if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
1689
						filters[key].push(filterVal);
1690
					}
1691
				}
1692
			});
1693
		},
1694
 
1695
		/**
1696
		 * Find the existing selected value for each select filter - private
1697
		 *
1698
		 * @param key {string} object key
1699
		 */
1700
		_existingSelectedFilters: function(key) {
1701
			this.writeDebug('_existingSelectedFilters',arguments);
1702
			$('#' + this.settings.taxonomyFilters[key] + ' select').each(function () {
1703
				var filterVal = $(this).val();
1704
 
1705
				// Only add the taxonomy id if it doesn't already exist
1706
				if (typeof filterVal !== 'undefined' && filterVal !== '' &&  filters[key].indexOf(filterVal) === -1) {
1707
					filters[key] = [filterVal];
1708
				}
1709
			});
1710
		},
1711
 
1712
		/**
1713
		 * Find the existing selected value for each radio button filter - private
1714
		 *
1715
		 * @param key {string} object key
1716
		 */
1717
		_existingRadioFilters: function(key) {
1718
			this.writeDebug('_existingRadioFilters',arguments);
1719
			$('#' + this.settings.taxonomyFilters[key] + ' input[type=radio]').each(function () {
1720
				if ($(this).prop('checked')) {
1721
					var filterVal = $(this).val();
1722
 
1723
					// Only add the taxonomy id if it doesn't already exist
1724
					if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
1725
						filters[key] = [filterVal];
1726
					}
1727
				}
1728
			});
1729
		},
1730
 
1731
		/**
1732
		 * Check for existing filter selections
1733
		 */
1734
		checkFilters: function () {
1735
			this.writeDebug('checkFilters');
1736
			for(var key in this.settings.taxonomyFilters) {
1737
 
1738
				if(this.settings.taxonomyFilters.hasOwnProperty(key)) {
1739
					// Find the existing checked boxes for each checkbox filter
1740
					this._existingCheckedFilters(key);
1741
 
1742
					// Find the existing selected value for each select filter
1743
					this._existingSelectedFilters(key);
1744
 
1745
					// Find the existing value for each radio button filter
1746
					this._existingRadioFilters(key);
1747
				}
1748
			}
1749
		},
1750
 
1751
		/**
1752
		 * Check query string parameters for filter values.
1753
		 */
1754
		checkQueryStringFilters: function () {
1755
			this.writeDebug('checkQueryStringFilters',arguments);
1756
			// Loop through the filters.
1757
			for(var key in filters) {
1758
				if(filters.hasOwnProperty(key)) {
1759
					var filterVal = this.getQueryString(key);
1760
 
1761
					// Only add the taxonomy id if it doesn't already exist
1762
					if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
1763
						filters[key] = [filterVal];
1764
					}
1765
				}
1766
			}
1767
		},
1768
 
1769
		/**
1770
		 * Get the filter key from the taxonomyFilter setting
1771
		 *
1772
		 * @param filterContainer {string} ID of the changed filter's container
1773
		 */
1774
		getFilterKey: function (filterContainer) {
1775
			this.writeDebug('getFilterKey',arguments);
1776
			for (var key in this.settings.taxonomyFilters) {
1777
				if (this.settings.taxonomyFilters.hasOwnProperty(key)) {
1778
					for (var i = 0; i < this.settings.taxonomyFilters[key].length; i++) {
1779
						if (this.settings.taxonomyFilters[key] === filterContainer) {
1780
							return key;
1781
						}
1782
					}
1783
				}
1784
			}
1785
		},
1786
 
1787
		/**
1788
		 * Initialize or reset the filters object to its original state
1789
		 */
1790
		taxonomyFiltersInit: function () {
1791
			this.writeDebug('taxonomyFiltersInit');
1792
 
1793
			// Set up the filters
1794
			for(var key in this.settings.taxonomyFilters) {
1795
				if(this.settings.taxonomyFilters.hasOwnProperty(key)) {
1796
					filters[key] = [];
1797
				}
1798
			}
1799
		},
1800
 
1801
		/**
1802
		 * Taxonomy filtering
1803
		 */
1804
		taxonomyFiltering: function() {
1805
			this.writeDebug('taxonomyFiltering');
1806
			var _this = this;
1807
 
1808
			// Set up the filters
1809
			_this.taxonomyFiltersInit();
1810
 
1811
			// Check query string for taxonomy parameter keys.
1812
			_this.checkQueryStringFilters();
1813
 
1814
			// Handle filter updates
1815
			$('.' + this.settings.taxonomyFiltersContainer).on('change.'+pluginName, 'input, select', function (e) {
1816
				e.stopPropagation();
1817
 
1818
				var filterVal, filterContainer, filterKey;
1819
 
1820
				// Handle checkbox filters
1821
				if ($(this).is('input[type="checkbox"]')) {
1822
					// First check for existing selections
1823
					_this.checkFilters();
1824
 
1825
					filterVal = $(this).val();
1826
					filterContainer = $(this).closest('.bh-sl-filters').attr('id');
1827
					filterKey = _this.getFilterKey(filterContainer);
1828
 
1829
					if (filterKey) {
1830
						// Add or remove filters based on checkbox values
1831
						if ($(this).prop('checked')) {
1832
							// Add ids to the filter arrays as they are checked
1833
							if(filters[filterKey].indexOf(filterVal) === -1) {
1834
								filters[filterKey].push(filterVal);
1835
							}
1836
 
1837
							if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
1838
								if ((olat) && (olng)) {
1839
									_this.settings.mapSettings.zoom = 0;
1840
									_this.processForm();
1841
								}
1842
								else {
1843
									_this.mapping(mappingObj);
1844
								}
1845
							}
1846
						}
1847
						else {
1848
							// Remove ids from the filter arrays as they are unchecked
1849
							var filterIndex = filters[filterKey].indexOf(filterVal);
1850
							if (filterIndex > -1) {
1851
								filters[filterKey].splice(filterIndex, 1);
1852
								if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
1853
									if ((olat) && (olng)) {
1854
										if (_this.countFilters() === 0) {
1855
											_this.settings.mapSettings.zoom = originalZoom;
1856
										}
1857
										else {
1858
											_this.settings.mapSettings.zoom = 0;
1859
										}
1860
										_this.processForm();
1861
									}
1862
									else {
1863
										_this.mapping(mappingObj);
1864
									}
1865
								}
1866
							}
1867
						}
1868
					}
1869
				}
1870
				// Handle select or radio filters
1871
				else if ($(this).is('select') || $(this).is('input[type="radio"]')) {
1872
					// First check for existing selections
1873
					_this.checkFilters();
1874
 
1875
					filterVal = $(this).val();
1876
					filterContainer = $(this).closest('.bh-sl-filters').attr('id');
1877
					filterKey = _this.getFilterKey(filterContainer);
1878
 
1879
					// Check for blank filter on select since default val could be empty
1880
					if (filterVal) {
1881
						if (filterKey) {
1882
							filters[filterKey] = [filterVal];
1883
							if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
1884
								if ((olat) && (olng)) {
1885
									_this.settings.mapSettings.zoom = 0;
1886
									_this.processForm();
1887
								}
1888
								else {
1889
									_this.mapping(mappingObj);
1890
								}
1891
							}
1892
						}
1893
					}
1894
					// Reset if the default option is selected
1895
					else {
1896
						if (filterKey) {
1897
							filters[filterKey] = [];
1898
						}
1899
						_this.reset();
1900
						if ((olat) && (olng)) {
1901
							_this.settings.mapSettings.zoom = originalZoom;
1902
							_this.processForm();
1903
						}
1904
						else {
1905
							_this.mapping(mappingObj);
1906
						}
1907
					}
1908
				}
1909
			});
1910
		},
1911
 
1912
		/**
1913
		 * Updates the location list to reflect the markers that are displayed on the map
1914
		 *
1915
		 * @param markers {Object} Map markers
1916
		 * @param map {Object} Google map
1917
		 */
1918
		checkVisibleMarkers: function(markers, map) {
1919
			this.writeDebug('checkVisibleMarkers',arguments);
1920
			var _this = this;
1921
			var locations, listHtml;
1922
 
1923
			// Empty the location list
1924
			$('.' + this.settings.locationList + ' ul').empty();
1925
 
1926
			// Set up the new list
1927
			$(markers).each(function(x, marker){
1928
				if(map.getBounds().contains(marker.getPosition())) {
1929
					// Define the location data
1930
					_this.listSetup(marker, 0, 0);
1931
 
1932
					// Set up the list template with the location data
1933
					listHtml = listTemplate(locations);
1934
					$('.' + _this.settings.locationList + ' ul').append(listHtml);
1935
				}
1936
			});
1937
 
1938
			// Re-add the list background colors
1939
			$('.' + this.settings.locationList + ' ul li:even').css('background', this.settings.listColor1);
1940
			$('.' + this.settings.locationList + ' ul li:odd').css('background', this.settings.listColor2);
1941
		},
1942
 
1943
		/**
1944
		 * Performs a new search when the map is dragged to a new position
1945
		 *
1946
		 * @param map {Object} Google map
1947
         */
1948
		dragSearch: function(map) {
1949
			this.writeDebug('dragSearch',arguments);
1950
			var newCenter = map.getCenter(),
1951
				newCenterCoords,
1952
				_this = this;
1953
 
1954
			// Save the new zoom setting
1955
			this.settings.mapSettings.zoom = map.getZoom();
1956
 
1957
			olat = mappingObj.lat = newCenter.lat();
1958
			olng = mappingObj.lng = newCenter.lng();
1959
 
1960
			// Determine the new origin addresss
1961
			var newAddress = new this.reverseGoogleGeocode(this);
1962
			newCenterCoords = new google.maps.LatLng(mappingObj.lat, mappingObj.lng);
1963
			newAddress.geocode({'latLng': newCenterCoords}, function (data) {
1964
				if (data !== null) {
1965
					mappingObj.origin = addressInput = data.address;
1966
					_this.mapping(mappingObj);
1967
				} else {
1968
					// Unable to geocode
1969
					_this.notify(_this.settings.addressErrorAlert);
1970
				}
1971
			});
1972
		},
1973
 
1974
		/**
1975
		 * Handle no results
1976
		 */
1977
		emptyResult: function() {
1978
			this.writeDebug('emptyResult',arguments);
1979
			var center,
1980
				locList =  $('.' + this.settings.locationList + ' ul'),
1981
				myOptions = this.settings.mapSettings,
1982
				noResults;
1983
 
1984
			// Create the map
1985
			this.map = new google.maps.Map(document.getElementById(this.settings.mapID), myOptions);
1986
 
1987
			// Callback
1988
			if (this.settings.callbackNoResults) {
1989
				this.settings.callbackNoResults.call(this, this.map, myOptions);
1990
			}
1991
 
1992
			// Empty the location list
1993
			locList.empty();
1994
 
1995
			// Append the no results message
1996
			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();
1997
			locList.append(noResults);
1998
 
1999
			// Center on the original origin or 0,0 if not available
2000
			if ((olat) && (olng)) {
2001
				center = new google.maps.LatLng(olat, olng);
2002
			} else {
2003
				center = new google.maps.LatLng(0, 0);
2004
			}
2005
 
2006
			this.map.setCenter(center);
2007
 
2008
			if (originalZoom) {
2009
				this.map.setZoom(originalZoom);
2010
			}
2011
		},
2012
 
2013
 
2014
		/**
2015
		 * Origin marker setup
2016
		 *
2017
		 * @param map {Object} Google map
2018
		 * @param origin {string} Origin address
2019
		 * @param originPoint {Object} LatLng of origin point
2020
		 */
2021
		originMarker: function(map, origin, originPoint) {
2022
			this.writeDebug('originMarker',arguments);
2023
 
2024
			if (this.settings.originMarker !== true) {
2025
				return;
2026
			}
2027
 
2028
			var marker,
2029
				originImg = '';
2030
 
2031
			if (typeof origin !== 'undefined') {
2032
				if(this.settings.originMarkerImg !== null) {
2033
					if(this.settings.originMarkerDim === null) {
2034
						originImg = this.markerImage(this.settings.originMarkerImg);
2035
					}
2036
					else {
2037
						originImg = this.markerImage(this.settings.originMarkerImg, this.settings.originMarkerDim.width, this.settings.originMarkerDim.height);
2038
					}
2039
				}
2040
				else {
2041
					originImg = {
2042
						url: 'https://mt.googleapis.com/vt/icon/name=icons/spotlight/spotlight-waypoint-a.png'
2043
					};
2044
				}
2045
 
2046
				marker = new google.maps.Marker({
2047
					position : originPoint,
2048
					map      : map,
2049
					icon     : originImg,
2050
					draggable: false
2051
				});
2052
			}
2053
		},
2054
 
2055
		/**
2056
		 * Modal window setup
2057
		 */
2058
		modalWindow: function() {
2059
			this.writeDebug('modalWindow');
2060
 
2061
			if (this.settings.modal !== true) {
2062
				return;
2063
			}
2064
 
2065
			var _this = this;
2066
 
2067
			// Callback
2068
			if (_this.settings.callbackModalOpen) {
2069
				_this.settings.callbackModalOpen.call(this);
2070
			}
2071
 
2072
			// Pop up the modal window
2073
			$('.' + _this.settings.overlay).fadeIn();
2074
			// Close modal when close icon is clicked and when background overlay is clicked
2075
			$(document).on('click.'+pluginName, '.' + _this.settings.closeIcon + ', .' + _this.settings.overlay, function () {
2076
				_this.modalClose();
2077
			});
2078
			// Prevent clicks within the modal window from closing the entire thing
2079
			$(document).on('click.'+pluginName, '.' + _this.settings.modalWindow, function (e) {
2080
				e.stopPropagation();
2081
			});
2082
			// Close modal when escape key is pressed
2083
			$(document).on('keyup.'+pluginName, function (e) {
2084
				if (e.keyCode === 27) {
2085
					_this.modalClose();
2086
				}
2087
			});
2088
		},
2089
 
2090
		/**
2091
		 * Handle clicks from the location list
2092
		 *
2093
		 * @param map {Object} Google map
2094
		 * @param infowindow
2095
		 * @param storeStart
2096
		 * @param page
2097
		 */
2098
		listClick: function(map, infowindow, storeStart, page) {
2099
			this.writeDebug('listClick',arguments);
2100
			var _this = this;
2101
 
2102
			$(document).on('click.' + pluginName, '.' + _this.settings.locationList + ' li', function () {
2103
				var markerId = $(this).data('markerid');
2104
				var selectedMarker = markers[markerId];
2105
 
2106
				// List click callback
2107
				if (_this.settings.callbackListClick) {
2108
					_this.settings.callbackListClick.call(this, markerId, selectedMarker);
2109
				}
2110
 
2111
				map.panTo(selectedMarker.getPosition());
2112
				var listLoc = 'left';
2113
				if (_this.settings.bounceMarker === true) {
2114
					selectedMarker.setAnimation(google.maps.Animation.BOUNCE);
2115
					setTimeout(function () {
2116
							selectedMarker.setAnimation(null);
2117
							_this.createInfowindow(selectedMarker, listLoc, infowindow, storeStart, page);
2118
						}, 700
2119
					);
2120
				}
2121
				else {
2122
					_this.createInfowindow(selectedMarker, listLoc, infowindow, storeStart, page);
2123
				}
2124
 
2125
				// Custom selected marker override
2126
				if (_this.settings.selectedMarkerImg !== null) {
2127
					_this.changeSelectedMarker(selectedMarker);
2128
				}
2129
 
2130
				// Focus on the list
2131
				$('.' + _this.settings.locationList + ' li').removeClass('list-focus');
2132
				$('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']').addClass('list-focus');
2133
			});
2134
 
2135
			// Prevent bubbling from list content links
2136
			$(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' li a', function(e) {
2137
				e.stopPropagation();
2138
			});
2139
		},
2140
 
2141
		/**
2142
		 * Output total results count if HTML element with .bh-sl-total-results class exists
2143
		 *
2144
		 * @param locCount
2145
		 */
2146
		resultsTotalCount: function(locCount) {
2147
			this.writeDebug('resultsTotalCount',arguments);
2148
 
2149
			var $resultsContainer = $('.bh-sl-total-results');
2150
 
2151
			if (typeof locCount === 'undefined' || locCount <= 0 || $resultsContainer.length === 0) {
2152
				return;
2153
			}
2154
 
2155
			$resultsContainer.text(locCount);
2156
		},
2157
 
2158
		/**
2159
		 * Inline directions setup
2160
		 *
2161
		 * @param map {Object} Google map
2162
		 * @param origin {string} Origin address
2163
		 */
2164
		inlineDirections: function(map, origin) {
2165
			this.writeDebug('inlineDirections',arguments);
2166
 
2167
			if(this.settings.inlineDirections !== true || typeof origin === 'undefined') {
2168
				return;
2169
			}
2170
 
2171
			var _this = this;
2172
 
2173
			// Open directions
2174
			$(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' li .loc-directions a', function (e) {
2175
				e.preventDefault();
2176
				var locID = $(this).closest('li').attr('data-markerid');
2177
				_this.directionsRequest(origin, locID, map);
2178
 
2179
				// Close directions
2180
				$(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' .bh-sl-close-icon', function () {
2181
					_this.closeDirections();
2182
				});
2183
			});
2184
		},
2185
 
2186
		/**
2187
		 * Visible markers list setup
2188
		 *
2189
		 * @param map {Object} Google map
2190
		 * @param markers {Object} Map markers
2191
		 */
2192
		visibleMarkersList: function(map, markers) {
2193
			this.writeDebug('visibleMarkersList',arguments);
2194
 
2195
			if(this.settings.visibleMarkersList !== true) {
2196
				return;
2197
			}
2198
 
2199
			var _this = this;
2200
 
2201
			// Add event listener to filter the list when the map is fully loaded
2202
			google.maps.event.addListenerOnce(map, 'idle', function(){
2203
				_this.checkVisibleMarkers(markers, map);
2204
			});
2205
 
2206
			// Add event listener for center change
2207
			google.maps.event.addListener(map, 'center_changed', function() {
2208
				_this.checkVisibleMarkers(markers, map);
2209
			});
2210
 
2211
			// Add event listener for zoom change
2212
			google.maps.event.addListener(map, 'zoom_changed', function() {
2213
				_this.checkVisibleMarkers(markers, map);
2214
			});
2215
		},
2216
 
2217
		/**
2218
		 * The primary mapping function that runs everything
2219
		 *
2220
		 * @param mappingObject {Object} all the potential mapping properties - latitude, longitude, origin, name, max distance, page
2221
		 */
2222
		mapping: function (mappingObject) {
2223
			this.writeDebug('mapping',arguments);
2224
			var _this = this;
2225
			var orig_lat, orig_lng, geocodeData, origin, originPoint, page;
2226
			if (!this.isEmptyObject(mappingObject)) {
2227
				orig_lat = mappingObject.lat;
2228
				orig_lng = mappingObject.lng;
2229
				geocodeData = mappingObject.geocodeResult;
2230
				origin = mappingObject.origin;
2231
				page = mappingObject.page;
2232
			}
2233
 
2234
			// Set the initial page to zero if not set
2235
			if ( _this.settings.pagination === true ) {
2236
				if (typeof page === 'undefined' || originalOrigin !== addressInput ) {
2237
					page = 0;
2238
				}
2239
			}
2240
 
2241
			// Data request
2242
			if (typeof origin === 'undefined' && this.settings.nameSearch === true) {
2243
				dataRequest = _this._getData();
2244
			}
2245
			else {
2246
				// Setup the origin point
2247
				originPoint = new google.maps.LatLng(orig_lat, orig_lng);
2248
 
2249
				// If the origin hasn't changed use the existing data so we aren't making unneeded AJAX requests
2250
				if((typeof originalOrigin !== 'undefined') && (origin === originalOrigin) && (typeof originalData !== 'undefined')) {
2251
					origin = originalOrigin;
2252
					dataRequest = originalData;
2253
				}
2254
				else {
2255
					// Do the data request - doing this in mapping so the lat/lng and address can be passed over and used if needed
2256
					dataRequest = _this._getData(olat, olng, origin, geocodeData);
2257
				}
2258
			}
2259
 
2260
			// Check filters here to handle selected filtering after page reload
2261
			if(_this.settings.taxonomyFilters !== null && _this.hasEmptyObjectVals(filters)) {
2262
				_this.checkFilters();
2263
			}
2264
			/**
2265
			 * Process the location data
2266
			 */
2267
			// Raw data
2268
			if( _this.settings.dataRaw !== null ) {
2269
				_this.processData(mappingObject, originPoint, dataRequest, page);
2270
			}
2271
			// Remote data
2272
			else {
2273
				dataRequest.done(function (data) {
2274
					_this.processData(mappingObject, originPoint, data, page);
2275
				});
2276
			}
2277
		},
2278
 
2279
		/**
2280
		 * Processes the location data
2281
		 *
2282
		 * @param mappingObject {Object} all the potential mapping properties - latitude, longitude, origin, name, max distance, page
2283
		 * @param originPoint {Object} LatLng of origin point
2284
		 * @param data {Object} location data
2285
		 * @param page {number} current page number
2286
		 */
2287
		processData: function (mappingObject, originPoint, data, page) {
2288
			this.writeDebug('processData',arguments);
2289
			var _this = this;
2290
			var i = 0;
2291
			var orig_lat, orig_lng, origin, name, maxDistance, marker, bounds, storeStart, storeNumToShow, myOptions, distError, openMap, infowindow;
2292
			var taxFilters = {};
2293
			if (!this.isEmptyObject(mappingObject)) {
2294
				orig_lat = mappingObject.lat;
2295
				orig_lng = mappingObject.lng;
2296
				origin = mappingObject.origin;
2297
				name = mappingObject.name;
2298
				maxDistance = mappingObject.distance;
2299
			}
2300
 
2301
			var $mapDiv = $('#' + _this.settings.mapID);
2302
			// Get the length unit
2303
			var distUnit = (_this.settings.lengthUnit === 'km') ? _this.settings.kilometersLang : _this.settings.milesLang;
2304
 
2305
			// Save data and origin separately so we can potentially avoid multiple AJAX requests
2306
			originalData = dataRequest;
2307
			if ( typeof origin !== 'undefined' ) {
2308
				originalOrigin = origin;
2309
			}
2310
 
2311
			// Callback
2312
			if (_this.settings.callbackSuccess) {
2313
				_this.settings.callbackSuccess.call(this);
2314
			}
2315
 
2316
			openMap = $mapDiv.hasClass('bh-sl-map-open');
2317
 
2318
			// Set a variable for fullMapStart so we can detect the first run
2319
			if (
2320
				( _this.settings.fullMapStart === true && openMap === false ) ||
2321
				( _this.settings.autoGeocode === true && openMap === false ) ||
2322
				( _this.settings.defaultLoc === true && openMap === false )
2323
			) {
2324
				firstRun = true;
2325
			}
2326
			else {
2327
				_this.reset();
2328
			}
2329
 
2330
			$mapDiv.addClass('bh-sl-map-open');
2331
 
2332
			// Process the location data depending on the data format type
2333
			if (_this.settings.dataType === 'json' || _this.settings.dataType === 'jsonp') {
2334
 
2335
				// Process JSON
2336
				for(var x = 0; i < data.length; x++){
2337
					var obj = data[x];
2338
					var locationData = {};
2339
 
2340
					// Parse each data variable
2341
					for (var key in obj) {
2342
						if (obj.hasOwnProperty(key)) {
2343
							locationData[key] = obj[key];
2344
						}
2345
					}
2346
 
2347
					_this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
2348
 
2349
					i++;
2350
				}
2351
			}
2352
			else if (_this.settings.dataType === 'kml') {
2353
				// Process KML
2354
				$(data).find('Placemark').each(function () {
2355
					var locationData = {
2356
						'name'       : $(this).find('name').text(),
2357
						'lat'        : $(this).find('coordinates').text().split(',')[1],
2358
						'lng'        : $(this).find('coordinates').text().split(',')[0],
2359
						'description': $(this).find('description').text()
2360
					};
2361
 
2362
					_this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
2363
 
2364
					i++;
2365
				});
2366
			}
2367
			else {
2368
				// Process XML
2369
				$(data).find(_this.settings.xmlElement).each(function () {
2370
					var locationData = {};
2371
 
2372
					for (var key in this.attributes) {
2373
						if (this.attributes.hasOwnProperty(key)) {
2374
							locationData[this.attributes[key].name] = this.attributes[key].value;
2375
						}
2376
					}
2377
 
2378
					_this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
2379
 
2380
					i++;
2381
				});
2382
			}
2383
 
2384
			// Name search - using taxonomy filter to handle
2385
			if (_this.settings.nameSearch === true) {
2386
				if(typeof searchInput !== 'undefined') {
2387
					filters[_this.settings.nameAttribute] = [searchInput];
2388
				}
2389
			}
2390
 
2391
			// Taxonomy filtering setup
2392
			if (_this.settings.taxonomyFilters !== null || _this.settings.nameSearch === true) {
2393
 
2394
				for(var k in filters) {
2395
					if (filters.hasOwnProperty(k) && filters[k].length > 0) {
2396
						// Let's use regex
2397
						for (var z = 0; z < filters[k].length; z++) {
2398
							// Creating a new object so we don't mess up the original filters
2399
							if (!taxFilters[k]) {
2400
								taxFilters[k] = [];
2401
							}
2402
							taxFilters[k][z] = '(?=.*\\b' + filters[k][z].replace(/([^\x00-\x7F]|[.*+?^=!:${}()|\[\]\/\\])/g, '') + '\\b)';
2403
						}
2404
					}
2405
				}
2406
				// Filter the data
2407
				if (!_this.isEmptyObject(taxFilters)) {
2408
					locationset = $.grep(locationset, function (val) {
2409
						return _this.filterData(val, taxFilters);
2410
					});
2411
				}
2412
			}
2413
 
2414
			// Sort the multi-dimensional array by distance
2415
			if (typeof origin !== 'undefined') {
2416
				_this.sortNumerically(locationset);
2417
			}
2418
 
2419
			// Featured locations filtering
2420
			if (_this.settings.featuredLocations === true) {
2421
				// Create array for featured locations
2422
				featuredset = $.grep(locationset, function (val) {
2423
					return val.featured === 'true';
2424
				});
2425
 
2426
				// Create array for normal locations
2427
				normalset = $.grep(locationset, function (val) {
2428
					return val.featured !== 'true';
2429
				});
2430
 
2431
				// Combine the arrays
2432
				locationset = [];
2433
				locationset = featuredset.concat(normalset);
2434
			}
2435
 
2436
			// Check the closest marker
2437
			if (_this.isEmptyObject(taxFilters)) {
2438
				if (_this.settings.maxDistance === true && maxDistance) {
2439
					if (typeof locationset[0] === 'undefined' || locationset[0].distance > maxDistance) {
2440
						_this.notify(_this.settings.distanceErrorAlert + maxDistance + ' ' + distUnit);
2441
					}
2442
				}
2443
				else {
2444
					if (typeof locationset[0] !== 'undefined') {
2445
						if (_this.settings.distanceAlert !== -1 && locationset[0].distance > _this.settings.distanceAlert) {
2446
							_this.notify(_this.settings.distanceErrorAlert + _this.settings.distanceAlert + ' ' + distUnit);
2447
							distError = true;
2448
						}
2449
					}
2450
					else {
2451
						throw new Error('No locations found. Please check the dataLocation setting and path.');
2452
					}
2453
				}
2454
			}
2455
 
2456
			// Slide in the map container
2457
			if (_this.settings.slideMap === true) {
2458
				$this.slideDown();
2459
			}
2460
 
2461
			// Handle no results
2462
			if (_this.isEmptyObject(locationset) || locationset[0].result === 'none') {
2463
				_this.emptyResult();
2464
				return;
2465
			}
2466
 
2467
			// Output page numbers if pagination setting is true
2468
			if (_this.settings.pagination === true) {
2469
				_this.paginationSetup(page);
2470
			}
2471
 
2472
			// Set up the modal window
2473
			_this.modalWindow();
2474
 
2475
			// Avoid error if number of locations is less than the default of 26
2476
			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))) {
2477
				storeNum = locationset.length;
2478
			}
2479
			else {
2480
				storeNum = _this.settings.storeLimit;
2481
			}
2482
 
2483
			// If pagination is on, change the store limit to the setting and slice the locationset array
2484
			if (_this.settings.pagination === true) {
2485
				storeNumToShow = _this.settings.locationsPerPage;
2486
				storeStart = page * _this.settings.locationsPerPage;
2487
 
2488
				if( (storeStart + storeNumToShow) > locationset.length ) {
2489
					storeNumToShow = _this.settings.locationsPerPage - ((storeStart + storeNumToShow) - locationset.length);
2490
				}
2491
 
2492
				locationset = locationset.slice(storeStart, storeStart + storeNumToShow);
2493
				storeNum = locationset.length;
2494
			}
2495
			else {
2496
				storeNumToShow = storeNum;
2497
				storeStart = 0;
2498
			}
2499
 
2500
			// Output location results count
2501
			_this.resultsTotalCount(locationset.length);
2502
 
2503
			// Google maps settings
2504
			if ((_this.settings.fullMapStart === true && firstRun === true) || (_this.settings.mapSettings.zoom === 0) || (typeof origin === 'undefined') || (distError === true)) {
2505
				myOptions = _this.settings.mapSettings;
2506
				bounds = new google.maps.LatLngBounds();
2507
			}
2508
			else if (_this.settings.pagination === true) {
2509
				// Update the map to focus on the first point in the new set
2510
				var nextPoint = new google.maps.LatLng(locationset[0].lat, locationset[0].lng);
2511
 
2512
				if (page === 0) {
2513
					_this.settings.mapSettings.center = originPoint;
2514
					myOptions = _this.settings.mapSettings;
2515
				}
2516
				else {
2517
					_this.settings.mapSettings.center = nextPoint;
2518
					myOptions = _this.settings.mapSettings;
2519
				}
2520
			}
2521
			else {
2522
				_this.settings.mapSettings.center = originPoint;
2523
				myOptions = _this.settings.mapSettings;
2524
			}
2525
 
2526
			// Create the map
2527
			_this.map = new google.maps.Map(document.getElementById(_this.settings.mapID), myOptions);
2528
 
2529
			// Re-center the map when the browser is re-sized
2530
			google.maps.event.addDomListener(window, 'resize', function() {
2531
				var center = _this.map.getCenter();
2532
				google.maps.event.trigger(_this.map, 'resize');
2533
				_this.map.setCenter(center);
2534
			});
2535
 
2536
 
2537
			// Add map drag listener if setting is enabled and re-search on drag end
2538
			if (_this.settings.dragSearch === true ) {
2539
				_this.map.addListener('dragend', function() {
2540
					_this.dragSearch(map);
2541
				});
2542
			}
2543
 
2544
			// Load the map
2545
			$this.data(_this.settings.mapID.replace('#', ''), _this.map);
2546
 
2547
			// Map set callback.
2548
			if (_this.settings.callbackMapSet) {
2549
				_this.settings.callbackMapSet.call(this, _this.map, originPoint, originalZoom, myOptions);
2550
			}
2551
 
2552
			// Initialize the infowondow
2553
			if ( typeof InfoBubble !== 'undefined' && _this.settings.infoBubble !== null ) {
2554
				var infoBubbleSettings = _this.settings.infoBubble;
2555
				infoBubbleSettings.map = _this.map;
2556
 
2557
				infowindow = new InfoBubble(infoBubbleSettings);
2558
			} else {
2559
				infowindow = new google.maps.InfoWindow();
2560
			}
2561
 
2562
 
2563
			// Add origin marker if the setting is set
2564
			_this.originMarker(_this.map, origin, originPoint);
2565
 
2566
			// Handle pagination
2567
			$(document).on('click.'+pluginName, '.bh-sl-pagination li', function (e) {
2568
				e.preventDefault();
2569
				// Run paginationChange
2570
				_this.paginationChange($(this).attr('data-page'));
2571
			});
2572
 
2573
			// Inline directions
2574
			_this.inlineDirections(_this.map, origin);
2575
 
2576
			// Add markers and infowindows loop
2577
			for (var y = 0; y <= storeNumToShow - 1; y++) {
2578
				var letter = '';
2579
 
2580
				if (page > 0) {
2581
					letter = String.fromCharCode('A'.charCodeAt(0) + (storeStart + y));
2582
				}
2583
				else {
2584
					letter = String.fromCharCode('A'.charCodeAt(0) + y);
2585
				}
2586
 
2587
				var point = new google.maps.LatLng(locationset[y].lat, locationset[y].lng);
2588
				marker = _this.createMarker(point, locationset[y].name, locationset[y].address, letter, _this.map, locationset[y].category);
2589
				marker.set('id', y);
2590
				markers[y] = marker;
2591
				if ((_this.settings.fullMapStart === true && firstRun === true) || (_this.settings.mapSettings.zoom === 0) || (typeof origin === 'undefined') || (distError === true)) {
2592
					bounds.extend(point);
2593
				}
2594
				// Pass variables to the pop-up infowindows
2595
				_this.createInfowindow(marker, null, infowindow, storeStart, page);
2596
			}
2597
 
2598
			// Center and zoom if no origin or zoom was provided, or distance of first marker is greater than distanceAlert
2599
			if ((_this.settings.fullMapStart === true && firstRun === true) || (_this.settings.mapSettings.zoom === 0) || (typeof origin === 'undefined') || (distError === true)) {
2600
				_this.map.fitBounds(bounds);
2601
			}
2602
 
2603
			// Create the links that focus on the related marker
2604
			var locList =  $('.' + _this.settings.locationList + ' ul');
2605
			locList.empty();
2606
 
2607
			// Set up the location list markup
2608
			if (firstRun && _this.settings.fullMapStartListLimit !== false && !isNaN(_this.settings.fullMapStartListLimit) && _this.settings.fullMapStartListLimit !== -1) {
2609
				for (var m = 0; m < _this.settings.fullMapStartListLimit; m++) {
2610
					var currentMarker = markers[m];
2611
					_this.listSetup(currentMarker, storeStart, page);
2612
				}
2613
			} else {
2614
				$(markers).each(function (x) {
2615
					var currentMarker = markers[x];
2616
					_this.listSetup(currentMarker, storeStart, page);
2617
				});
2618
			}
2619
 
2620
			// MarkerClusterer setup
2621
			if ( typeof MarkerClusterer !== 'undefined' && _this.settings.markerCluster !== null ) {
2622
				var markerCluster = new MarkerClusterer(_this.map, markers, _this.settings.markerCluster);
2623
			}
2624
 
2625
			// Handle clicks from the list
2626
			_this.listClick(_this.map, infowindow, storeStart, page);
2627
 
2628
			// Add the list li background colors - this wil be dropped in a future version in favor of CSS
2629
			$('.' + _this.settings.locationList + ' ul > li:even').css('background', _this.settings.listColor1);
2630
			$('.' + _this.settings.locationList + ' ul > li:odd').css('background', _this.settings.listColor2);
2631
 
2632
			// Visible markers list
2633
			_this.visibleMarkersList(_this.map, markers);
2634
 
2635
			// Modal ready callback
2636
			if (_this.settings.modal === true && _this.settings.callbackModalReady) {
2637
				_this.settings.callbackModalReady.call(this);
2638
			}
2639
 
2640
			// Filters callback
2641
			if (_this.settings.callbackFilters) {
2642
				_this.settings.callbackFilters.call(this, filters);
2643
			}
2644
		},
2645
 
2646
		/**
2647
		 * console.log helper function
2648
		 *
2649
		 * http://www.briangrinstead.com/blog/console-log-helper-function
2650
		 */
2651
		writeDebug: function () {
2652
			if (window.console && this.settings.debug) {
2653
				// Only run on the first time through - reset this function to the appropriate console.log helper
2654
				if (Function.prototype.bind) {
2655
					this.writeDebug = Function.prototype.bind.call(console.log, console, 'StoreLocator :');
2656
				} else {
2657
					this.writeDebug = function () {
2658
						arguments[0] = 'StoreLocator : ' + arguments[0];
2659
						Function.prototype.apply.call(console.log, console, arguments);
2660
					};
2661
				}
2662
				this.writeDebug.apply(this, arguments);
2663
			}
2664
		}
2665
 
2666
	});
2667
 
2668
	// A really lightweight plugin wrapper around the constructor,
2669
	// preventing against multiple instantiations and allowing any
2670
	// public function (ie. a function whose name doesn't start
2671
	// with an underscore) to be called via the jQuery plugin,
2672
	// e.g. $(element).defaultPluginName('functionName', arg1, arg2)
2673
	$.fn[ pluginName ] = function (options) {
2674
		var args = arguments;
2675
		// Is the first parameter an object (options), or was omitted, instantiate a new instance of the plugin
2676
		if (options === undefined || typeof options === 'object') {
2677
			return this.each(function () {
2678
				// Only allow the plugin to be instantiated once, so we check that the element has no plugin instantiation yet
2679
				if (!$.data(this, 'plugin_' + pluginName)) {
2680
					// 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.
2681
					$.data(this, 'plugin_' + pluginName, new Plugin( this, options ));
2682
				}
2683
			});
2684
			// Treat this as a call to a public method
2685
		} else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
2686
			// Cache the method call to make it possible to return a value
2687
			var returns;
2688
 
2689
			this.each(function () {
2690
				var instance = $.data(this, 'plugin_' + pluginName);
2691
 
2692
				// Tests that there's already a plugin-instance and checks that the requested public method exists
2693
				if (instance instanceof Plugin && typeof instance[options] === 'function') {
2694
 
2695
					// Call the method of our plugin instance, and pass it the supplied arguments.
2696
					returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
2697
				}
2698
 
2699
				// Allow instances to be destroyed via the 'destroy' method
2700
				if (options === 'destroy') {
2701
					$.data(this, 'plugin_' + pluginName, null);
2702
				}
2703
			});
2704
 
2705
			// If the earlier cached method gives a value back return the value, otherwise return this to preserve chainability.
2706
			return returns !== undefined ? returns : this;
2707
		}
2708
	};
2709
 
2710
 
2711
})(jQuery, window, document);