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