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