p;)e--}s[2*i+n]===p?w(r,s,i,e):w(r,s,++e,a),e<=o&&(i=e+1),o<=e&&(a=e-1)}}function w(t,e,r,s){y(t,r,s),y(e,2*r,2*s),y(e,2*r+1,2*s+1)}function y(t,e,r){var s=t[e];t[e]=t[r],t[r]=s}function M(t,e,r,s){t-=r,r=e-s;return t*t+r*r}const U={minZoom:0,maxZoom:16,minPoints:2,radius:40,extent:512,nodeSize:64,log:!1,generateId:!1,reduce:null,map:t=>t},x=Math.fround||(m=new Float32Array(1),t=>(m[0]=+t,m[0])),C=3,P=5,_=6;class E{constructor(t){this.options=Object.assign(Object.create(U),t),this.trees=new Array(this.options.maxZoom+1),this.stride=this.options.reduce?7:6,this.clusterProps=[]}load(e){const{log:r,minZoom:s,maxZoom:o}=this.options,i=(r&&console.time("total time"),`prepare ${e.length} points`);r&&console.time(i),this.points=e;var a=[];for(let t=0;t=s;t--){const s=+Date.now();n=this.trees[t]=this._createTree(this._cluster(n,t)),r&&console.log("z%d: %d clusters in %dms",t,n.numItems,+Date.now()-s)}return r&&console.timeEnd("total time"),this}getClusters(t,e){let r=((t[0]+180)%360+360)%360-180;var s=Math.max(-90,Math.min(90,t[1]));let o=180===t[2]?180:((t[2]+180)%360+360)%360-180;var i=Math.max(-90,Math.min(90,t[3]));if(360<=t[2]-t[0])r=-180,o=180;else if(r>o){const t=this.getClusters([r,s,180,i],e),a=this.getClusters([-180,s,o,i],e);return t.concat(a)}const a=this.trees[this._limitZoom(e)],n=a.range(L(r),O(i),L(o),O(s)),h=a.data,l=[];for(const t of n){const e=this.stride*t;l.push(1=i.length)throw new Error(s);var a=this.options.radius/(this.options.extent*Math.pow(2,r-1)),n=i[e*this.stride],h=i[e*this.stride+1],l=[];for(const e of o.within(n,h,a)){const r=e*this.stride;i[4+r]===t&&l.push(1a&&(o+=l[e+P])}if(o>f&&o>=r){let t,e=n*f,r=d*f,s=-1;var p,m=((i/c|0)<<5)+(a+1)+this.points.length;for(const n of g){const u=n*c;l[2+u]<=a||(l[2+u]=a,p=l[u+P],e+=l[u]*p,r+=l[1+u]*p,l[4+u]=m,h&&(t||(t=this._map(l,i,!0),s=this.clusterProps.length,this.clusterProps.push(t)),h(t,this._map(l,u))))}l[i+4]=m,u.push(e/o,r/o,1/0,m,-1,o),h&&u.push(s)}else{for(let t=0;t>5}_getOriginZoom(t){return(t-this.points.length)%32}_map(t,e,r){if(1<0?0:1{var e=n.getPosition(t);return{type:"Feature",geometry:{type:"Point",coordinates:[e.lng(),e.lat()]},properties:{marker:t}}});this.superCluster.load(r)}return e||(this.state.zoom<=this.maxZoom||r.zoom<=this.maxZoom)&&(e=!g(this.state,r)),this.state=r,e&&(this.clusters=this.cluster(t)),{clusters:this.clusters,changed:e}}cluster(t){t=t.map;return this.superCluster.getClusters([-180,-90,180,90],Math.round(t.getZoom())).map(t=>this.transformCluster(t))}transformCluster(t){var{geometry:{coordinates:[t,e]},properties:r}=t;return r.cluster?new a({markers:this.superCluster.getLeaves(r.cluster_id,1/0).map(t=>t.properties.marker),position:{lat:e,lng:t}}):(e=r.marker,new a({markers:[e],position:n.getPosition(e)}))}}class I{constructor(t,e){this.markers={sum:t.length};var t=e.map(t=>t.count),r=t.reduce((t,e)=>t+e,0);this.clusters={count:e.length,markers:{mean:r/e.length,sum:r,min:Math.min(...t),max:Math.max(...t)}}}}class T{render(t,e,r){var{count:t,position:s}=t,o=`
+
+
+
+${t}
+ `,i=`Cluster of ${t} markers`,a=Number(google.maps.Marker.MAX_ZINDEX)+t;if(n.isAdvancedMarkerAvailable(r)){const t=(new DOMParser).parseFromString(o,"image/svg+xml").documentElement,e=(t.setAttribute("transform","translate(0 25)"),{map:r,position:s,zIndex:a,title:i,content:t});return new google.maps.marker.AdvancedMarkerElement(e)}e={position:s,zIndex:a,title:i,icon:{url:"data:image/svg+xml;base64,"+btoa(o),anchor:new google.maps.Point(25,25)}};return new google.maps.Marker(e)}}class j{constructor(){var t,e=j,r=google.maps.OverlayView;for(t in r.prototype)e.prototype[t]=r.prototype[t]}}o.MarkerClustererEvents=void 0,(d=o.MarkerClustererEvents||(o.MarkerClustererEvents={})).CLUSTERING_BEGIN="clusteringbegin",d.CLUSTERING_END="clusteringend",d.CLUSTER_CLICK="click";const S=(t,e,r)=>{r.fitBounds(e.bounds)};return o.AbstractAlgorithm=t,o.AbstractViewportAlgorithm=e,o.Cluster=a,o.ClusterStats=I,o.DefaultRenderer=T,o.GridAlgorithm=class extends e{constructor(t){var{maxDistance:e=4e4,gridSize:r=40}=t;super(i(t,["maxDistance","gridSize"])),this.clusters=[],this.state={zoom:-1},this.maxDistance=e,this.gridSize=r}calculate(t){var{markers:t,map:e,mapCanvasProjection:r}=t,s={zoom:e.getZoom()};let o=!1;return this.state.zoom>=this.maxZoom&&s.zoom>=this.maxZoom||(o=!g(this.state,s)),this.state=s,e.getZoom()>=this.maxZoom?{clusters:this.noop({markers:t}),changed:o}:{clusters:this.cluster({markers:h(e,r,t,this.viewportPadding),map:e,mapCanvasProjection:r})}}cluster(t){let{markers:e,map:r,mapCanvasProjection:s}=t;return this.clusters=[],e.forEach(t=>{this.addToClosestCluster(t,r,s)}),this.clusters}addToClosestCluster(e,t,r){let s=this.maxDistance,o=null;for(let t=0;t{this.addMarker(t,!0)}),e||this.render()}removeMarker(t,e){var r=this.markers.indexOf(t);return-1!==r&&(n.setMap(t,null),this.markers.splice(r,1),e||this.render(),!0)}removeMarkers(t,e){let r=!1;return t.forEach(t=>{r=this.removeMarker(t,!0)||r}),r&&!e&&this.render(),r}clearMarkers(t){this.markers.length=0,t||this.render()}render(){const t=this.getMap();if(t instanceof google.maps.Map&&t.getProjection()){google.maps.event.trigger(this,o.MarkerClustererEvents.CLUSTERING_BEGIN,this);var{clusters:e,changed:r}=this.algorithm.calculate({markers:this.markers,map:t,mapCanvasProjection:this.getProjection()});if(r||null==r){const o=new Set;for(const t of e)1==t.markers.length&&o.add(t.markers[0]);const t=[];for(const s of this.clusters)null!=s.marker&&(1==s.markers.length?o.has(s.marker)||n.setMap(s.marker,null):t.push(s.marker));this.clusters=e,this.renderClusters(),requestAnimationFrame(()=>t.forEach(t=>n.setMap(t,null)))}google.maps.event.trigger(this,o.MarkerClustererEvents.CLUSTERING_END,this)}}onAdd(){this.idleListener=this.getMap().addListener("idle",this.render.bind(this)),this.render()}onRemove(){google.maps.event.removeListener(this.idleListener),this.reset()}reset(){this.markers.forEach(t=>n.setMap(t,null)),this.clusters.forEach(t=>t.delete()),this.clusters=[]}renderClusters(){const t=new I(this.markers,this.clusters),r=this.getMap();this.clusters.forEach(e=>{1===e.markers.length?e.marker=e.markers[0]:(e.marker=this.renderer.render(e,t,r),e.markers.forEach(t=>n.setMap(t,null)),this.onClusterClick&&e.marker.addListener("click",t=>{google.maps.event.trigger(this,o.MarkerClustererEvents.CLUSTER_CLICK,e),this.onClusterClick(t,e,r)})),n.setMap(e.marker,r)})}},o.MarkerUtils=n,o.NoopAlgorithm=class extends t{constructor(t){super(i(t,[]))}calculate(t){var{markers:t,map:e,mapCanvasProjection:r}=t;return{clusters:this.cluster({markers:t,map:e,mapCanvasProjection:r}),changed:!1}}cluster(t){return this.noop(t)}},o.SuperClusterAlgorithm=Z,o.SuperClusterViewportAlgorithm=class extends e{constructor(t){var{maxZoom:e,radius:r=60,viewportPadding:s=60}=t,t=i(t,["maxZoom","radius","viewportPadding"]);super({maxZoom:e,viewportPadding:s}),this.superCluster=new E(Object.assign({maxZoom:this.maxZoom,radius:r},t)),this.state={zoom:-1,view:[0,0,0,0]}}calculate(t){const e={zoom:Math.round(t.map.getZoom()),view:s(t.map.getBounds(),t.mapCanvasProjection,this.viewportPadding)};let r=!g(this.state,e);if(!g(t.markers,this.markers)){r=!0,this.markers=[...t.markers];const e=this.markers.map(t=>{var e=n.getPosition(t);return{type:"Feature",geometry:{type:"Point",coordinates:[e.lng(),e.lat()]},properties:{marker:t}}});this.superCluster.load(e)}return r&&(this.clusters=this.cluster(t),this.state=e),{clusters:this.clusters,changed:r}}cluster(t){var{map:t,mapCanvasProjection:e}=t,t={zoom:Math.round(t.getZoom()),view:s(t.getBounds(),e,this.viewportPadding)};return this.superCluster.getClusters(t.view,t.zoom).map(t=>this.transformCluster(t))}transformCluster(t){var{geometry:{coordinates:[t,e]},properties:r}=t;return r.cluster?new a({markers:this.superCluster.getLeaves(r.cluster_id,1/0).map(t=>t.properties.marker),position:{lat:e,lng:t}}):(e=r.marker,new a({markers:[e],position:n.getPosition(e)}))}},o.defaultOnClusterClickHandler=S,o.distanceBetweenPoints=u,o.extendBoundsToPaddedViewport=l,o.extendPixelBounds=c,o.filterMarkersToPaddedViewport=h,o.getPaddedViewport=s,o.noop=r,o.pixelBoundsToLatLngBounds=p,Object.defineProperty(o,"__esModule",{value:!0}),o}({});
\ No newline at end of file
diff --git a/dist/assets/js/plugins/storeLocator/jquery.storelocator.js b/dist/assets/js/plugins/storeLocator/jquery.storelocator.js
new file mode 100644
index 0000000..07fbed0
--- /dev/null
+++ b/dist/assets/js/plugins/storeLocator/jquery.storelocator.js
@@ -0,0 +1,4051 @@
+/*! jQuery Google Maps Store Locator - v3.4.1 - 2024-12-20
+* http://www.bjornblog.com/web/jquery-store-locator-plugin
+* Copyright (c) 2024 Bjorn Holine; Licensed MIT */
+
+;(function ($, window, document, undefined) {
+ 'use strict';
+
+ var pluginName = 'storeLocator';
+ var googleMapsScriptIsInjected = false;
+ var googleMapsAPIPromise = null;
+
+ // Only allow for one instantiation of this script
+ if (typeof $.fn[pluginName] !== 'undefined') {
+ return;
+ }
+
+ // Variables used across multiple methods
+ var $this, map, listTemplate, infowindowTemplate, dataTypeRead, originalOrigin, originalData, originalZoom, dataRequest, searchInput, addressInput, olat, olng, storeNum, directionsDisplay, directionsService, prevSelectedMarkerBefore, prevSelectedMarkerAfter, firstRun, reload, nameAttrs, originalFilterVals, paginationPage, locationsTotal;
+ var featuredset = [], locationset = [], normalset = [], markers = [];
+ var filters = {}, locationData = {}, GeoCodeCalc = {}, mappingObj = {}, disabledFilterVals = {};
+
+ // Create the defaults once. DO NOT change these settings in this file - settings should be overridden in the plugin call
+ var defaults = {
+ 'ajaxData' : null,
+ 'altDistanceNoResult' : false,
+ 'apiKey' : null,
+ 'autoComplete' : false,
+ 'autoCompleteDisableListener': false,
+ 'autoCompleteOptions' : {},
+ 'autoGeocode' : false,
+ 'bounceMarker' : true, // Deprecated.
+ 'catMarkers' : null,
+ 'dataLocation' : 'data/locations.json',
+ 'dataRaw' : null,
+ 'dataType' : 'json',
+ 'debug' : false,
+ 'defaultLat' : null,
+ 'defaultLng' : null,
+ 'defaultLoc' : false,
+ 'disableAlphaMarkers' : false,
+ 'distanceAlert' : 60,
+ 'dragSearch' : false,
+ 'exclusiveFiltering' : false,
+ 'exclusiveTax' : null,
+ 'featuredDistance' : null,
+ 'featuredLocations' : false,
+ 'fullMapStart' : false,
+ 'fullMapStartBlank' : false,
+ 'fullMapStartListLimit' : false,
+ 'infoBubble' : null,
+ 'inlineDirections' : false,
+ 'lazyLoadMap' : false,
+ 'lengthUnit' : 'm',
+ 'listColor1' : '#ffffff',
+ 'listColor2' : '#eeeeee',
+ 'loading' : false,
+ 'locationsPerPage' : 10,
+ 'mapSettings' : {
+ mapTypeId: 'roadmap',
+ zoom : 12,
+ },
+ 'mapSettingsID' : '',
+ 'markerCluster' : null,
+ 'markerImg' : null,
+ 'markerDim' : null,
+ 'maxDistance' : false,
+ 'modal' : false,
+ 'nameAttribute' : 'name',
+ 'nameSearch' : false,
+ 'noForm' : false,
+ 'openNearest' : false,
+ 'originMarker' : false,
+ 'originMarkerDim' : null,
+ 'originMarkerImg' : null,
+ 'pagination' : false,
+ 'querystringParams' : false,
+ 'selectedMarkerImg' : null,
+ 'selectedMarkerImgDim' : null,
+ 'sessionStorage' : false,
+ 'slideMap' : true,
+ 'sortBy' : null,
+ 'storeLimit' : 26,
+ 'taxonomyFilters' : null,
+ 'visibleMarkersList' : false,
+ 'xmlElement' : 'marker',
+ // HTML elements
+ 'addressID' : 'bh-sl-address',
+ 'closeIcon' : 'bh-sl-close-icon',
+ 'formContainer' : 'bh-sl-form-container',
+ 'formID' : 'bh-sl-user-location',
+ 'geocodeID' : null,
+ 'lengthSwapID' : 'bh-sl-length-swap',
+ 'loadingContainer' : 'bh-sl-loading',
+ 'locationList' : 'bh-sl-loc-list',
+ 'mapID' : 'bh-sl-map',
+ 'maxDistanceID' : 'bh-sl-maxdistance',
+ 'modalContent' : 'bh-sl-modal-content',
+ 'modalWindow' : 'bh-sl-modal-window',
+ 'orderID' : 'bh-sl-order',
+ 'overlay' : 'bh-sl-overlay',
+ 'regionID' : 'bh-sl-region',
+ 'searchID' : 'bh-sl-search',
+ 'sortID' : 'bh-sl-sort',
+ 'taxonomyFiltersContainer': 'bh-sl-filters-container',
+ // Templates
+ 'infowindowTemplatePath' : 'assets/js/plugins/storeLocator/templates/infowindow-description.html',
+ 'listTemplatePath' : 'assets/js/plugins/storeLocator/templates/location-list-description.html',
+ 'KMLinfowindowTemplatePath': 'assets/js/plugins/storeLocator/templates/kml-infowindow-description.html',
+ 'KMLlistTemplatePath' : 'assets/js/plugins/storeLocator/templates/kml-location-list-description.html',
+ 'listTemplateID' : null,
+ 'infowindowTemplateID' : null,
+ // Callbacks
+ 'callbackAutoGeoSuccess' : null,
+ 'callbackBeforeMapInject' : null,
+ 'callbackBeforeSend' : null,
+ 'callbackCloseDirections' : null,
+ 'callbackCreateMarker' : null,
+ 'callbackDirectionsRequest' : null,
+ 'callbackFilters' : null,
+ 'callbackFormVals' : null,
+ 'callbackGeocodeRestrictions': null,
+ 'callbackJsonp' : null,
+ 'callbackListClick' : null,
+ 'callbackMapSet' : null,
+ 'callbackMarkerClick' : null,
+ 'callbackModalClose' : null,
+ 'callbackModalOpen' : null,
+ 'callbackModalReady' : null,
+ 'callbackNearestLoc' : null,
+ 'callbackNoResults' : null,
+ 'callbackNotify' : null,
+ 'callbackOrder' : null,
+ 'callbackPageChange' : null,
+ 'callbackRegion' : null,
+ 'callbackSorting' : null,
+ 'callbackSuccess' : null,
+ // Language options
+ 'addressErrorAlert' : 'Unable to find address',
+ 'autoGeocodeErrorAlert': 'Automatic location detection failed. Please fill in your address or zip code.',
+ 'distanceErrorAlert' : 'Unfortunately, our closest location is more than ',
+ 'kilometerLang' : 'kilometer',
+ 'kilometersLang' : 'kilometers',
+ 'mileLang' : 'mile',
+ 'milesLang' : 'miles',
+ 'noResultsTitle' : 'No results',
+ 'noResultsDesc' : 'No locations were found with the given criteria. Please modify your selections or input.',
+ 'nextPage' : 'Next »',
+ 'prevPage' : '« Prev'
+ };
+
+ // Plugin constructor
+ function Plugin(element, options) {
+ $this = $(element);
+ this.element = element;
+ this.settings = $.extend({}, defaults, options);
+ this._defaults = defaults;
+ this._name = pluginName;
+
+ // Add Map ID to map settings if set.
+ if (this.settings.mapSettingsID !== '') {
+ this.settings.mapSettings.mapId = this.settings.mapSettingsID;
+ }
+
+ // Load Google Maps API when lazy load is enabled.
+ if (this.settings.lazyLoadMap && this.settings.apiKey !== null && typeof google === 'undefined') {
+ var _this = this;
+ var optionsQuery = {};
+ var loadMap = false;
+
+ // Load new marker library.
+ optionsQuery.libraries = 'marker';
+
+ // Autocomplete.
+ if (this.settings.autoComplete === true) {
+ optionsQuery.libraries = 'places,marker';
+ }
+
+ // Allow callback to resolve map loading when set.
+ if (this.settings.callbackBeforeMapInject) {
+ new Promise(function (resolve, reject) {
+ _this.settings.callbackBeforeMapInject.call(this, options, resolve);
+ }).then(function () {
+ _this.triggerMapLoad(optionsQuery)
+ });
+ } else {
+ _this.triggerMapLoad(optionsQuery)
+ }
+ } else {
+ this.init();
+ }
+ }
+
+ // Avoid Plugin.prototype conflicts
+ $.extend(Plugin.prototype, {
+
+ /**
+ * Init function
+ */
+ init: function () {
+ var _this = this;
+ this.writeDebug('init');
+
+ // Calculate geocode distance functions
+ if (this.settings.lengthUnit === 'km') {
+ // Kilometers
+ GeoCodeCalc.EarthRadius = 6367.0;
+ }
+ else {
+ // Default is miles
+ GeoCodeCalc.EarthRadius = 3956.0;
+ }
+
+ // KML is read as XML
+ if (this.settings.dataType === 'kml') {
+ dataTypeRead = 'xml';
+ }
+ else {
+ dataTypeRead = this.settings.dataType;
+ }
+
+ // Add directions panel if enabled
+ if (this.settings.inlineDirections === true) {
+ $('.' + this.settings.locationList).prepend('
');
+ }
+
+ // Save the original zoom setting so it can be retrieved if taxonomy filtering resets it
+ originalZoom = this.settings.mapSettings.zoom;
+
+ // Add Handlebars helper for handling URL output
+ Handlebars.registerHelper('niceURL', function(url) {
+ if (url) {
+ return url.replace('https://', '').replace('http://', '');
+ }
+ });
+
+ // Handle distance changes on select
+ if (this.settings.maxDistance === true) {
+ this.distanceFiltering();
+ }
+
+ // Do taxonomy filtering if set
+ if (this.settings.taxonomyFilters !== null) {
+ this.taxonomyFiltering();
+ }
+
+ // Do sorting and ordering if set.
+ this.sorting();
+ this.order();
+
+ // Add modal window divs if set
+ if (this.settings.modal === true) {
+ // Clone the filters if there are any, so they can be used in the modal
+ if (this.settings.taxonomyFilters !== null) {
+ // Clone the filters
+ $('.' + this.settings.taxonomyFiltersContainer).clone(true, true).prependTo($this);
+ }
+
+ $this.wrap('');
+ $('.' + this.settings.modalWindow).prepend('
');
+ $('.' + this.settings.overlay).hide();
+ }
+
+ // Set up Google Places autocomplete if it's set to true
+ if (this.settings.autoComplete === true) {
+ var searchInput = document.getElementById(this.settings.addressID);
+ var autoPlaces = new google.maps.places.Autocomplete(searchInput, this.settings.autoCompleteOptions);
+
+ // Add listener when autoComplete selection changes.
+ if (this.settings.autoComplete === true && this.settings.autoCompleteDisableListener !== true) {
+ autoPlaces.addListener('place_changed', function(e) {
+ _this.processForm(e);
+ });
+ }
+ }
+
+ // Load the templates and continue from there
+ this._loadTemplates();
+ },
+
+ /**
+ * Trigger async map loading
+ */
+ triggerMapLoad: function(optionsQuery) {
+ this.writeDebug('triggerMapLoad');
+ var _this = this;
+
+ this.loadMapsAPI(this.settings.apiKey, optionsQuery)
+ .then(function (map) {
+ _this.map = map;
+ _this.init();
+ });
+ },
+
+ /**
+ * Inject Google Maps script
+ *
+ * @param {Object} options Options query object to pass as query string parameters to Google Maps.
+ */
+ injectGoogleMapsScript: function (options) {
+ this.writeDebug('injectGoogleMapsScript');
+ options = (typeof options !== 'undefined') ? options : {};
+
+ if (googleMapsScriptIsInjected) {
+ throw new Error('Google Maps API is already loaded.');
+ }
+
+ var optionsQuery = Object.keys(options)
+ .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(options[k]))
+ .join('&');
+ var apiURL = 'https://maps.googleapis.com/maps/api/js?' + optionsQuery;
+ var mapScript = document.createElement('script');
+ mapScript.setAttribute('src', apiURL);
+ mapScript.setAttribute('async', '');
+ mapScript.setAttribute('defer', '');
+
+ // Append the script to the document head.
+ document.head.appendChild(mapScript);
+ googleMapsScriptIsInjected = true;
+ },
+
+ /**
+ * Load Google Maps API
+ *
+ * @param {string} apiKey Google Maps JavaScript API key.
+ * @param {Object} options Options query object to pass as query string parameters to Google Maps.
+ *
+ * @returns {Promise
}
+ */
+ loadMapsAPI: function (apiKey, options) {
+ this.writeDebug('loadMapsAPI');
+ options = (typeof options !== 'undefined') ? options : {};
+ var _this = this;
+
+ if (!googleMapsAPIPromise) {
+ googleMapsAPIPromise = new Promise(function (resolve, reject) {
+ try {
+ window.onGoogleMapsAPILoaded = resolve;
+
+ _this.injectGoogleMapsScript({
+ key: apiKey,
+ loading: 'async',
+ callback: 'onGoogleMapsAPILoaded',
+ ...options,
+ });
+ } catch (error) {
+ reject(error);
+ }
+ }).then(function () { window.google.maps });
+ }
+
+ return googleMapsAPIPromise;
+ },
+
+ /**
+ * Destroy
+ * Note: The Google map is not destroyed here because Google recommends using a single instance and reusing it
+ * (it's not really supported)
+ */
+ destroy: function () {
+ this.writeDebug('destroy');
+ // Reset
+ this.reset();
+ var $mapDiv = $('#' + this.settings.mapID);
+
+ // Remove marker event listeners
+ if (markers.length) {
+ for(var i = 0; i <= markers.length; i++) {
+ google.maps.event.removeListener(markers[i]);
+ }
+ }
+
+ // Remove markup
+ $('.' + this.settings.locationList + ' ul').empty();
+ if ($mapDiv.hasClass('bh-sl-map-open')) {
+ $mapDiv.empty().removeClass('bh-sl-map-open');
+ }
+
+ // Remove modal markup
+ if (this.settings.modal === true) {
+ $('. ' + this.settings.overlay).remove();
+ }
+
+ // Remove map style from container
+ $mapDiv.attr('style', '');
+
+ // Hide map container
+ $this.hide();
+ // Remove data
+ $.removeData($this.get(0));
+ // Remove namespaced events
+ $(document).off(pluginName);
+ // Unbind plugin
+ $this.unbind();
+ },
+
+ /**
+ * Reset function
+ * This method clears out all the variables and removes events. It does not reload the map.
+ */
+ reset: function () {
+ this.writeDebug('reset');
+ locationset = [];
+ featuredset = [];
+ normalset = [];
+ markers = [];
+ firstRun = false;
+ $(document).off('click.'+pluginName, '.' + this.settings.locationList + ' li');
+
+ if ( $('.' + this.settings.locationList + ' .bh-sl-close-directions-container').length ) {
+ $('.bh-sl-close-directions-container').remove();
+ }
+
+ if (this.settings.inlineDirections === true) {
+ // Remove directions panel if it's there
+ var $adp = $('.' + this.settings.locationList + ' .adp');
+ if ( $adp.length > 0 ) {
+ $adp.remove();
+ $('.' + this.settings.locationList + ' ul').fadeIn();
+ }
+ $(document).off('click', '.' + this.settings.locationList + ' li .loc-directions a');
+ }
+
+ if (this.settings.pagination === true) {
+ $(document).off('click.'+pluginName, '.bh-sl-pagination li a');
+ }
+ },
+
+ /**
+ * Reset the form filters
+ */
+ formFiltersReset: function () {
+ this.writeDebug('formFiltersReset');
+ if (this.settings.taxonomyFilters === null) {
+ return;
+ }
+
+ var $inputs = $('.' + this.settings.taxonomyFiltersContainer + ' input'),
+ $selects = $('.' + this.settings.taxonomyFiltersContainer + ' select');
+
+ if ( typeof($inputs) !== 'object') {
+ return;
+ }
+
+ // Loop over the input fields
+ $inputs.each(function() {
+ if ($(this).is('input[type="checkbox"]') || $(this).is('input[type="radio"]')) {
+ $(this).prop('checked',false);
+ }
+ });
+
+ // Loop over select fields
+ $selects.each(function() {
+ $(this).prop('selectedIndex',0);
+ });
+ },
+
+ /**
+ * Reload everything
+ * This method does a reset of everything and reloads the map as it would first appear.
+ */
+ mapReload: function() {
+ this.writeDebug('mapReload');
+ this.reset();
+ reload = true;
+
+ if (this.settings.taxonomyFilters !== null) {
+ this.formFiltersReset();
+ this.resetDisabledFilterVals();
+ this.taxonomyFiltersInit();
+ }
+
+ if ((olat) && (olng)) {
+ this.settings.mapSettings.zoom = originalZoom;
+ this.processForm();
+ }
+ else {
+ this.mapping(mappingObj);
+ }
+ },
+
+ /**
+ * Notifications
+ * Some errors use alert by default. This is overridable with the callbackNotify option
+ *
+ * @param notifyText {string} the notification message
+ */
+ notify: function (notifyText) {
+ this.writeDebug('notify',notifyText);
+ if (this.settings.callbackNotify) {
+ this.settings.callbackNotify.call(this, notifyText);
+ }
+ else {
+ alert(notifyText);
+ }
+ },
+
+ /**
+ * Distance calculations
+ */
+ geoCodeCalcToRadian: function (v) {
+ this.writeDebug('geoCodeCalcToRadian',v);
+ return v * (Math.PI / 180);
+ },
+ geoCodeCalcDiffRadian: function (v1, v2) {
+ this.writeDebug('geoCodeCalcDiffRadian',arguments);
+ return this.geoCodeCalcToRadian(v2) - this.geoCodeCalcToRadian(v1);
+ },
+ geoCodeCalcCalcDistance: function (lat1, lng1, lat2, lng2, radius) {
+ this.writeDebug('geoCodeCalcCalcDistance',arguments);
+ 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) ))));
+ },
+
+ /**
+ * Range helper function for coordinate validation
+ *
+ * @param min {number} minimum number allowed
+ * @param num {number} number to check
+ * @param max {number} maximum number allowed
+ *
+ * @returns {boolean}
+ */
+ inRange: function(min, num, max){
+ this.writeDebug('inRange',arguments);
+ num = Math.abs(num);
+ return isFinite(num) && (num >= min) && (num <= max);
+ },
+
+ /**
+ * Coordinate validation
+ *
+ * @param lat {number} latitude
+ * @param lng {number} longitude
+ *
+ * @returns {boolean}
+ */
+ coordinatesInRange: function (lat, lng) {
+ this.writeDebug('coordinatesInRange',arguments);
+ return this.inRange(-90, lat, 90) && this.inRange(-180, lng, 180);
+ },
+
+ /**
+ * Check for query string
+ *
+ * @param param {string} query string parameter to test
+ *
+ * @returns {string} query string value
+ */
+ getQueryString: function(param) {
+ this.writeDebug('getQueryString',param);
+ if(param) {
+ param = param.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
+ var regex = new RegExp('[\\?&]' + param + '=([^]*)'),
+ results = regex.exec(location.search);
+ return (results === null) ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
+ }
+ },
+
+ /**
+ * Get google.maps.Map instance
+ *
+ * @returns {Object} google.maps.Map instance
+ */
+ getMap: function() {
+ return this.map;
+ },
+
+ /**
+ * Load templates via Handlebars templates in /templates or inline via IDs - private
+ */
+ _loadTemplates: function () {
+ this.writeDebug('_loadTemplates');
+ var source;
+ var _this = this;
+ var templateError = '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.
';
+ // Get the KML templates
+ if (this.settings.dataType === 'kml' && this.settings.listTemplateID === null && this.settings.infowindowTemplateID === null) {
+
+ // Try loading the external template files
+ $.when(
+ // KML infowindows
+ $.get(this.settings.KMLinfowindowTemplatePath, function (template) {
+ source = template;
+ infowindowTemplate = Handlebars.compile(source);
+ }),
+
+ // KML locations list
+ $.get(this.settings.KMLlistTemplatePath, function (template) {
+ source = template;
+ listTemplate = Handlebars.compile(source);
+ })
+ ).then(function () {
+ // Continue to the main script if templates are loaded successfully
+ _this.locator();
+
+ }, function () {
+ // KML templates not loaded
+ $('.' + _this.settings.formContainer).append(templateError);
+ throw new Error('Could not load storeLocator plugin templates');
+ });
+ }
+ // Handle script tag template method
+ else if (this.settings.listTemplateID !== null && this.settings.infowindowTemplateID !== null) {
+ // Infowindows
+ infowindowTemplate = Handlebars.compile($('#' + this.settings.infowindowTemplateID).html());
+
+ // Locations list
+ listTemplate = Handlebars.compile($('#' + this.settings.listTemplateID).html());
+
+ // Continue to the main script
+ _this.locator();
+ }
+ // Get the JSON/XML templates
+ else {
+ // Try loading the external template files
+ $.when(
+ // Infowindows
+ $.get(this.settings.infowindowTemplatePath, function (template) {
+ source = template;
+ infowindowTemplate = Handlebars.compile(source);
+ }),
+
+ // Locations list
+ $.get(this.settings.listTemplatePath, function (template) {
+ source = template;
+ listTemplate = Handlebars.compile(source);
+ })
+ ).then(function () {
+ // Continue to the main script if templates are loaded successfully
+ _this.locator();
+
+ }, function () {
+ // JSON/XML templates not loaded
+ $('.' + _this.settings.formContainer).append(templateError);
+ throw new Error('Could not load storeLocator plugin templates');
+ });
+ }
+ },
+
+ /**
+ * Primary locator function runs after the templates are loaded
+ */
+ locator: function () {
+ this.writeDebug('locator');
+ if (this.settings.slideMap === true) {
+ // Let's hide the map container to begin
+ $this.hide();
+ }
+
+ this._start();
+ this._formEventHandler();
+ },
+
+ /**
+ * Form event handler setup - private
+ */
+ _formEventHandler: function () {
+ this.writeDebug('_formEventHandler');
+ var _this = this;
+ // ASP.net or regular submission?
+ if (this.settings.noForm === true) {
+ $(document).on('click.'+pluginName, '.' + this.settings.formContainer + ' button', function (e) {
+ _this.processForm(e);
+ });
+ $(document).on('keydown.'+pluginName, function (e) {
+ if (e.keyCode === 13 && $('#' + _this.settings.addressID).is(':focus')) {
+ _this.processForm(e);
+ }
+ });
+ }
+ else {
+ $(document).on('submit.'+pluginName, '#' + this.settings.formID, function (e) {
+ _this.processForm(e);
+ });
+ }
+
+ // Reset button trigger.
+ if ($('.bh-sl-reset').length && $('#' + this.settings.mapID).length) {
+ $(document).on('click.' + pluginName, '.bh-sl-reset', function () {
+ _this.mapReload();
+ });
+ }
+
+ // Track changes to the address search field.
+ $('#' + this.settings.addressID).on('change.'+pluginName, function () {
+ originalFilterVals = undefined;
+ disabledFilterVals = {};
+
+ // Unset origin tracking if input field is removed.
+ if (
+ $.trim($('#' + _this.settings.addressID).val()) === '' &&
+ (typeof searchInput === 'undefined' || searchInput === '')
+ ) {
+
+ // Reset the origin, mapping object, and disabled filter values.
+ if (_this.settings.taxonomyFilters !== null && _this.settings.exclusiveFiltering === false) {
+ olat = undefined;
+ olng = undefined;
+ originalOrigin = undefined;
+ mappingObj = {};
+ _this.resetDisabledFilterVals();
+ _this.taxonomyFiltersInit();
+ _this.mapping(null);
+ }
+ }
+ });
+ },
+
+ /**
+ * AJAX data request - private
+ *
+ * @param lat {number} latitude
+ * @param lng {number} longitude
+ * @param address {string} street address
+ * @param geocodeData {object} full Google geocode results object
+ * @param map (object} Google Maps object.
+ *
+ * @returns {Object} deferred object
+ */
+ _getData: function (lat, lng, address, geocodeData, map) {
+ this.writeDebug('_getData',arguments);
+ var _this = this,
+ northEast = '',
+ southWest = '',
+ formattedAddress = '';
+
+ // Define extra geocode result info
+ if (typeof geocodeData !== 'undefined' && typeof geocodeData.geometry.bounds !== 'undefined') {
+ formattedAddress = geocodeData.formatted_address;
+ northEast = JSON.stringify( geocodeData.geometry.bounds.getNorthEast() );
+ southWest = JSON.stringify( geocodeData.geometry.bounds.getSouthWest() );
+ }
+
+ // Before send callback
+ if (this.settings.callbackBeforeSend) {
+ this.settings.callbackBeforeSend.call(this, lat, lng, address, formattedAddress, northEast, southWest, map);
+ }
+
+ // Raw data
+ if(_this.settings.dataRaw !== null) {
+ // XML
+ if( dataTypeRead === 'xml' ) {
+ return $.parseXML(_this.settings.dataRaw);
+ }
+
+ // JSON
+ else if (dataTypeRead === 'json') {
+ if (Array.isArray && Array.isArray(_this.settings.dataRaw)) {
+ return _this.settings.dataRaw;
+ }
+ else if (typeof _this.settings.dataRaw === 'string') {
+ return JSON.parse(_this.settings.dataRaw);
+ }
+ else {
+ return [];
+ }
+
+ }
+ }
+ // Remote data
+ else {
+ var d = $.Deferred();
+
+ // Loading
+ if (this.settings.loading === true) {
+ $('.' + this.settings.formContainer).append('
');
+ }
+
+ // Data to pass with the AJAX request
+ var ajaxData = {
+ 'origLat' : lat,
+ 'origLng' : lng,
+ 'origAddress': address,
+ 'formattedAddress': formattedAddress,
+ 'boundsNorthEast' : northEast,
+ 'boundsSouthWest' : southWest
+ };
+
+ // Set up extra object for custom extra data to be passed with the AJAX request
+ if (this.settings.ajaxData !== null && typeof this.settings.ajaxData === 'object') {
+ $.extend(ajaxData, this.settings.ajaxData);
+ }
+
+ // AJAX request
+ $.ajax({
+ type : 'GET',
+ url : this.settings.dataLocation + (this.settings.dataType === 'jsonp' ? (this.settings.dataLocation.match(/\?/) ? '&' : '?') + 'callback=?' : ''),
+ // Passing the lat, lng, address, formatted address and bounds with the AJAX request so they can optionally be used by back-end languages
+ data : ajaxData,
+ dataType : dataTypeRead,
+ jsonpCallback: (this.settings.dataType === 'jsonp' ? this.settings.callbackJsonp : null)
+ }).done(function(p) {
+ d.resolve(p);
+
+ // Loading remove
+ if (_this.settings.loading === true) {
+ $('.' + _this.settings.formContainer + ' .' + _this.settings.loadingContainer).remove();
+ }
+ }).fail(d.reject);
+ return d.promise();
+ }
+ },
+
+ /**
+ * Checks for default location, full map, and HTML5 geolocation settings - private
+ */
+ _start: function () {
+ this.writeDebug('_start');
+ var _this = this,
+ doAutoGeo = this.settings.autoGeocode,
+ latlng;
+
+ // Full map blank start
+ if (_this.settings.fullMapStartBlank !== false) {
+ var $mapDiv = $('#' + _this.settings.mapID);
+ $mapDiv.addClass('bh-sl-map-open');
+ var myOptions = _this.settings.mapSettings;
+ myOptions.zoom = _this.settings.fullMapStartBlank;
+
+ latlng = new google.maps.LatLng(this.settings.defaultLat, this.settings.defaultLng);
+ myOptions.center = latlng;
+
+ // Create the map
+ _this.map = new google.maps.Map(document.getElementById(_this.settings.mapID), myOptions);
+
+ // Re-center the map when the browser is re-sized
+ window.addEventListener('resize', function() {
+ var center = _this.map.getCenter();
+ google.maps.event.trigger(_this.map, 'resize');
+ _this.map.setCenter(center);
+ });
+
+ // Only do this once
+ _this.settings.fullMapStartBlank = false;
+ myOptions.zoom = originalZoom;
+ } else {
+ // If a default location is set
+ if (this.settings.defaultLoc === true) {
+ this.defaultLocation();
+ }
+
+ // If there is already have a value in the address bar
+ if ($.trim($('#' + this.settings.addressID).val()) !== ''){
+ _this.writeDebug('Using Address Field');
+ _this.processForm(null);
+ doAutoGeo = false; // No need for additional processing
+ }
+ // If show full map option is true
+ else if (this.settings.fullMapStart === true && this.settings.defaultLoc === false) {
+ 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))) {
+ _this.writeDebug('Using Query String');
+ this.processForm(null);
+ doAutoGeo = false; // No need for additional processing
+ }
+ else {
+ this.mapping(null);
+ }
+ }
+ }
+
+ // HTML5 auto geolocation API option
+ if (this.settings.autoGeocode === true && doAutoGeo === true) {
+ _this.writeDebug('Auto Geo');
+
+ _this.htmlGeocode();
+ }
+
+ // HTML5 geolocation API button option
+ if (this.settings.autoGeocode !== null) {
+ _this.writeDebug('Button Geo');
+
+ $(document).on('click.'+pluginName, '#' + this.settings.geocodeID, function () {
+ _this.htmlGeocode();
+ });
+ }
+ },
+
+ /**
+ * Geocode function used for auto geocode setting and geocodeID button
+ */
+ htmlGeocode: function() {
+ this.writeDebug('htmlGeocode',arguments);
+ var _this = this;
+
+ if (_this.settings.sessionStorage === true && window.sessionStorage && window.sessionStorage.getItem('myGeo')){
+ _this.writeDebug('Using Session Saved Values for GEO');
+ _this.autoGeocodeQuery(JSON.parse(window.sessionStorage.getItem('myGeo')));
+ return false;
+ }
+ else if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(function(position){
+ _this.writeDebug('Current Position Result');
+ // To not break autoGeocodeQuery then we create the obj to match the geolocation format
+ var pos = {
+ coords: {
+ latitude : position.coords.latitude,
+ longitude: position.coords.longitude,
+ accuracy : position.coords.accuracy
+ }
+ };
+
+ // Have to do this to get around scope issues
+ if (_this.settings.sessionStorage === true && window.sessionStorage) {
+ window.sessionStorage.setItem('myGeo',JSON.stringify(pos));
+ }
+
+ // Callback
+ if (_this.settings.callbackAutoGeoSuccess) {
+ _this.settings.callbackAutoGeoSuccess.call(this, pos);
+ }
+
+ _this.autoGeocodeQuery(pos);
+ }, function(error){
+ _this._autoGeocodeError(error);
+ });
+ }
+ },
+
+ /**
+ * Geocode function used to geocode the origin (entered location)
+ */
+ googleGeocode: function (thisObj) {
+ thisObj.writeDebug('googleGeocode',arguments);
+ var geocoder = new google.maps.Geocoder();
+ this.geocode = function (request, callbackFunction) {
+ geocoder.geocode(request, function (results, status) {
+ if (status === google.maps.GeocoderStatus.OK) {
+ var result = {};
+ result.latitude = results[0].geometry.location.lat();
+ result.longitude = results[0].geometry.location.lng();
+ result.geocodeResult = results[0];
+ callbackFunction(result);
+ } else {
+ callbackFunction(null);
+ throw new Error('Geocode was not successful for the following reason: ' + status);
+ }
+ });
+ };
+ },
+
+ /**
+ * Reverse geocode to get address for automatic options needed for directions link
+ */
+ reverseGoogleGeocode: function (thisObj) {
+ thisObj.writeDebug('reverseGoogleGeocode',arguments);
+ var geocoder = new google.maps.Geocoder();
+ this.geocode = function (request, callbackFunction) {
+ geocoder.geocode(request, function (results, status) {
+ if (status === google.maps.GeocoderStatus.OK) {
+ if (results[0]) {
+ var result = {};
+ result.address = results[0].formatted_address;
+ result.fullResult = results[0];
+ callbackFunction(result);
+ }
+ } else {
+ callbackFunction(null);
+ throw new Error('Reverse geocode was not successful for the following reason: ' + status);
+ }
+ });
+ };
+ },
+
+ /**
+ * Rounding function used for distances
+ *
+ * @param num {number} the full number
+ * @param dec {number} the number of digits to show after the decimal
+ *
+ * @returns {number}
+ */
+ roundNumber: function (num, dec) {
+ this.writeDebug('roundNumber',arguments);
+ return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
+ },
+
+ /**
+ * Checks to see if the object is empty. Using this instead of $.isEmptyObject for legacy browser support
+ *
+ * @param obj {Object} the object to check
+ *
+ * @returns {boolean}
+ */
+ isEmptyObject: function (obj) {
+ this.writeDebug('isEmptyObject',arguments);
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Checks to see if all the property values in the object are empty
+ *
+ * @param obj {Object} the object to check
+ *
+ * @returns {boolean}
+ */
+ hasEmptyObjectVals: function (obj) {
+ this.writeDebug('hasEmptyObjectVals',arguments);
+ var objTest = true;
+
+ for(var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ if (obj[key] !== '' && obj[key].length !== 0) {
+ objTest = false;
+ }
+ }
+ }
+
+ return objTest;
+ },
+
+ /**
+ * Checks to see if only a single taxonomy group has a selected value
+ *
+ * @param obj {Object} the object to check
+ * @param key {string} Key value of current filter group
+ *
+ * @returns {boolean}
+ */
+ hasSingleGroupFilterVal: function(obj, key) {
+ this.writeDebug('hasSingleGroupFilterVal',arguments);
+
+ // Copy the object so the original doesn't change.
+ var objCopy = Object.assign({}, obj);
+
+ return !this.hasEmptyObjectVals(objCopy[key]);
+ },
+
+ /**
+ * Modal window close function
+ */
+ modalClose: function () {
+ this.writeDebug('modalClose');
+ // Callback
+ if (this.settings.callbackModalClose) {
+ this.settings.callbackModalClose.call(this);
+ }
+
+ // Reset the filters
+ filters = {};
+
+ // Undo category selections
+ $('.' + this.settings.overlay + ' select').prop('selectedIndex', 0);
+ $('.' + this.settings.overlay + ' input').prop('checked', false);
+
+ // Hide the modal
+ $('.' + this.settings.overlay).hide();
+ },
+
+ /**
+ * Create the location variables - private
+ *
+ * @param loopcount {number} current marker id
+ */
+ _createLocationVariables: function (loopcount) {
+ this.writeDebug('_createLocationVariables',arguments);
+ var value;
+ locationData = {};
+
+ for (var key in locationset[loopcount]) {
+ if (locationset[loopcount].hasOwnProperty(key)) {
+ value = locationset[loopcount][key];
+
+ if (key === 'distance' || key === 'altdistance') {
+ value = this.roundNumber(value, 2);
+ }
+
+ locationData[key] = value;
+ }
+ }
+ },
+
+ /**
+ * Location alphabetical sorting function
+ *
+ * @param locationsarray {array} locationset array
+ */
+ sortAlpha: function(locationsarray) {
+ this.writeDebug('sortAlpha',arguments);
+ var property = (this.settings.sortBy.hasOwnProperty('prop') && typeof this.settings.sortBy.prop !== 'undefined') ? this.settings.sortBy.prop : 'name';
+
+ if (this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
+ locationsarray.sort(function (a, b) {
+ return b[property].toLowerCase().localeCompare(a[property].toLowerCase());
+ });
+ } else {
+ locationsarray.sort(function (a, b) {
+ return a[property].toLowerCase().localeCompare(b[property].toLowerCase());
+ });
+ }
+ },
+
+ /**
+ * Location date sorting function
+ *
+ * @param locationsarray {array} locationset array
+ */
+ sortDate: function(locationsarray) {
+ this.writeDebug('sortDate',arguments);
+ var property = (this.settings.sortBy.hasOwnProperty('prop') && typeof this.settings.sortBy.prop !== 'undefined') ? this.settings.sortBy.prop : 'date';
+
+ if (this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
+ locationsarray.sort(function (a, b) {
+ return new Date(b[property]).getTime() - new Date(a[property]).getTime();
+ });
+ } else {
+ locationsarray.sort(function (a, b) {
+ return new Date(a[property]).getTime() - new Date(b[property]).getTime();
+ });
+ }
+ },
+
+ /**
+ * Location distance sorting function
+ *
+ * @param locationsarray {array} locationset array
+ * @param distanceOverride {boolean} Force sort by distance
+ */
+ sortNumerically: function (locationsarray, distanceOverride) {
+ this.writeDebug('sortNumerically',arguments);
+ var property = (
+ this.settings.sortBy !== null &&
+ this.settings.sortBy.hasOwnProperty('prop') &&
+ typeof this.settings.sortBy.prop !== 'undefined'
+ ) ? this.settings.sortBy.prop : 'distance';
+
+ if (typeof distanceOverride !== 'undefined' && distanceOverride === true) {
+ property = 'distance';
+ }
+
+ if (this.settings.sortBy !== null && this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
+ locationsarray.sort(function (a, b) {
+ return ((b[property] < a[property]) ? -1 : ((b[property] > a[property]) ? 1 : 0));
+ });
+ } else {
+ locationsarray.sort(function (a, b) {
+ return ((a[property] < b[property]) ? -1 : ((a[property] > b[property]) ? 1 : 0));
+ });
+ }
+ },
+
+ /**
+ * Alternative sorting setup
+ *
+ * @param locationsarray {array} locationset array
+ */
+ sortCustom: function (locationsarray) {
+ this.writeDebug('sortCustom',arguments);
+
+ // Alphabetically, date, or numeric
+ if (this.settings.sortBy.hasOwnProperty('method') && this.settings.sortBy.method.toString() === 'alpha') {
+ this.sortAlpha(locationsarray);
+ } else if (this.settings.sortBy.hasOwnProperty('method') && this.settings.sortBy.method.toString() === 'date') {
+ this.sortDate(locationsarray);
+ } else {
+ this.sortNumerically(locationsarray);
+ }
+ },
+
+ /**
+ * Run the matching between regular expression filters and string value
+ *
+ * @param filter {array} One or multiple filters to apply
+ * @param val {string} Value to compare
+ * @param inclusive {boolean} Inclusive (default) or exclusive
+ *
+ * @returns {boolean}
+ */
+ filterMatching: function(filter, val, inclusive) {
+ this.writeDebug('inclusiveFilter',arguments);
+ inclusive = (typeof inclusive !== 'undefined') ? inclusive : true;
+ var applyFilters;
+
+ // Undefined check.
+ if (typeof val === 'undefined') {
+ return false;
+ }
+
+ // Modify the join depending on inclusive (AND) vs exclusive (OR).
+ if ( true === inclusive ) {
+ applyFilters = filter.join('');
+ } else {
+ applyFilters = filter.join('|');
+ }
+
+ if ((new RegExp(applyFilters, 'i').test(val.replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '')))) {
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Filter the data with Regex
+ *
+ * @param data {array} data array to check for filter values
+ * @param filters {Object} taxonomy filters object
+ *
+ * @returns {boolean}
+ */
+ filterData: function (data, filters) {
+ this.writeDebug('filterData', arguments);
+ var filterTest = true;
+
+ for (var k in filters) {
+ if (filters.hasOwnProperty(k)) {
+ var testResults = [];
+
+ for (var l = 0; l < filters[k].length; l++) {
+
+ // Exclusive filtering
+ if (this.settings.exclusiveFiltering === true || (this.settings.exclusiveTax !== null && Array.isArray(this.settings.exclusiveTax) && this.settings.exclusiveTax.indexOf(k) !== -1)) {
+ testResults[l] = this.filterMatching(filters[k], data[k], false);
+ }
+ // Inclusive filtering
+ else {
+ testResults[l] = this.filterMatching(filters[k], data[k]);
+ }
+ }
+
+ // First handle name search, then standard filtering.
+ if (typeof nameAttrs !== 'undefined' && nameAttrs.indexOf(k) !== -1 && testResults.indexOf(true) !== -1) {
+ return true;
+ } else {
+ if (testResults.indexOf(true) === -1) {
+ filterTest = false;
+ }
+ }
+ }
+ }
+
+ return filterTest;
+ },
+
+ /**
+ * Build pagination numbers and next/prev links - private
+ *
+ * @param currentPage {number}
+ * @param totalPages {number}
+ *
+ * @returns {string}
+ */
+ _paginationOutput: function(currentPage, totalPages) {
+ this.writeDebug('_paginationOutput',arguments);
+
+ currentPage = parseInt(currentPage);
+ totalPages = Math.ceil(totalPages);
+ var pagesStart, pagesEnd;
+ var output = '';
+ var nextPage = currentPage + 1;
+ var prevPage = currentPage - 1;
+ var pagesCutoff = 5;
+ var pagesCeiling = Math.ceil(pagesCutoff / 2);
+ var pagesFloor = Math.floor(pagesCutoff / 2);
+
+ // Determine page numbers to display.
+ if (totalPages < pagesCutoff) {
+ pagesStart = 0;
+ pagesEnd = totalPages;
+ } else if (currentPage >= 0 && currentPage <= pagesCeiling) {
+ pagesStart = 0;
+ pagesEnd = pagesCutoff;
+ } else if ((currentPage + pagesFloor) >= totalPages) {
+ pagesStart = (totalPages - pagesCutoff);
+ pagesEnd = totalPages;
+ } else {
+ pagesStart = (currentPage - pagesCeiling);
+ pagesEnd = (currentPage + pagesFloor);
+ }
+
+ // Previous page
+ if ( currentPage > 0 ) {
+ output += '';
+ output += '' + this.settings.prevPage + ' ';
+ output += ' ';
+ }
+
+ // Additional pages indicator.
+ if ((currentPage + 1) >= pagesCutoff && totalPages > pagesCutoff) {
+ // First page link.
+ output += '';
+ output += '' + 1 + ' ';
+ output += ' ';
+
+ output += '';
+ output += '… ';
+ output += ' ';
+ }
+
+ // Add the numbers
+ for (var p = pagesStart; p < pagesEnd; p++) {
+ var n = p + 1;
+
+ if (p === currentPage) {
+ output += '';
+ output += '' + n + ' ';
+ output += ' ';
+ } else {
+ output += '';
+ output += '' + n + ' ';
+ output += ' ';
+ }
+ }
+
+ // Additional pages indicator.
+ if ((currentPage + pagesCeiling) <= totalPages && totalPages > pagesCutoff) {
+ output += '';
+ output += '… ';
+ output += ' ';
+
+ // Last page link.
+ output += '';
+ output += '' + totalPages + ' ';
+ output += ' ';
+ }
+
+ // Next page
+ if ( nextPage < totalPages ) {
+ output += '';
+ output += '' + this.settings.nextPage + ' ';
+ output += ' ';
+ }
+
+ return output;
+ },
+
+ /**
+ * Reset pagination after the input has changed
+ */
+ paginationReset: function() {
+ this.writeDebug('paginationReset',arguments);
+
+ var currentUrl = window.location.href;
+ var url = new URL(currentUrl);
+
+ // Remove the old page in the URL.
+ url.searchParams.delete('bhsl-page');
+
+ // Update the query string param to match the new value.
+ if (history.pushState) {
+ window.history.pushState({path: url.href}, '', url.href);
+ }
+ },
+
+ /**
+ * Determine the total number of pages for pagination
+ */
+ totalPages: function() {
+ this.writeDebug('totalPages',arguments);
+
+ // Location limit.
+ if (
+ typeof originalOrigin !== 'undefined' &&
+ this.settings.storeLimit > 0 &&
+ locationset.length > this.settings.storeLimit
+ ) {
+ return this.settings.storeLimit / this.settings.locationsPerPage;
+ }
+
+ // WP API response after search.
+ if (locationsTotal > 0) {
+ return locationsTotal / this.settings.locationsPerPage;
+ }
+
+ // Unlimited or last page.
+ if (
+ this.settings.storeLimit === -1 ||
+ locationset.length < this.settings.storeLimit
+ ) {
+ return locationset.length / this.settings.locationsPerPage;
+ } else {
+ return this.settings.storeLimit / this.settings.locationsPerPage;
+ }
+ },
+
+ /**
+ * Set up the pagination pages
+ *
+ * @param currentPage {number} optional current page
+ */
+ paginationSetup: function (currentPage) {
+ this.writeDebug('paginationSetup',arguments);
+ var pagesOutput = '';
+ var $paginationList = $('.bh-sl-pagination-container .bh-sl-pagination');
+
+ // Total pages
+ var totalPages = this.totalPages();
+
+ // Current page check
+ if (typeof currentPage === 'undefined') {
+ currentPage = 0;
+ }
+
+ // Initial pagination setup
+ if ($paginationList.length === 0) {
+
+ pagesOutput = this._paginationOutput(currentPage, totalPages);
+ }
+ // Update pagination on page change
+ else {
+ // Remove the old pagination
+ $paginationList.empty();
+
+ // Add the numbers
+ pagesOutput = this._paginationOutput(currentPage, totalPages);
+ }
+
+ $paginationList.append(pagesOutput);
+ },
+
+ /**
+ * Determine if the legacy or Advanced markers should be used
+ *
+ * @returns {boolean}
+ */
+ useLegacyMarkers: function() {
+ this.writeDebug('useLegacyMarkers',arguments);
+
+ return !this.settings.mapSettings.hasOwnProperty('mapId') ||
+ this.settings.mapSettings.mapId === '';
+ },
+
+ /**
+ * Legacy marker image setup
+ *
+ * Original functionality supporting the now deprecated google.maps.Marker
+ * This will eventually be removed in favor of markerImage below.
+ *
+ * @param markerUrl {string} path to marker image
+ * @param markerWidth {number} width of marker
+ * @param markerHeight {number} height of marker
+ *
+ * @returns {Object} Google Maps icon object
+ */
+ legacyMarkerImage: function (markerUrl, markerWidth, markerHeight) {
+ this.writeDebug('legacyMarkerImage',arguments);
+ var markerImg;
+
+ // User defined marker dimensions
+ if (typeof markerWidth !== 'undefined' && typeof markerHeight !== 'undefined') {
+ markerImg = {
+ url: markerUrl,
+ size: new google.maps.Size(markerWidth, markerHeight),
+ scaledSize: new google.maps.Size(markerWidth, markerHeight)
+ };
+ }
+ // Default marker dimensions: 32px x 32px
+ else {
+ markerImg = {
+ url: markerUrl,
+ size: new google.maps.Size(32, 32),
+ scaledSize: new google.maps.Size(32, 32)
+ };
+ }
+
+ return markerImg;
+ },
+
+ /**
+ * Marker image setup
+ *
+ * @param markerUrl {string} path to marker image
+ * @param markerWidth {number} width of marker
+ * @param markerHeight {number} height of marker
+ *
+ * @returns {HTMLImageElement} Image element
+ */
+ markerImage: function (markerUrl, markerWidth, markerHeight) {
+ this.writeDebug('markerImage',arguments);
+
+ // Check if legacy marker image should be used
+ if (this.useLegacyMarkers()) {
+ return this.legacyMarkerImage(markerUrl, markerWidth, markerHeight);
+ }
+
+ var markerImg = document.createElement('img');
+ markerImg.src = markerUrl;
+
+ // User defined marker dimensions
+ if (typeof markerWidth !== 'undefined' && typeof markerHeight !== 'undefined') {
+ markerImg.height = markerHeight;
+ markerImg.width = markerWidth;
+ }
+ // Default marker dimensions: 32px x 32px
+ else {
+ markerImg.height = 32;
+ markerImg.width = 32;
+ }
+
+ return markerImg;
+ },
+
+ /**
+ * Map marker setup
+ *
+ * @param point {Object} LatLng of current location
+ * @param name {string} location name
+ * @param address {string} location address
+ * @param letter {string} optional letter used for front-end identification and correlation between list and
+ * points
+ * @param map {Object} the Google Map
+ * @param category {string} location category/categories
+ *
+ * @returns {Object} Google Maps marker
+ */
+ createMarker: function (point, name, address, letter, map, category) {
+ this.writeDebug('createMarker',arguments);
+ var marker, markerImg, letterMarkerImg;
+ var categories = [];
+
+ // Custom multi-marker image override (different markers for different categories
+ if (this.settings.catMarkers !== null) {
+ if (typeof category !== 'undefined') {
+ // Multiple categories
+ if (category.indexOf(',') !== -1) {
+ // Break the category variable into an array if there are multiple categories for the location
+ categories = category.split(',');
+ // With multiple categories the color will be determined by the last matched category in the data
+ for (var i = 0; i < categories.length; i++) {
+ if (categories[i] in this.settings.catMarkers) {
+ markerImg = this.markerImage(
+ this.settings.catMarkers[categories[i]][0],
+ Number(this.settings.catMarkers[categories[i]][1]),
+ Number(this.settings.catMarkers[categories[i]][2])
+ );
+ }
+ }
+ }
+ // Single category
+ else {
+ if (category in this.settings.catMarkers) {
+ markerImg = this.markerImage(
+ this.settings.catMarkers[category][0],
+ Number(this.settings.catMarkers[category][1]),
+ Number(this.settings.catMarkers[category][2])
+ );
+ }
+ }
+ }
+ }
+
+ // Custom single marker image override
+ if (this.settings.markerImg !== null) {
+ if (this.settings.markerDim === null) {
+ markerImg = this.markerImage(this.settings.markerImg);
+ } else {
+ markerImg = this.markerImage(
+ this.settings.markerImg,
+ this.settings.markerDim.width,
+ this.settings.markerDim.height
+ );
+ }
+ }
+
+ // Marker setup
+ if (this.settings.callbackCreateMarker) {
+ // Marker override callback
+ marker = this.settings.callbackCreateMarker.call(this, map, point, letter, category);
+ }
+ else {
+ // Create the default markers
+ 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))) {
+ if (this.useLegacyMarkers()) {
+ marker = new google.maps.Marker({
+ draggable: false,
+ icon : markerImg, // Reverts to default marker if markerImg not set.
+ map : map,
+ optimized: false,
+ position : point,
+ title : name,
+ });
+ } else {
+ marker = new google.maps.marker.AdvancedMarkerElement({
+ content : markerImg, // Reverts to default marker if markerImg not set.
+ draggable: false,
+ map : map,
+ position : point,
+ title : name,
+ });
+ }
+ }
+ else {
+ // Letter markers
+ if (this.useLegacyMarkers()) {
+ marker = new google.maps.Marker({
+ draggable: false,
+ label : letter,
+ map : map,
+ optimized: false,
+ position : point,
+ title : name,
+ });
+ } else {
+ var letterPin = new google.maps.marker.PinElement({glyph: letter});
+
+ marker = new google.maps.marker.AdvancedMarkerElement({
+ content : letterPin.element,
+ draggable: false,
+ map : map,
+ position : point,
+ title : name,
+ });
+ }
+ }
+ }
+
+ return marker;
+ },
+
+ /**
+ * Define the location data for the templates - private
+ *
+ * @param currentMarker {Object} Google Maps marker
+ * @param storeStart {number} optional first location on the current page
+ * @param page {number} optional current page
+ *
+ * @returns {Object} extended location data object
+ */
+ _defineLocationData: function (currentMarker, storeStart, page) {
+ this.writeDebug('_defineLocationData',arguments);
+ var indicator = '';
+
+ if (this.useLegacyMarkers()) {
+ this._createLocationVariables(currentMarker.get('id'));
+ } else {
+ this._createLocationVariables(currentMarker.bhslID);
+ }
+
+ var altDistLength,
+ distLength;
+
+ if (locationData.distance <= 1) {
+ if (this.settings.lengthUnit === 'km') {
+ distLength = this.settings.kilometerLang;
+ altDistLength = this.settings.mileLang;
+ }
+ else {
+ distLength = this.settings.mileLang;
+ altDistLength = this.settings.kilometerLang;
+ }
+ }
+ else {
+ if (this.settings.lengthUnit === 'km') {
+ distLength = this.settings.kilometersLang;
+ altDistLength = this.settings.milesLang;
+ }
+ else {
+ distLength = this.settings.milesLang;
+ altDistLength = this.settings.kilometersLang;
+ }
+ }
+
+ var markerId;
+
+ // Set up alpha character
+ if (this.useLegacyMarkers()) {
+ markerId = currentMarker.get('id');
+ } else {
+ markerId = currentMarker.bhslID;
+ }
+
+ // Use dot markers instead of alpha if there are more than 26 locations
+ 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))) {
+ if (page > 0) {
+ indicator = storeStart + markerId + 1;
+ } else {
+ indicator = markerId + 1;
+ }
+ } else {
+ if (page > 0) {
+ indicator = String.fromCharCode('A'.charCodeAt(0) + (storeStart + markerId));
+ }
+ else {
+ indicator = String.fromCharCode('A'.charCodeAt(0) + markerId);
+ }
+ }
+
+ // Define location data
+ return {
+ location: [$.extend(locationData, {
+ 'markerid' : markerId,
+ 'marker' : indicator,
+ 'altlength': altDistLength,
+ 'length' : distLength,
+ 'origin' : originalOrigin
+ })]
+ };
+ },
+
+ /**
+ * Set up the list templates
+ *
+ * @param marker {Object} Google Maps marker
+ * @param storeStart {number} optional first location on the current page
+ * @param page {number} optional current page
+ */
+ listSetup: function (marker, storeStart, page) {
+ this.writeDebug('listSetup',arguments);
+ // Define the location data
+ var locations = this._defineLocationData(marker, storeStart, page);
+
+ // Set up the list template with the location data
+ var listHtml = listTemplate(locations);
+ $('.' + this.settings.locationList + ' > ul').append(listHtml);
+ },
+
+ /**
+ * Change the selected marker image
+ *
+ * @param marker {Object} Google Maps marker object
+ */
+ changeSelectedMarker: function (marker) {
+ var markerImg;
+
+ // Reset the previously selected marker
+ if ( typeof prevSelectedMarkerAfter !== 'undefined' ) {
+ prevSelectedMarkerAfter.setIcon( prevSelectedMarkerBefore );
+ }
+
+ // Change the selected marker icon
+ if (this.settings.selectedMarkerImgDim === null) {
+ markerImg = this.markerImage(this.settings.selectedMarkerImg);
+ } else {
+ markerImg = this.markerImage(this.settings.selectedMarkerImg, this.settings.selectedMarkerImgDim.width, this.settings.selectedMarkerImgDim.height);
+ }
+
+ // Save the marker before switching it
+ prevSelectedMarkerBefore = marker.icon;
+
+ marker.setIcon( markerImg );
+
+ // Save the marker to a variable so it can be reverted when another marker is clicked
+ prevSelectedMarkerAfter = marker;
+ },
+
+ /**
+ * Create the infowindow
+ *
+ * @param marker {Object} Google Maps marker object
+ * @param location {string} indicates if the list or a map marker was clicked
+ * @param infowindow Google Maps InfoWindow constructor
+ * @param storeStart {number}
+ * @param page {number}
+ */
+ createInfowindow: function (marker, location, infowindow, storeStart, page) {
+ this.writeDebug('createInfowindow',arguments);
+ var _this = this;
+ // Define the location data
+ var locations = this._defineLocationData(marker, storeStart, page);
+
+ // Set up the infowindow template with the location data
+ var formattedAddress = infowindowTemplate(locations);
+
+ // Opens the infowindow when list item is clicked
+ if (location === 'left') {
+ infowindow.setContent(formattedAddress);
+
+ if (this.useLegacyMarkers()) {
+ infowindow.open(marker.get('map'), marker);
+ } else {
+ infowindow.open(marker.map, marker);
+ }
+ }
+ // Opens the infowindow when the marker is clicked
+ else {
+ if (this.useLegacyMarkers()) {
+ google.maps.event.addListener(marker, 'click', function () {
+ infowindow.setContent(formattedAddress);
+ infowindow.open(marker.get('map'), marker);
+ // Focus on the list
+ var markerId = marker.get('id');
+ var $selectedLocation = $('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']');
+
+ if ($selectedLocation.length > 0) {
+ // Marker click callback
+ if (_this.settings.callbackMarkerClick) {
+ _this.settings.callbackMarkerClick.call(this, marker, markerId, $selectedLocation, locationset[markerId], _this.map);
+ }
+
+ $('.' + _this.settings.locationList + ' li').removeClass('list-focus');
+ $selectedLocation.addClass('list-focus');
+
+ // Scroll list to selected marker
+ var $container = $('.' + _this.settings.locationList);
+ $container.animate({
+ scrollTop: $selectedLocation.offset().top - $container.offset().top + $container.scrollTop()
+ });
+ }
+
+ // Custom selected marker override
+ if (_this.settings.selectedMarkerImg !== null) {
+ _this.changeSelectedMarker(marker);
+ }
+ });
+ } else {
+ marker.addListener('click', function (domEvent, latLng) {
+ infowindow.setContent(formattedAddress);
+ infowindow.open(marker.map, marker);
+
+ // Focus on the list
+ var markerId = marker.bhslID;
+ var $selectedLocation = $('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']');
+
+ if ($selectedLocation.length > 0) {
+ // Marker click callback
+ if (_this.settings.callbackMarkerClick) {
+ _this.settings.callbackMarkerClick.call(this, marker, markerId, $selectedLocation, locationset[markerId], _this.map);
+ }
+
+ $('.' + _this.settings.locationList + ' li').removeClass('list-focus');
+ $selectedLocation.addClass('list-focus');
+
+ // Scroll list to selected marker
+ var $container = $('.' + _this.settings.locationList);
+ $container.animate({
+ scrollTop: $selectedLocation.offset().top - $container.offset().top + $container.scrollTop()
+ });
+ }
+
+ // Custom selected marker override
+ if (_this.settings.selectedMarkerImg !== null) {
+ _this.changeSelectedMarker(marker);
+ }
+ });
+ }
+ }
+ },
+
+ /**
+ * HTML5 geocoding function for automatic location detection
+ *
+ * @param position {Object} coordinates
+ */
+ autoGeocodeQuery: function (position) {
+ this.writeDebug('autoGeocodeQuery',arguments);
+ var _this = this,
+ distance = null,
+ $distanceInput = $('#' + this.settings.maxDistanceID),
+ originAddress;
+
+ // Query string parameters
+ if (this.settings.querystringParams === true) {
+ // Check for distance query string parameters
+ if (this.getQueryString(this.settings.maxDistanceID)){
+ distance = this.getQueryString(this.settings.maxDistanceID);
+
+ if ($distanceInput.val() !== '') {
+ distance = $distanceInput.val();
+ }
+ }
+ else{
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+ }
+ else {
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+
+ // The address needs to be determined for the directions link
+ var r = new this.reverseGoogleGeocode(this);
+ var latlng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
+ r.geocode({'latLng': latlng}, function (data) {
+ if (data !== null) {
+ originAddress = addressInput = data.address;
+ olat = mappingObj.lat = position.coords.latitude;
+ olng = mappingObj.lng = position.coords.longitude;
+ mappingObj.origin = originAddress;
+ mappingObj.distance = distance;
+ _this.mapping(mappingObj);
+
+ // Fill in the search box.
+ if (typeof originAddress !== 'undefined') {
+ $('#' + _this.settings.addressID).val(originAddress);
+ }
+ } else {
+ // Unable to geocode
+ _this.notify(_this.settings.addressErrorAlert);
+ }
+ });
+ },
+
+ /**
+ * Handle autoGeocode failure - private
+ *
+ */
+ _autoGeocodeError: function () {
+ this.writeDebug('_autoGeocodeError');
+ // If automatic detection doesn't work show an error
+ this.notify(this.settings.autoGeocodeErrorAlert);
+ },
+
+ /**
+ * Default location method
+ */
+ defaultLocation: function() {
+ this.writeDebug('defaultLocation');
+ var _this = this,
+ distance = null,
+ $distanceInput = $('#' + this.settings.maxDistanceID),
+ originAddress;
+
+ // Query string parameters
+ if (this.settings.querystringParams === true) {
+ // Check for distance query string parameters
+ if (this.getQueryString(this.settings.maxDistanceID)){
+ distance = this.getQueryString(this.settings.maxDistanceID);
+
+ if ($distanceInput.val() !== '') {
+ distance = $distanceInput.val();
+ }
+ }
+ else {
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+ }
+ else {
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+
+ // The address needs to be determined for the directions link
+ var r = new this.reverseGoogleGeocode(this);
+ var latlng = new google.maps.LatLng(this.settings.defaultLat, this.settings.defaultLng);
+ r.geocode({'latLng': latlng}, function (data) {
+ if (data !== null) {
+ originAddress = addressInput = data.address;
+ olat = mappingObj.lat = _this.settings.defaultLat;
+ olng = mappingObj.lng = _this.settings.defaultLng;
+ mappingObj.distance = distance;
+ mappingObj.origin = originAddress;
+ _this.mapping(mappingObj);
+ } else {
+ // Unable to geocode
+ _this.notify(_this.settings.addressErrorAlert);
+ }
+ });
+ },
+
+ /**
+ * Change the page
+ *
+ * @param newPage {number} page to change to
+ */
+ paginationChange: function (newPage) {
+ this.writeDebug('paginationChange',arguments);
+
+ var currentUrl = window.location.href;
+ var url = new URL(currentUrl);
+
+ // Update the page in the URL.
+ url.searchParams.set('bhsl-page', parseInt(newPage) + 1);
+
+ // Update the query string param to match the new value.
+ if (history.pushState) {
+ window.history.pushState({path: url.href}, '', url.href);
+ }
+
+ // Page change callback
+ if (this.settings.callbackPageChange) {
+ this.settings.callbackPageChange.call(this, newPage);
+ }
+
+ mappingObj.page = newPage;
+ this.mapping(mappingObj);
+ },
+
+ /**
+ * Get the address by marker ID
+ *
+ * @param markerID {number} location ID
+ *
+ * @returns {string} formatted address
+ */
+ getAddressByMarker: function(markerID) {
+ this.writeDebug('getAddressByMarker',arguments);
+ var formattedAddress = "";
+ // Set up formatted address
+ if(locationset[markerID].address){ formattedAddress += locationset[markerID].address + ' '; }
+ if(locationset[markerID].address2){ formattedAddress += locationset[markerID].address2 + ' '; }
+ if(locationset[markerID].city){ formattedAddress += locationset[markerID].city + ', '; }
+ if(locationset[markerID].state){ formattedAddress += locationset[markerID].state + ' '; }
+ if(locationset[markerID].postal){ formattedAddress += locationset[markerID].postal + ' '; }
+ if(locationset[markerID].country){ formattedAddress += locationset[markerID].country + ' '; }
+
+ return formattedAddress;
+ },
+
+ /**
+ * Clear the markers from the map
+ */
+ clearMarkers: function() {
+ this.writeDebug('clearMarkers');
+ var locationsLimit = null;
+
+ if (locationset.length < this.settings.storeLimit) {
+ locationsLimit = locationset.length;
+ }
+ else {
+ locationsLimit = this.settings.storeLimit;
+ }
+
+ for (var i = 0; i < locationsLimit; i++) {
+ markers[i].setMap(null);
+ }
+ },
+
+ /**
+ * Handle inline direction requests
+ *
+ * @param origin {string} origin address
+ * @param locID {number} location ID
+ * @param map {Object} Google Map
+ */
+ directionsRequest: function(origin, locID, map) {
+ this.writeDebug('directionsRequest',arguments);
+
+ // Directions request callback
+ if (this.settings.callbackDirectionsRequest) {
+ this.settings.callbackDirectionsRequest.call(this, origin, locID, map, locationset[locID]);
+ }
+
+ var destination = this.getAddressByMarker(locID);
+
+ if (destination) {
+ // Hide the location list
+ $('.' + this.settings.locationList + ' ul').hide();
+ // Remove the markers
+ this.clearMarkers();
+
+ // Clear the previous directions request
+ if (directionsDisplay !== null && typeof directionsDisplay !== 'undefined') {
+ directionsDisplay.setMap(null);
+ directionsDisplay = null;
+ }
+
+ directionsDisplay = new google.maps.DirectionsRenderer();
+ directionsService = new google.maps.DirectionsService();
+
+ // Directions request
+ directionsDisplay.setMap(map);
+ directionsDisplay.setPanel($('.bh-sl-directions-panel').get(0));
+
+ var request = {
+ origin: origin,
+ destination: destination,
+ travelMode: google.maps.TravelMode.DRIVING
+ };
+ directionsService.route(request, function(response, status) {
+ if (status === google.maps.DirectionsStatus.OK) {
+ directionsDisplay.setDirections(response);
+ }
+ });
+
+ $('.' + this.settings.locationList).prepend('');
+ }
+
+ $(document).off('click', '.' + this.settings.locationList + ' li .loc-directions a');
+ },
+
+ /**
+ * Close the directions panel and reset the map with the original locationset and zoom
+ */
+ closeDirections: function() {
+ this.writeDebug('closeDirections');
+
+ // Close directions callback
+ if (this.settings.callbackCloseDirections) {
+ this.settings.callbackCloseDirections.call(this);
+ }
+
+ // Remove the close icon, remove the directions, add the list back
+ this.reset();
+
+ if ((olat) && (olng)) {
+ if (this.countFilters() === 0) {
+ this.settings.mapSettings.zoom = originalZoom;
+ }
+ else {
+ this.settings.mapSettings.zoom = 0;
+ }
+ this.processForm(null);
+ }
+
+ $(document).off('click.'+pluginName, '.' + this.settings.locationList + ' .bh-sl-close-icon');
+ },
+
+ /**
+ * Handle length unit swap
+ *
+ * @param $lengthSwap
+ */
+ lengthUnitSwap: function($lengthSwap) {
+ this.writeDebug('lengthUnitSwap',arguments);
+
+ if ($lengthSwap.val() === 'alt-distance') {
+ $('.' + this.settings.locationList + ' .loc-alt-dist').show();
+ $('.' + this.settings.locationList + ' .loc-default-dist').hide();
+ } else if ($lengthSwap.val() === 'default-distance') {
+ $('.' + this.settings.locationList + ' .loc-default-dist').show();
+ $('.' + this.settings.locationList + ' .loc-alt-dist').hide();
+ }
+ },
+
+ /**
+ * Process the form values and/or query string
+ *
+ * @param e {Object} event
+ */
+ processForm: function (e) {
+ this.writeDebug('processForm',arguments);
+ var _this = this,
+ distance = null,
+ geocodeRestrictions = {},
+ $addressInput = $('#' + this.settings.addressID),
+ $searchInput = $('#' + this.settings.searchID),
+ $distanceInput = $('#' + this.settings.maxDistanceID),
+ region = '';
+
+ // Stop the form submission.
+ if (typeof e !== 'undefined' && e !== null) {
+ e.preventDefault();
+ }
+
+ // Blur any form field to hide mobile keyboards.
+ $('.' + _this.settings.formContainer +' input, .' + _this.settings.formContainer + ' select').blur();
+
+ // Query string parameters
+ if (this.settings.querystringParams === true) {
+ // Check for query string parameters
+ if (this.getQueryString(this.settings.addressID) || this.getQueryString(this.settings.searchID) || this.getQueryString(this.settings.maxDistanceID)) {
+ addressInput = this.getQueryString(this.settings.addressID);
+ searchInput = this.getQueryString(this.settings.searchID);
+ distance = this.getQueryString(this.settings.maxDistanceID);
+
+ // Max distance field.
+ if (distance && $('#' + this.settings.maxDistanceID + ' option[value=' + distance + ']').length) {
+ $distanceInput.val(distance);
+ }
+
+ // Update zoom if origin coordinates are available and a distance query string value is set.
+ if (addressInput && distance) {
+ _this.settings.mapSettings.zoom = 0;
+ }
+
+ // The form should override the query string parameters.
+ if ($addressInput.val() !== '') {
+ addressInput = $addressInput.val();
+ }
+ if ($searchInput.val() !== '') {
+ searchInput = $searchInput.val();
+ }
+ if ($distanceInput.val() !== '') {
+ distance = $distanceInput.val();
+ }
+ }
+ else {
+ // Get the user input and use it
+ addressInput = $addressInput.val() || '';
+ searchInput = $searchInput.val() || '';
+
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+ }
+ else {
+ // Get the user input and use it
+ addressInput = $addressInput.val() || '';
+ searchInput = $searchInput.val() || '';
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+
+ // Region
+ if (this.settings.callbackRegion) {
+ // Region override callback
+ region = this.settings.callbackRegion.call(this, addressInput, searchInput, distance);
+ } else {
+ // Region setting
+ region = $('#' + this.settings.regionID).val();
+ }
+
+ // Form values callback
+ if (this.settings.callbackFormVals) {
+ this.settings.callbackFormVals.call(this, addressInput, searchInput, distance, region);
+ }
+
+ // Add component restriction if the region has been set.
+ if (typeof region !== 'undefined') {
+ geocodeRestrictions = {
+ country: region
+ };
+ }
+
+ // Component restriction value via callback.
+ if (typeof this.settings.callbackGeocodeRestrictions === 'function') {
+ // Component restriction override callback
+ geocodeRestrictions = this.settings.callbackGeocodeRestrictions.call(this, addressInput, searchInput, distance);
+ }
+
+ if (addressInput === '' && searchInput === '' && this.settings.autoGeocode !== true) {
+ this._start();
+ }
+ else if (addressInput !== '') {
+ // Check for existing name search and remove if address input is blank.
+ if (searchInput === '' && filters.hasOwnProperty('name')) {
+ delete filters.name;
+ }
+
+ // Geocode the origin if needed
+ if (typeof originalOrigin !== 'undefined' && typeof olat !== 'undefined' && typeof olng !== 'undefined' && (addressInput === originalOrigin)) {
+ // Run the mapping function
+ mappingObj.lat = olat;
+ mappingObj.lng = olng;
+ mappingObj.origin = addressInput;
+ mappingObj.name = searchInput;
+ mappingObj.distance = distance;
+ _this.mapping(mappingObj);
+ }
+ else {
+ var g = new this.googleGeocode(this);
+ g.geocode({
+ address: addressInput,
+ componentRestrictions: geocodeRestrictions,
+ region: region
+ }, function (data) {
+ if (data !== null) {
+ olat = data.latitude;
+ olng = data.longitude;
+
+ // Run the mapping function
+ mappingObj.lat = olat;
+ mappingObj.lng = olng;
+ mappingObj.origin = addressInput;
+ mappingObj.name = searchInput;
+ mappingObj.distance = distance;
+ mappingObj.geocodeResult = data.geocodeResult;
+ _this.mapping(mappingObj);
+ } else {
+ // Unable to geocode
+ _this.notify(_this.settings.addressErrorAlert);
+ }
+ });
+ }
+ }
+ else if (searchInput !== '') {
+ // Check for existing origin and remove if address input is blank.
+ if ( addressInput === '' ) {
+ delete mappingObj.origin;
+ }
+
+ mappingObj.name = searchInput;
+ _this.mapping(mappingObj);
+ }
+ else if (this.settings.autoGeocode === true) {
+ // Run the mapping function
+ mappingObj.lat = olat;
+ mappingObj.lng = olng;
+ mappingObj.origin = addressInput;
+ mappingObj.name = searchInput;
+ mappingObj.distance = distance;
+ _this.mapping(mappingObj);
+ }
+
+ // Reset pagination if the input has changed.
+ if (typeof originalOrigin !== 'undefined' && addressInput !== originalOrigin) {
+ this.paginationReset();
+ }
+ },
+
+ /**
+ * Checks distance of each location and sets up the locationset array
+ *
+ * @param data {Object} location data object
+ * @param lat {number} origin latitude
+ * @param lng {number} origin longitude
+ * @param origin {string} origin address
+ * @param maxDistance {number} maximum distance if set
+ */
+ locationsSetup: function (data, lat, lng, origin, maxDistance) {
+ this.writeDebug('locationsSetup',arguments);
+ if (typeof origin !== 'undefined') {
+ if (!data.distance) {
+ data.distance = this.geoCodeCalcCalcDistance(lat, lng, data.lat, data.lng, GeoCodeCalc.EarthRadius);
+
+ // Alternative distance length unit
+ if (this.settings.lengthUnit === 'm') {
+ // Miles to kilometers
+ data.altdistance = parseFloat(data.distance)*1.609344;
+ } else if (this.settings.lengthUnit === 'km') {
+ // Kilometers to miles
+ data.altdistance = parseFloat(data.distance)/1.609344;
+ }
+ }
+ }
+
+ // Make sure the location coordinates are valid.
+ if (!this.coordinatesInRange(data.lat, data.lng)) {
+ this.writeDebug('locationsSetup', "location ignored because coordinates out of range: " + maxDistance, data);
+ return;
+ }
+
+ // Create the array
+ if (this.settings.maxDistance === true && typeof maxDistance !== 'undefined' && maxDistance !== null) {
+ if (data.distance <= maxDistance) {
+ locationset.push( data );
+ } else {
+ this.writeDebug('locationsSetup', "location ignored because it is out of maxDistance: " + maxDistance, data);
+ return;
+ }
+ } else if (this.settings.maxDistance === true && this.settings.querystringParams === true && typeof maxDistance !== 'undefined' && maxDistance !== null) {
+ if (data.distance <= maxDistance) {
+ locationset.push( data );
+ } else {
+ this.writeDebug('locationsSetup', "location ignored because it is out of maxDistance: " + maxDistance, data);
+ return;
+ }
+ } else {
+ locationset.push( data );
+ }
+ },
+
+ /**
+ * Set up front-end sorting functionality
+ */
+ sorting: function() {
+ this.writeDebug('sorting',arguments);
+ var _this = this,
+ $mapDiv = $('#' + _this.settings.mapID),
+ $sortSelect = $('#' + _this.settings.sortID);
+
+ if ($sortSelect.length === 0) {
+ return;
+ }
+
+ $sortSelect.on('change.'+pluginName, function (e) {
+ e.stopPropagation();
+
+ // Reset pagination.
+ if (_this.settings.pagination === true) {
+ _this.paginationChange(0);
+ }
+
+ var sortMethod,
+ sortVal;
+
+ sortMethod = (typeof $(this).find(':selected').attr('data-method') !== 'undefined') ? $(this).find(':selected').attr('data-method') : 'distance';
+ sortVal = $(this).val();
+
+ _this.settings.sortBy.method = sortMethod;
+ _this.settings.sortBy.prop = sortVal;
+
+ // Callback
+ if (_this.settings.callbackSorting) {
+ _this.settings.callbackSorting.call(this, _this.settings.sortBy);
+ }
+
+ if ($mapDiv.hasClass('bh-sl-map-open')) {
+ _this.mapping(mappingObj);
+ }
+ });
+ },
+
+ /**
+ * Set up front-end ordering functionality - this ties in to sorting and that has to be enabled for this to
+ * work.
+ */
+ order: function() {
+ this.writeDebug('order',arguments);
+ var _this = this,
+ $mapDiv = $('#' + _this.settings.mapID),
+ $orderSelect = $('#' + _this.settings.orderID);
+
+ if ($orderSelect.length === 0) {
+ return;
+ }
+
+ $orderSelect.on('change.'+pluginName, function (e) {
+ e.stopPropagation();
+
+ // Reset pagination.
+ if (_this.settings.pagination === true) {
+ _this.paginationChange(0);
+ }
+
+ _this.settings.sortBy.order = $(this).val();
+
+ // Callback
+ if (_this.settings.callbackOrder) {
+ _this.settings.callbackOrder.call(this, _this.settings.order);
+ }
+
+ if ($mapDiv.hasClass('bh-sl-map-open')) {
+ _this.mapping(mappingObj);
+ }
+ });
+ },
+
+ /**
+ * Distance filtering
+ */
+ distanceFiltering: function () {
+ this.writeDebug('distanceFiltering');
+ var _this = this;
+ var $distanceInput = $('#' + this.settings.maxDistanceID);
+
+ // Add event listener
+ $distanceInput.on('change.'+pluginName, function (e) {
+ e.stopPropagation();
+
+ // Query string parameter value updates on change.
+ if (_this.settings.querystringParams === true) {
+ var currentUrl = window.location.href;
+ var url = new URL(currentUrl);
+
+ // Update the distance in the URL.
+ url.searchParams.set(_this.settings.maxDistanceID, this.value);
+
+ // Update the query string param to match the new value.
+ if (history.pushState) {
+ window.history.pushState({path: url.href}, '', url.href);
+ } else {
+ window.location.replace(url.href);
+ }
+ }
+
+ if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
+ if ((olat) && (olng)) {
+ _this.settings.mapSettings.zoom = 0;
+ _this.processForm();
+ } else {
+ _this.mapping(mappingObj);
+ }
+ }
+ });
+ },
+
+ /**
+ * Count the selected filters
+ *
+ * @returns {number}
+ */
+ countFilters: function () {
+ this.writeDebug('countFilters');
+ var filterCount = 0;
+
+ if (!this.isEmptyObject(filters)) {
+ for (var key in filters) {
+ if (filters.hasOwnProperty(key)) {
+ filterCount += filters[key].length;
+ }
+ }
+ }
+
+ return filterCount;
+ },
+
+ /**
+ * Find the existing checked boxes for each checkbox filter - private
+ *
+ * @param key {string} object key
+ */
+ _existingCheckedFilters: function(key) {
+ this.writeDebug('_existingCheckedFilters',arguments);
+ $('#' + this.settings.taxonomyFilters[key] + ' input[type=checkbox]').each(function () {
+ if ($(this).prop('checked')) {
+ var filterVal = $(this).val();
+
+ // Only add the taxonomy id if it doesn't already exist
+ if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
+ filters[key].push(filterVal);
+ }
+ }
+ });
+ },
+
+ /**
+ * Find the existing selected value for each select filter - private
+ *
+ * @param key {string} object key
+ */
+ _existingSelectedFilters: function(key) {
+ this.writeDebug('_existingSelectedFilters',arguments);
+ $('#' + this.settings.taxonomyFilters[key] + ' select').each(function () {
+ var filterVal = $(this).val();
+
+ // Only add the taxonomy id if it doesn't already exist
+ if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
+ filters[key] = [filterVal];
+ }
+ });
+ },
+
+ /**
+ * Find the existing selected value for each radio button filter - private
+ *
+ * @param key {string} object key
+ */
+ _existingRadioFilters: function(key) {
+ this.writeDebug('_existingRadioFilters',arguments);
+ $('#' + this.settings.taxonomyFilters[key] + ' input[type=radio]').each(function () {
+ if ($(this).prop('checked')) {
+ var filterVal = $(this).val();
+
+ // Only add the taxonomy id if it doesn't already exist
+ if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
+ filters[key] = [filterVal];
+ }
+ }
+ });
+ },
+
+ /**
+ * Check for existing filter selections
+ */
+ checkFilters: function () {
+ this.writeDebug('checkFilters');
+ for(var key in this.settings.taxonomyFilters) {
+
+ if (this.settings.taxonomyFilters.hasOwnProperty(key)) {
+ // Find the existing checked boxes for each checkbox filter
+ this._existingCheckedFilters(key);
+
+ // Find the existing selected value for each select filter
+ this._existingSelectedFilters(key);
+
+ // Find the existing value for each radio button filter
+ this._existingRadioFilters(key);
+ }
+ }
+ },
+
+ /**
+ * Select the indicated values from query string parameters.
+ *
+ * @param taxonomy {string} Current taxonomy.
+ * @param value {array} Query string array values.
+ */
+ selectQueryStringFilters: function( taxonomy, value ) {
+ this.writeDebug('selectQueryStringFilters', arguments);
+
+ var $taxGroupContainer = $('#' + this.settings.taxonomyFilters[taxonomy]);
+
+ // Handle checkboxes.
+ if ( $taxGroupContainer.find('input[type="checkbox"]').length ) {
+
+ for ( var i = 0; i < value.length; i++ ) {
+ $taxGroupContainer.find('input:checkbox[value="' + value[i] + '"]').prop('checked', true);
+ }
+ }
+
+ // Handle select fields.
+ if ( $taxGroupContainer.find('select').length ) {
+ // Only expecting one value for select fields.
+ $taxGroupContainer.find('option[value="' + value[0] + '"]').prop('selected', true);
+ }
+
+ // Handle radio buttons.
+ if ( $taxGroupContainer.find('input[type="radio"]').length ) {
+ // Only one value for radio button.
+ $taxGroupContainer.find('input:radio[value="' + value[0] + '"]').prop('checked', true);
+ }
+ },
+
+ /**
+ * Check query string parameters for filter values.
+ */
+ checkQueryStringFilters: function () {
+ this.writeDebug('checkQueryStringFilters',arguments);
+
+ // Loop through the filters.
+ for(var key in filters) {
+ if (filters.hasOwnProperty(key)) {
+ var filterVal = this.getQueryString(key);
+
+ // Check for multiple values separated by comma.
+ if ( filterVal.indexOf( ',' ) !== -1 ) {
+ filterVal = filterVal.split( ',' );
+ }
+
+ // Only add the taxonomy id if it doesn't already exist
+ if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
+ if ( Array.isArray( filterVal ) ) {
+ filters[key] = filterVal;
+ } else {
+ filters[key] = [filterVal];
+ }
+ }
+
+ // Select the filters indicated in the query string.
+ if ( filters[key].length ) {
+ this.selectQueryStringFilters( key, filters[key] );
+ }
+ }
+ }
+ },
+
+ /**
+ * Get the filter key from the taxonomyFilter setting
+ *
+ * @param filterContainer {string} ID of the changed filter's container
+ */
+ getFilterKey: function (filterContainer) {
+ this.writeDebug('getFilterKey',arguments);
+ for (var key in this.settings.taxonomyFilters) {
+ if (this.settings.taxonomyFilters.hasOwnProperty(key)) {
+ for (var i = 0; i < this.settings.taxonomyFilters[key].length; i++) {
+ if (this.settings.taxonomyFilters[key] === filterContainer) {
+ return key;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Initialize or reset the filters object to its original state
+ */
+ taxonomyFiltersInit: function () {
+ this.writeDebug('taxonomyFiltersInit');
+
+ // Set up the filters
+ for(var key in this.settings.taxonomyFilters) {
+ if (this.settings.taxonomyFilters.hasOwnProperty(key)) {
+ filters[key] = [];
+ }
+ }
+ },
+
+ /**
+ * Taxonomy filtering
+ */
+ taxonomyFiltering: function() {
+ this.writeDebug('taxonomyFiltering');
+ var _this = this;
+
+ // Set up the filters
+ _this.taxonomyFiltersInit();
+
+ // Check query string for taxonomy parameter keys.
+ _this.checkQueryStringFilters();
+
+ // Handle filter updates
+ $('.' + this.settings.taxonomyFiltersContainer).on('change.'+pluginName, 'input, select', function (e) {
+ e.stopPropagation();
+
+ var filterVal, filterContainer, filterKey;
+
+ // Reset pagination.
+ if (_this.settings.pagination === true) {
+ _this.paginationReset();
+ }
+
+ // Handle checkbox filters
+ if ($(this).is('input[type="checkbox"]')) {
+ // First check for existing selections
+ _this.checkFilters();
+
+ filterVal = $(this).val();
+ filterContainer = $(this).closest('.bh-sl-filters').attr('id');
+ filterKey = _this.getFilterKey(filterContainer);
+
+ if (filterKey) {
+ // Add or remove filters based on checkbox values
+ if ($(this).prop('checked')) {
+ // Add ids to the filter arrays as they are checked
+ if (filters[filterKey].indexOf(filterVal) === -1) {
+ filters[filterKey].push(filterVal);
+ }
+
+ if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
+ if ((olat) && (olng)) {
+ _this.settings.mapSettings.zoom = 0;
+ _this.processForm();
+ }
+ else {
+ _this.mapping(mappingObj);
+ }
+ }
+ }
+ else {
+ // Remove ids from the filter arrays as they are unchecked
+ var filterIndex = filters[filterKey].indexOf(filterVal);
+ if (filterIndex > -1) {
+ filters[filterKey].splice(filterIndex, 1);
+ if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
+ if ((olat) && (olng)) {
+ if (_this.countFilters() === 0) {
+ _this.settings.mapSettings.zoom = originalZoom;
+ } else {
+ _this.settings.mapSettings.zoom = 0;
+ }
+
+ _this.processForm();
+ }
+ else {
+ _this.mapping(mappingObj);
+ }
+ }
+ }
+ }
+ }
+ }
+ // Handle select or radio filters
+ else if ($(this).is('select') || $(this).is('input[type="radio"]')) {
+ // First check for existing selections
+ _this.checkFilters();
+
+ filterVal = $(this).val();
+ filterContainer = $(this).closest('.bh-sl-filters').attr('id');
+ filterKey = _this.getFilterKey(filterContainer);
+
+ // Check for blank filter on select since default val could be empty
+ if (filterVal) {
+ if (filterKey) {
+ filters[filterKey] = [filterVal];
+ if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
+ if ((olat) && (olng)) {
+ _this.settings.mapSettings.zoom = 0;
+ _this.processForm();
+ } else {
+ _this.mapping(mappingObj);
+ }
+ }
+ }
+ }
+ // Reset if the default option is selected
+ else {
+ if (filterKey) {
+ filters[filterKey] = [];
+ }
+ _this.reset();
+ if ((olat) && (olng)) {
+ _this.settings.mapSettings.zoom = originalZoom;
+ _this.processForm();
+ }
+ else {
+ _this.mapping(mappingObj);
+ }
+ }
+ }
+ });
+ },
+
+ /**
+ * Updates the location list to reflect the markers that are displayed on the map
+ *
+ * @param markers {Object} Map markers
+ * @param map {Object} Google map
+ */
+ checkVisibleMarkers: function(markers, map) {
+ this.writeDebug('checkVisibleMarkers',arguments);
+ var _this = this;
+ var locations, listHtml;
+
+ // Empty the location list
+ $('.' + this.settings.locationList + ' ul').empty();
+
+ // Set up the new list
+ $(markers).each(function(x, marker){
+ if (_this.useLegacyMarkers()) {
+ if (map.getBounds().contains(marker.getPosition())) {
+ // Define the location data
+ _this.listSetup(marker, 0, 0);
+
+ // Set up the list template with the location data
+ listHtml = listTemplate(locations);
+ $('.' + _this.settings.locationList + ' > ul').append(listHtml);
+ }
+ } else {
+ if (map.getBounds().contains(marker.position)) {
+ // Define the location data
+ _this.listSetup(marker, 0, 0);
+
+ // Set up the list template with the location data
+ listHtml = listTemplate(locations);
+ $('.' + _this.settings.locationList + ' > ul').append(listHtml);
+ }
+ }
+ });
+
+ // Re-add the list background colors
+ $('.' + this.settings.locationList + ' ul li:even').css('background', this.settings.listColor1);
+ $('.' + this.settings.locationList + ' ul li:odd').css('background', this.settings.listColor2);
+ },
+
+ /**
+ * Performs a new search when the map is dragged to a new position
+ *
+ * @param map {Object} Google map
+ */
+ dragSearch: function(map) {
+ this.writeDebug('dragSearch',arguments);
+ var newCenter = map.getCenter(),
+ newCenterCoords,
+ _this = this;
+
+ // Save the new zoom setting
+ this.settings.mapSettings.zoom = map.getZoom();
+
+ olat = mappingObj.lat = newCenter.lat();
+ olng = mappingObj.lng = newCenter.lng();
+
+ // Determine the new origin address
+ var newAddress = new this.reverseGoogleGeocode(this);
+ newCenterCoords = new google.maps.LatLng(mappingObj.lat, mappingObj.lng);
+ newAddress.geocode({'latLng': newCenterCoords}, function (data) {
+ if (data !== null) {
+ mappingObj.origin = addressInput = data.address;
+ _this.mapping(mappingObj);
+ } else {
+ // Unable to geocode
+ _this.notify(_this.settings.addressErrorAlert);
+ }
+ });
+ },
+
+ /**
+ * Handle no results
+ */
+ emptyResult: function() {
+ this.writeDebug('emptyResult',arguments);
+ var center,
+ locList = $('.' + this.settings.locationList + ' ul'),
+ myOptions = this.settings.mapSettings,
+ noResults;
+
+ // Create the map
+ this.map = new google.maps.Map(document.getElementById(this.settings.mapID), myOptions);
+
+ // Callback
+ if (this.settings.callbackNoResults) {
+ this.settings.callbackNoResults.call(this, this.map, myOptions);
+ }
+
+ // Empty the location list
+ locList.empty();
+
+ // Append the no results message
+ noResults = $('' + this.settings.noResultsTitle + '
' + this.settings.noResultsDesc + '').hide().fadeIn();
+ locList.append(noResults);
+
+ // Center on the original origin or 0,0 if not available
+ if ((olat) && (olng)) {
+ center = new google.maps.LatLng(olat, olng);
+ } else {
+ center = new google.maps.LatLng(0, 0);
+ }
+
+ this.map.setCenter(center);
+
+ if (originalZoom) {
+ this.map.setZoom(originalZoom);
+ }
+ },
+
+
+ /**
+ * Origin marker setup
+ *
+ * @param map {Object} Google map
+ * @param origin {string} Origin address
+ * @param originPoint {Object} LatLng of origin point
+ */
+ originMarker: function(map, origin, originPoint) {
+ this.writeDebug('originMarker',arguments);
+
+ if (this.settings.originMarker !== true) {
+ return;
+ }
+
+ var marker,
+ originImg;
+
+ if (typeof origin !== 'undefined') {
+ if (this.useLegacyMarkers()) {
+ if (this.settings.originMarkerImg !== null) {
+ if (this.settings.originMarkerDim === null) {
+ originImg = this.markerImage(this.settings.originMarkerImg);
+ }
+ else {
+ originImg = this.markerImage(this.settings.originMarkerImg, this.settings.originMarkerDim.width, this.settings.originMarkerDim.height);
+ }
+ }
+ else {
+ originImg = {
+ url: 'https://mt.googleapis.com/vt/icon/name=icons/spotlight/spotlight-waypoint-a.png'
+ };
+ }
+
+ marker = new google.maps.Marker({
+ position : originPoint,
+ map : map,
+ icon : originImg,
+ draggable: false
+ });
+ } else {
+ // Default green origin pin.
+ var defaultOriginPin = new google.maps.marker.PinElement({
+ background : '#39b25e',
+ borderColor: '#177d3d',
+ glyphColor : '#177d3c'
+ });
+
+ marker = new google.maps.marker.AdvancedMarkerElement({
+ content : defaultOriginPin.element,
+ draggable: false,
+ map : map,
+ position : originPoint,
+ title : name,
+ });
+
+ // Origin image.
+ if (this.settings.originMarkerImg !== null) {
+ originImg = document.createElement('img');
+
+ if (this.settings.originMarkerDim === null) {
+ originImg.src = this.settings.originMarkerImg;
+ } else {
+ originImg = this.markerImage(
+ this.settings.originMarkerImg,
+ this.settings.originMarkerDim.width,
+ this.settings.originMarkerDim.height,
+ );
+ }
+
+ marker.content = originImg;
+ }
+ }
+ }
+ },
+
+ /**
+ * Modal window setup
+ */
+ modalWindow: function() {
+ this.writeDebug('modalWindow');
+
+ if (this.settings.modal !== true) {
+ return;
+ }
+
+ var _this = this;
+
+ // Callback
+ if (_this.settings.callbackModalOpen) {
+ _this.settings.callbackModalOpen.call(this);
+ }
+
+ // Pop up the modal window
+ $('.' + _this.settings.overlay).fadeIn();
+ // Close modal when close icon is clicked and when background overlay is clicked
+ $(document).on('click.'+pluginName, '.' + _this.settings.closeIcon + ', .' + _this.settings.overlay, function () {
+ _this.modalClose();
+ });
+ // Prevent clicks within the modal window from closing the entire thing
+ $(document).on('click.'+pluginName, '.' + _this.settings.modalWindow, function (e) {
+ e.stopPropagation();
+ });
+ // Close modal when escape key is pressed
+ $(document).on('keyup.'+pluginName, function (e) {
+ if (e.keyCode === 27) {
+ _this.modalClose();
+ }
+ });
+ },
+
+ /**
+ * Open and select the location closest to the origin
+ *
+ * @param nearestLoc {Object} Details for the nearest location
+ * @param infowindow {Object} Info window object
+ * @param storeStart {number} Starting point of current page when pagination is enabled
+ * @param page {number} Current page number when pagination is enabled
+ */
+ openNearestLocation: function(nearestLoc, infowindow, storeStart, page) {
+ this.writeDebug('openNearestLocation',arguments);
+
+ if (
+ this.settings.openNearest !== true ||
+ typeof nearestLoc === 'undefined' ||
+ typeof originalOrigin === 'undefined' ||
+ (this.settings.fullMapStart === true && firstRun === true && this.settings.querystringParams === false) ||
+ (this.settings.defaultLoc === true && firstRun === true && this.settings.querystringParams === false)
+ ) {
+ return;
+ }
+
+ var _this = this;
+
+ // Callback
+ if (_this.settings.callbackNearestLoc) {
+ _this.settings.callbackNearestLoc.call(this, _this.map, nearestLoc, infowindow, storeStart, page);
+ }
+
+ var markerId = (nearestLoc.hasOwnProperty('markerid')) ? nearestLoc.markerid : 0;
+ var selectedMarker = markers[markerId];
+
+ _this.createInfowindow(selectedMarker, 'left', infowindow, storeStart, page);
+
+ // Scroll list to selected marker
+ var $container = $('.' + _this.settings.locationList);
+ var $selectedLocation = $('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']');
+
+ // Focus on the list
+ $('.' + _this.settings.locationList + ' li').removeClass('list-focus');
+ $selectedLocation.addClass('list-focus');
+
+ $container.animate({
+ scrollTop: $selectedLocation.offset().top - $container.offset().top + $container.scrollTop()
+ });
+ },
+
+ /**
+ * Handle clicks from the location list
+ *
+ * @param map {Object} Google map
+ * @param infowindow {Object} Info window object
+ * @param storeStart {number} Starting point of current page when pagination is enabled
+ * @param page {number} Current page number when pagination is enabled
+ */
+ listClick: function(map, infowindow, storeStart, page) {
+ this.writeDebug('listClick',arguments);
+ var _this = this;
+
+ $(document).on('click.' + pluginName, '.' + _this.settings.locationList + ' li', function () {
+ var markerId = $(this).data('markerid');
+ var selectedMarker = markers[markerId];
+
+ // List click callback
+ if (_this.settings.callbackListClick) {
+ _this.settings.callbackListClick.call(this, markerId, selectedMarker, locationset[markerId], map);
+ }
+
+ if (_this.useLegacyMarkers()) {
+ map.panTo(selectedMarker.getPosition());
+ } else {
+ map.panTo(selectedMarker.position);
+ }
+
+ var listLoc = 'left';
+ _this.createInfowindow(selectedMarker, listLoc, infowindow, storeStart, page);
+
+ // Custom selected marker override
+ if (_this.settings.selectedMarkerImg !== null) {
+ _this.changeSelectedMarker(selectedMarker);
+ }
+
+ // Focus on the list
+ $('.' + _this.settings.locationList + ' li').removeClass('list-focus');
+ $('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']').addClass('list-focus');
+ });
+
+ // Prevent bubbling from list content links
+ $(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' li a', function(e) {
+ e.stopPropagation();
+ });
+ },
+
+ /**
+ * Output total results count if HTML element with .bh-sl-total-results class exists
+ *
+ * @param locCount
+ */
+ resultsTotalCount: function(locCount) {
+ this.writeDebug('resultsTotalCount',arguments);
+
+ var $resultsContainer = $('.bh-sl-total-results');
+
+ if (typeof locCount === 'undefined' || locCount <= 0 || $resultsContainer.length === 0) {
+ return;
+ }
+
+ $resultsContainer.text(locCount);
+ },
+
+ /**
+ * Inline directions setup
+ *
+ * @param map {Object} Google map
+ * @param origin {string} Origin address
+ */
+ inlineDirections: function(map, origin) {
+ this.writeDebug('inlineDirections',arguments);
+
+ if (this.settings.inlineDirections !== true || typeof origin === 'undefined') {
+ return;
+ }
+
+ var _this = this;
+
+ // Open directions
+ $(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' li .loc-directions a', function (e) {
+ e.preventDefault();
+ var locID = $(this).closest('li').attr('data-markerid');
+ _this.directionsRequest(origin, parseInt(locID), map);
+
+ // Close directions
+ $(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' .bh-sl-close-icon', function () {
+ _this.closeDirections();
+ });
+ });
+ },
+
+ /**
+ * Visible markers list setup
+ *
+ * @param map {Object} Google map
+ * @param markers {Object} Map markers
+ */
+ visibleMarkersList: function(map, markers) {
+ this.writeDebug('visibleMarkersList',arguments);
+
+ if (this.settings.visibleMarkersList !== true) {
+ return;
+ }
+
+ var _this = this;
+
+ // Add event listener to filter the list when the map is fully loaded
+ google.maps.event.addListenerOnce(map, 'idle', function(){
+ _this.checkVisibleMarkers(markers, map);
+ });
+
+ // Add event listener for center change
+ google.maps.event.addListener(map, 'center_changed', function() {
+ _this.checkVisibleMarkers(markers, map);
+ });
+
+ // Add event listener for zoom change
+ google.maps.event.addListener(map, 'zoom_changed', function() {
+ _this.checkVisibleMarkers(markers, map);
+ });
+ },
+
+ /**
+ * Restrict featured locations from displaying in results by a specific distance
+ *
+ * @returns {Array}
+ */
+ featuredDistanceRestriction: function() {
+ this.writeDebug('featuredDistanceRestriction',arguments);
+ var _this = this;
+
+ featuredset = $.grep(featuredset, function (val) {
+
+ if (val.hasOwnProperty('distance')) {
+ return parseFloat(val.distance) <= parseFloat(_this.settings.featuredDistance);
+ }
+ });
+
+ return featuredset;
+ },
+
+ /**
+ * Restrict featured locations by distance.
+ *
+ * @returns {Array}
+ */
+ featuredRestrictions: function(mappingObject) {
+ this.writeDebug('featuredRestrictions',arguments);
+
+ if (this.settings.featuredDistance === null) {
+ return featuredset;
+ }
+
+ // Featured locations radius restriction.
+ if (this.settings.featuredDistance !== null) {
+ featuredset = this.featuredDistanceRestriction(mappingObject);
+ }
+
+ return featuredset;
+ },
+
+ /**
+ * The primary mapping function that runs everything
+ *
+ * @param mappingObject {Object} all the potential mapping properties - latitude, longitude, origin, name, max
+ * distance, page
+ */
+ mapping: function (mappingObject) {
+ this.writeDebug('mapping',arguments);
+ var _this = this;
+ var orig_lat, orig_lng, geocodeData, origin, originPoint, page;
+ if (!this.isEmptyObject(mappingObject)) {
+ orig_lat = mappingObject.lat;
+ orig_lng = mappingObject.lng;
+ geocodeData = mappingObject.geocodeResult;
+ origin = mappingObject.origin;
+ page = mappingObject.page;
+ }
+
+ // Set the initial page to zero if not set
+ if ( _this.settings.pagination === true ) {
+ if (typeof page === 'undefined' || originalOrigin !== addressInput ) {
+ page = 0;
+ }
+
+ paginationPage = page;
+ }
+
+ // Override page if the query string was set.
+ var queryStringPage = _this.getQueryString('bhsl-page');
+ if (queryStringPage !== '') {
+ page = paginationPage = parseInt(queryStringPage) - 1;
+ }
+
+ // Data request
+ if (typeof origin === 'undefined' && this.settings.nameSearch === true) {
+ dataRequest = _this._getData();
+ }
+ else {
+ // Set up the origin point
+ originPoint = new google.maps.LatLng(orig_lat, orig_lng);
+
+ // If the origin hasn't changed use the existing data, so we aren't making unneeded AJAX requests
+ if ((typeof originalOrigin !== 'undefined') && (origin === originalOrigin) && (typeof originalData !== 'undefined') && this.settings.pagination !== true) {
+ origin = originalOrigin;
+ dataRequest = originalData;
+ }
+ else {
+ // Do the data request - doing this in mapping so the lat/lng and address can be passed over and used if needed
+ dataRequest = _this._getData(olat, olng, origin, geocodeData, mappingObject);
+ }
+ }
+
+ // Check filters here to handle selected filtering after page reload
+ if (_this.settings.taxonomyFilters !== null && _this.hasEmptyObjectVals(filters)) {
+ _this.checkFilters();
+ }
+ /**
+ * Process the location data
+ */
+ // Raw data
+ if ( _this.settings.dataRaw !== null ) {
+ _this.processData(mappingObject, originPoint, dataRequest, page);
+ }
+ // Remote data
+ else {
+ dataRequest.done(function (data) {
+ _this.processData(mappingObject, originPoint, data, page);
+ });
+ }
+ },
+
+ /**
+ * Reset disabled form fields
+ */
+ resetDisabledFilterVals: function() {
+ this.writeDebug('resetDisabledFilterVals');
+
+ for (var taxKey in this.settings.taxonomyFilters) {
+ if (this.settings.taxonomyFilters.hasOwnProperty(taxKey)) {
+ for (var x = 0; x < this.settings.taxonomyFilters[taxKey].length; x++) {
+ $('#' + this.settings.taxonomyFilters[taxKey] + ' input,option').each(function () {
+ var disabled = $(this).attr('disabled');
+
+ if (typeof disabled !== 'undefined') {
+ $(this).removeAttr('disabled');
+ }
+ });
+ }
+ }
+ }
+ },
+
+ /**
+ * Get available filter values
+ *
+ * @param callback
+ */
+ getAvailableFilters: function(callback) {
+ this.writeDebug('getAvailableFilters');
+ var availableValues = [];
+
+ for (var location in locationset) {
+ if (locationset.hasOwnProperty(location)) {
+ // Loop through the location values.
+ for (var locationKey in locationset[location]) {
+ if (filters.hasOwnProperty(locationKey) && locationset[location][locationKey] !== '') {
+ if (availableValues.hasOwnProperty(locationKey)) {
+ var availableVal = availableValues[locationKey].concat(',', locationset[location][locationKey].replace(', ', ',').trim());
+ availableValues[locationKey] = Array.from(new Set(availableVal.split(','))).toString();
+ } else {
+ availableValues[locationKey] = locationset[location][locationKey].replace(', ', ',').trim();
+ }
+ }
+ }
+ }
+ }
+
+ // Account for missing filter properties in location set.
+ for (var keyName in filters) {
+ if (!availableValues.hasOwnProperty(keyName)) {
+ availableValues[keyName] = '';
+ }
+ }
+
+ callback(availableValues);
+ },
+
+ /**
+ * Disable input fields that aren't available within the current location set
+ */
+ maybeDisableFilterOptions: function() {
+ this.writeDebug('maybeDisableFilterOptions');
+ var availableValues = [];
+ var _this = this;
+
+ // Initially reset any input/option fields that were previously disabled.
+ this.resetDisabledFilterVals();
+
+ // Loop through current location set to determine what filter values are still available.
+ this.getAvailableFilters( function(values) {
+ availableValues = values;
+
+ // Save the original filter values for reference.
+ if (typeof originalFilterVals === 'undefined') {
+ originalFilterVals = availableValues;
+ }
+
+ // Update input and option fields to disabled if they're not available.
+ for (var key in _this.settings.taxonomyFilters) {
+ if (_this.settings.taxonomyFilters.hasOwnProperty(key)) {
+
+ // Loop through the taxonomy filter group items.
+ for (var i = 0; i < _this.settings.taxonomyFilters[key].length; i++) {
+ if (_this.settings.taxonomyFilters.hasOwnProperty(key)) {
+ $('#' + _this.settings.taxonomyFilters[key] + ' input, #' + _this.settings.taxonomyFilters[key] + ' option').each(function () {
+
+ // Initial determination of values that should be disabled.
+ if ($(this).val() !== '' && ! Array.from(new Set(availableValues[key].split(','))).includes($(this).val())) {
+ if (! disabledFilterVals.hasOwnProperty(key)) {
+ disabledFilterVals[key] = [];
+ }
+
+ // Handle select options and radio button values when there is no address input.
+ if (
+ (typeof addressInput === 'undefined' || addressInput === '') &&
+ ($(this).prop('tagName') === 'OPTION' || $(this).prop('type') === 'radio') &&
+ _this.hasSingleGroupFilterVal(filters, key) &&
+ Array.from(new Set(originalFilterVals[key].split(','))).includes($(this).val())
+ ) {
+ return;
+ }
+
+ // Handle select options and radio button values when there is address input.
+ if (
+ (typeof addressInput !== 'undefined' || addressInput !== '') &&
+ ($(this).prop('tagName') === 'OPTION' || $(this).prop('type') === 'radio') &&
+ _this.hasSingleGroupFilterVal(filters, key) &&
+ Array.from(new Set(originalFilterVals[key].split(','))).includes($(this).val()) &&
+ _this.countFilters() === 1
+ ) {
+ return;
+ }
+
+ // Keep select options and radio button available values after one filter has been selected.
+ if (
+ ($(this).prop('tagName') === 'OPTION' || $(this).prop('type') === 'radio') &&
+ _this.hasSingleGroupFilterVal(filters, key) &&
+ _this.countFilters() > 1 &&
+ Array.from(new Set(originalFilterVals[key].split(','))).includes($(this).val()) &&
+ ! disabledFilterVals[key].includes($(this).val())
+ ) {
+ return;
+ }
+
+ // Track disabled values.
+ if (
+ disabledFilterVals.hasOwnProperty(key) &&
+ Array.isArray(disabledFilterVals[key]) &&
+ ! disabledFilterVals[key].includes($(this).val())
+ ) {
+ disabledFilterVals[key].push($(this).val());
+ }
+
+ $(this).attr('disabled', true);
+ }
+ });
+ }
+ }
+ }
+ }
+ });
+ },
+
+ /**
+ * Processes the location data
+ *
+ * @param mappingObject {Object} all the potential mapping properties - latitude, longitude, origin, name, max
+ * distance, page
+ * @param originPoint {Object} LatLng of origin point
+ * @param data {Object} location data
+ * @param page {number} current page number
+ */
+ processData: function (mappingObject, originPoint, data, page) {
+ this.writeDebug('processData',arguments);
+ var _this = this;
+ var i = 0;
+ var orig_lat, orig_lng, origin, name, maxDistance, marker, bounds, storeStart, storeNumToShow, myOptions, distError, openMap, infowindow, nearestLoc;
+ var taxFilters = {};
+ var $lengthSwap = $('#' + _this.settings.lengthSwapID);
+
+ if (!this.isEmptyObject(mappingObject)) {
+ orig_lat = mappingObject.lat;
+ orig_lng = mappingObject.lng;
+ origin = mappingObject.origin;
+ name = mappingObject.name;
+ maxDistance = mappingObject.distance;
+ }
+
+ var $mapDiv = $('#' + _this.settings.mapID);
+ // Get the length unit
+ var distUnit = (_this.settings.lengthUnit === 'km') ? _this.settings.kilometersLang : _this.settings.milesLang;
+
+ // Save data and origin separately so we can potentially avoid multiple AJAX requests
+ originalData = dataRequest;
+ if ( typeof origin !== 'undefined' ) {
+ originalOrigin = origin;
+ }
+
+ // Callback
+ if (_this.settings.callbackSuccess) {
+ _this.settings.callbackSuccess.call(this, mappingObject, originPoint, data, page);
+ }
+
+ openMap = $mapDiv.hasClass('bh-sl-map-open');
+
+ // Set a variable for fullMapStart, so we can detect the first run
+ if (
+ (_this.settings.fullMapStart === true && openMap === false) ||
+ (_this.settings.autoGeocode === true && openMap === false) ||
+ (_this.settings.defaultLoc === true && openMap === false)
+ ) {
+ firstRun = true;
+ } else if (
+ (_this.settings.fullMapStart === true && reload === true) ||
+ (_this.settings.autoGeocode === true && reload === true) ||
+ (_this.settings.defaultLoc === true && reload === true)
+ ) {
+ _this.reset();
+ } else {
+ _this.reset();
+ }
+
+ $mapDiv.addClass('bh-sl-map-open');
+
+ // Process the location data depending on the data format type
+ if (_this.settings.dataType === 'json' || _this.settings.dataType === 'jsonp') {
+
+ // Process JSON
+ for(var x = 0; i < data.length; x++){
+ var obj = data[x];
+ var locationData = {};
+
+ // Parse each data variable
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ locationData[key] = obj[key];
+ }
+ }
+
+ _this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
+
+ i++;
+ }
+ }
+ else if (_this.settings.dataType === 'kml') {
+ // Process KML
+ $(data).find('Placemark').each(function () {
+ var locationData = {
+ 'name' : $(this).find('name').text(),
+ 'lat' : $(this).find('coordinates').text().split(',')[1],
+ 'lng' : $(this).find('coordinates').text().split(',')[0],
+ 'description': $(this).find('description').text()
+ };
+
+ _this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
+
+ i++;
+ });
+ }
+ else {
+ // Process XML
+ $(data).find(_this.settings.xmlElement).each(function () {
+ var locationData = {};
+
+ for (var key in this.attributes) {
+ if (this.attributes.hasOwnProperty(key)) {
+ locationData[this.attributes[key].name] = this.attributes[key].value;
+ }
+ }
+
+ _this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
+
+ i++;
+ });
+ }
+
+ // Name search - using taxonomy filter to handle
+ if (_this.settings.nameSearch === true) {
+ if (typeof searchInput !== 'undefined' && '' !== searchInput) {
+
+ if (_this.settings.nameAttribute.indexOf(',')) {
+ nameAttrs = _this.settings.nameAttribute.split(',');
+
+ // Multiple name attributes should swap to exclusive filtering.
+ if (_this.settings.exclusiveTax !== null) {
+ _this.settings.exclusiveTax.concat(nameAttrs);
+ } else {
+ _this.settings.exclusiveTax = nameAttrs;
+ }
+
+ for (var a = 0; a < nameAttrs.length; a++) {
+ filters[nameAttrs[a].trim()] = [searchInput];
+ }
+ } else {
+ filters[_this.settings.nameAttribute] = [searchInput];
+ }
+ }
+
+ // Check for a previous value.
+ if (
+ typeof searchInput !== 'undefined' &&
+ '' === searchInput
+ ) {
+ if (typeof nameAttrs !== 'undefined') {
+ for (var pa = 0; pa < nameAttrs.length; pa++) {
+ if (nameAttrs[pa] in filters) {
+ delete filters[nameAttrs[pa]];
+ }
+ }
+ } else {
+ delete filters[_this.settings.nameAttribute];
+ }
+ }
+ }
+
+ // Taxonomy filtering setup
+ if (_this.settings.taxonomyFilters !== null || _this.settings.nameSearch === true) {
+
+ for(var k in filters) {
+ if (filters.hasOwnProperty(k) && filters[k].length > 0) {
+ // Let's use regex
+ for (var z = 0; z < filters[k].length; z++) {
+ // Creating a new object so we don't mess up the original filters
+ if (!taxFilters[k]) {
+ taxFilters[k] = [];
+ }
+
+ // Swap pattern matching depending on name search vs. taxonomy filtering.
+ if (typeof nameAttrs !== 'undefined') {
+ if (nameAttrs.indexOf(k) !== -1) {
+ taxFilters[k][z] = '(?:^|\\s)' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '');
+ } else {
+ taxFilters[k][z] = '(?=.*' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '') + '(?!\\s))';
+ }
+ } else {
+ if (k === _this.settings.nameAttribute) {
+ taxFilters[k][z] = '(?:^|\\s)' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '');
+ } else {
+ taxFilters[k][z] = '(?=.*' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '') + '(?!\\s))';
+ }
+ }
+ }
+ }
+ }
+
+ // Filter the data
+ if (!_this.isEmptyObject(taxFilters)) {
+ locationset = $.grep(locationset, function (val) {
+ return _this.filterData(val, taxFilters);
+ });
+ }
+ }
+
+ // Sorting
+ if (_this.settings.sortBy !== null && typeof _this.settings.sortBy === 'object') {
+
+ // Sort the multi-dimensional array by distance to get the nearest location first when enabled
+ if (_this.settings.openNearest === true && typeof originalOrigin !== 'undefined') {
+ this.sortNumerically(locationset, true);
+
+ // Save the closest location to a variable for openNearest setting
+ if (typeof locationset[0] !== 'undefined') {
+
+ if (this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
+ nearestLoc = locationset[locationset.length - 1];
+ } else {
+ nearestLoc = locationset[0];
+ }
+ }
+ }
+
+ // Custom sorting
+ _this.sortCustom(locationset);
+ } else {
+ // Sort the multi-dimensional array by distance
+ if (typeof origin !== 'undefined') {
+ _this.sortNumerically(locationset);
+ }
+
+ // Check the closest marker
+ if (_this.isEmptyObject(taxFilters)) {
+ if (_this.settings.maxDistance === true && maxDistance) {
+ if (typeof locationset[0] === 'undefined' || locationset[0].distance > maxDistance) {
+ _this.notify(_this.settings.distanceErrorAlert + maxDistance + ' ' + distUnit);
+ }
+ }
+ else {
+ if (typeof locationset[0] !== 'undefined') {
+ if (
+ _this.settings.distanceAlert !== -1 &&
+ locationset[0].distance > _this.settings.distanceAlert &&
+ (typeof paginationPage === 'undefined' || parseInt(paginationPage) === 1)
+ ) {
+ _this.notify(_this.settings.distanceErrorAlert + _this.settings.distanceAlert + ' ' + distUnit);
+ distError = true;
+ }
+ }
+ else {
+ _this.emptyResult();
+ throw new Error('No locations found. Please check the dataLocation setting and path.');
+ return;
+ }
+ }
+ }
+
+ // Save the closest location to a variable for openNearest setting
+ if (typeof locationset[0] !== 'undefined') {
+ nearestLoc = locationset[0];
+ }
+ }
+
+ // Featured locations filtering
+ if (_this.settings.featuredLocations === true) {
+
+ // Create array for featured locations
+ featuredset = $.grep(locationset, function (val) {
+ if (val.hasOwnProperty('featured')) {
+ return val.featured === 'true';
+ }
+ });
+
+ // Featured location restrictions.
+ featuredset = _this.featuredRestrictions(mappingObject);
+
+ // Create array for normal locations
+ normalset = $.grep(locationset, function (val) {
+ if (val.hasOwnProperty('featured')) {
+ return val.featured !== 'true';
+ }
+ });
+
+ // Combine the arrays
+ locationset = [];
+ locationset = featuredset.concat(normalset);
+ }
+
+ // Disable filter inputs if there are no locations with the values left.
+ if (
+ (firstRun !== true && _this.settings.exclusiveFiltering === false) ||
+ (_this.settings.fullMapStart === true && _this.settings.exclusiveFiltering === false) ||
+ (_this.settings.defaultLoc === true && _this.settings.exclusiveFiltering === false)
+ ) {
+ _this.maybeDisableFilterOptions();
+ }
+
+ // Slide in the map container
+ if (_this.settings.slideMap === true) {
+ $this.slideDown();
+ }
+
+ // Output page numbers if pagination setting is true
+ if (_this.settings.pagination === true) {
+ _this.paginationSetup(page);
+ }
+
+ // Alternative method to display no results if locations are too far away instead of all locations.
+ if (_this.settings.altDistanceNoResult === true && nearestLoc.distance > _this.settings.distanceAlert) {
+ _this.emptyResult();
+ return;
+ }
+
+ // Handle no results
+ if (_this.isEmptyObject(locationset) || locationset[0].result === 'none') {
+ _this.emptyResult();
+ return;
+ }
+
+ // Set up the modal window
+ _this.modalWindow();
+
+ // Avoid error if number of locations is less than the default of 26
+ 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))) {
+ storeNum = locationset.length;
+ }
+ else {
+ storeNum = _this.settings.storeLimit;
+ }
+
+ // If fullMapStart is enabled and taxFilters is reset and name search and origin are empty, swap back to the original length.
+ if (
+ _this.settings.fullMapStart === true &&
+ _this.isEmptyObject(taxFilters) &&
+ (searchInput === '' || typeof searchInput === 'undefined') &&
+ (addressInput === '' || typeof addressInput === 'undefined')
+ ) {
+ storeNum = locationset.length;
+ }
+
+ // If pagination is on, change the store limit to the setting and slice the locationset array
+ if (_this.settings.pagination === true) {
+ storeNumToShow = _this.settings.locationsPerPage;
+ storeStart = page * _this.settings.locationsPerPage;
+
+ if ( (storeStart + storeNumToShow) > locationset.length ) {
+ storeNumToShow = _this.settings.locationsPerPage - ((storeStart + storeNumToShow) - locationset.length);
+ }
+
+ locationset = locationset.slice(storeStart, storeStart + storeNumToShow);
+ storeNum = locationset.length;
+ }
+ else {
+ storeNumToShow = storeNum;
+ storeStart = 0;
+ }
+
+ // Output location results count
+ _this.resultsTotalCount(locationset.length);
+
+ // Google maps settings
+ if (
+ (_this.settings.fullMapStart === true && firstRun === true && _this.settings.querystringParams !== true) ||
+ (_this.settings.mapSettings.zoom === 0) ||
+ (typeof origin === 'undefined') ||
+ (distError === true) ||
+ ((_this.settings.maxDistance === true && firstRun === false) && this.countFilters() > 0)
+ ) {
+ myOptions = _this.settings.mapSettings;
+ bounds = new google.maps.LatLngBounds();
+ }
+ else if (_this.settings.pagination === true) {
+ // Update the map to focus on the first point in the new set
+ var nextPoint = new google.maps.LatLng(locationset[0].lat, locationset[0].lng);
+
+ if (page === 0) {
+ _this.settings.mapSettings.center = originPoint;
+ myOptions = _this.settings.mapSettings;
+ }
+ else {
+ _this.settings.mapSettings.center = nextPoint;
+ myOptions = _this.settings.mapSettings;
+ }
+ }
+ else {
+ _this.settings.mapSettings.center = originPoint;
+ myOptions = _this.settings.mapSettings;
+ }
+
+ // Create the map
+ _this.map = new google.maps.Map(document.getElementById(_this.settings.mapID), myOptions);
+
+ // Re-center the map when the browser is re-sized
+ window.addEventListener('resize', function() {
+ var center = _this.map.getCenter();
+ google.maps.event.trigger(_this.map, 'resize');
+ _this.map.setCenter(center);
+ });
+
+ // Add map drag listener if setting is enabled and re-search on drag end
+ if (_this.settings.dragSearch === true ) {
+ _this.map.addListener('dragend', function() {
+ _this.dragSearch(_this.map);
+ });
+ }
+
+ // Load the map
+ $this.data(_this.settings.mapID.replace('#', ''), _this.map);
+
+ // Map set callback.
+ if (_this.settings.callbackMapSet) {
+ _this.settings.callbackMapSet.call(this, _this.map, originPoint, originalZoom, myOptions);
+ }
+
+ // Initialize the infowindow
+ if ( typeof InfoBubble !== 'undefined' && _this.settings.infoBubble !== null ) {
+ var infoBubbleSettings = _this.settings.infoBubble;
+ infoBubbleSettings.map = _this.map;
+
+ infowindow = new InfoBubble(infoBubbleSettings);
+ } else {
+ infowindow = new google.maps.InfoWindow();
+ }
+
+ // Add origin marker if the setting is set
+ _this.originMarker(_this.map, origin, originPoint);
+
+ // Handle pagination
+ $(document).on('click.'+pluginName, '.bh-sl-pagination li a', function (e) {
+ e.preventDefault();
+ // Run paginationChange
+ _this.paginationChange($(this).parent().attr('data-page'));
+ });
+
+ // Inline directions
+ _this.inlineDirections(_this.map, origin);
+
+ // Add markers and infowindows loop
+ for (var y = 0; y <= storeNumToShow - 1; y++) {
+ var letter = '';
+
+ if (page > 0) {
+ letter = String.fromCharCode('A'.charCodeAt(0) + (storeStart + y));
+ }
+ else {
+ letter = String.fromCharCode('A'.charCodeAt(0) + y);
+ }
+
+ var point = new google.maps.LatLng(locationset[y].lat, locationset[y].lng);
+ marker = _this.createMarker(point, locationset[y].name, locationset[y].address, letter, _this.map, locationset[y].category);
+
+ if (_this.useLegacyMarkers()) {
+ marker.set('id', y);
+ } else {
+ marker.bhslID = y;
+ }
+
+ markers[y] = marker;
+
+ // Add marker ID to location data
+ if (_this.useLegacyMarkers()) {
+ locationset[y].markerid = marker.get('id');
+ } else {
+ locationset[y].markerid = marker.bhslID;
+ }
+
+ if (this.settings.dataRaw !== null) {
+ for (var l = 0; l < this.settings.dataRaw.length; l++) {
+ if (this.settings.dataRaw[l] && this.settings.dataRaw[l].hasOwnProperty('id') && this.settings.dataRaw[l].id === locationset[y].id) {
+ this.settings.dataRaw[l].markerid = locationset[y].markerid;
+ }
+ }
+ }
+
+ if (
+ (_this.settings.fullMapStart === true && firstRun === true && _this.settings.querystringParams !== true) ||
+ (_this.settings.mapSettings.zoom === 0) ||
+ (typeof origin === 'undefined') ||
+ (distError === true) ||
+ ((_this.settings.maxDistance === true && firstRun === false) && this.countFilters() > 0)
+ ) {
+ bounds.extend(point);
+ }
+ // Pass variables to the pop-up infowindows
+ _this.createInfowindow(marker, null, infowindow, storeStart, page);
+ }
+
+ // Center and zoom if no origin or zoom was provided, or distance of first marker is greater than distanceAlert
+ if (
+ (_this.settings.fullMapStart === true && firstRun === true && _this.settings.querystringParams !== true) ||
+ (_this.settings.mapSettings.zoom === 0) ||
+ (typeof origin === 'undefined') ||
+ (distError === true) ||
+ ((_this.settings.maxDistance === true && firstRun === false) && this.countFilters() > 0)
+ ) {
+ _this.map.fitBounds(bounds);
+
+ // Prevent zooming in too far after fitBounds
+ var zoomListener = google.maps.event.addListener(_this.map, 'idle', function() {
+ if (_this.map.getZoom() > 16) {
+ _this.map.setZoom(16);
+ }
+ google.maps.event.removeListener(zoomListener);
+ });
+ }
+
+ // Create the links that focus on the related marker
+ var locList = $('.' + _this.settings.locationList + ' ul');
+ locList.empty();
+
+ // Set up the location list markup
+ if (
+ firstRun &&
+ _this.settings.fullMapStartListLimit !== false &&
+ !isNaN(_this.settings.fullMapStartListLimit) &&
+ _this.settings.fullMapStartListLimit !== -1 &&
+ markers.length > _this.settings.fullMapStartListLimit
+ ) {
+ for (var m = 0; m < _this.settings.fullMapStartListLimit; m++) {
+ var currentMarker = markers[m];
+ _this.listSetup(currentMarker, storeStart, page);
+ }
+ } else {
+ $(markers).each(function (x) {
+ var currentMarker = markers[x];
+ _this.listSetup(currentMarker, storeStart, page);
+ });
+ }
+
+ // Length unit swap setup
+ if ($lengthSwap.length) {
+ _this.lengthUnitSwap($lengthSwap);
+
+ $lengthSwap.on('change.'+pluginName, function (e) {
+ e.stopPropagation();
+ _this.lengthUnitSwap($lengthSwap);
+ });
+ }
+
+ // Open nearest location.
+ _this.openNearestLocation(nearestLoc, infowindow, storeStart, page);
+
+ // MarkerClusterer setup.
+ if (_this.useLegacyMarkers()) {
+ if ( typeof MarkerClusterer !== 'undefined' && _this.settings.markerCluster !== null ) {
+ var markerCluster = new MarkerClusterer(_this.map, markers, _this.settings.markerCluster);
+ }
+ } else {
+ if ( typeof markerClusterer !== 'undefined' ) {
+ var customClustererParams = _this.settings.markerCluster;
+
+ new markerClusterer.MarkerClusterer({
+ markers,
+ map: _this.map,
+ customClustererParams
+ });
+ }
+ }
+
+ // Handle clicks from the list
+ _this.listClick(_this.map, infowindow, storeStart, page);
+
+ // Add the list li background colors - this wil be dropped in a future version in favor of CSS
+ $('.' + _this.settings.locationList + ' ul > li:even').css('background', _this.settings.listColor1);
+ $('.' + _this.settings.locationList + ' ul > li:odd').css('background', _this.settings.listColor2);
+
+ // Visible markers list
+ _this.visibleMarkersList(_this.map, markers);
+
+ // Fill in form values from query string parameters.
+ if (_this.settings.querystringParams === true) {
+ var $addressInput = $('#' + _this.settings.addressID);
+ var $searchInput = $('#' + _this.settings.searchID);
+
+ // Address field.
+ if (typeof mappingObj !== 'undefined' && mappingObj.hasOwnProperty('origin') && $addressInput.val() === '') {
+ $addressInput.val(mappingObj.origin);
+ }
+
+ // Name search field.
+ if (typeof mappingObj !== 'undefined' && mappingObj.hasOwnProperty('name') && $searchInput.val() === '') {
+ $searchInput.val(mappingObj.name);
+ }
+ }
+
+ // Modal ready callback
+ if (_this.settings.modal === true && _this.settings.callbackModalReady) {
+ _this.settings.callbackModalReady.call(this, mappingObject);
+ }
+
+ // Filters callback
+ if (_this.settings.callbackFilters) {
+ _this.settings.callbackFilters.call(this, filters, mappingObject);
+ }
+ },
+
+ /**
+ * console.log helper function
+ *
+ * http://www.briangrinstead.com/blog/console-log-helper-function
+ */
+ writeDebug: function () {
+ if (window.console && this.settings.debug) {
+ // Only run on the first time through - reset this function to the appropriate console.log helper
+ if (Function.prototype.bind) {
+ this.writeDebug = Function.prototype.bind.call(console.log, console, 'StoreLocator :');
+ } else {
+ this.writeDebug = function () {
+ arguments[0] = 'StoreLocator : ' + arguments[0];
+ Function.prototype.apply.call(console.log, console, arguments);
+ };
+ }
+ this.writeDebug.apply(this, arguments);
+ }
+ }
+
+ });
+
+ // A really lightweight plugin wrapper around the constructor,
+ // preventing against multiple instantiations and allowing any
+ // public function (ie. a function whose name doesn't start
+ // with an underscore) to be called via the jQuery plugin,
+ // e.g. $(element).defaultPluginName('functionName', arg1, arg2)
+ $.fn[ pluginName ] = function (options) {
+ var args = arguments;
+ // Is the first parameter an object (options), or was omitted, instantiate a new instance of the plugin
+ if (options === undefined || typeof options === 'object') {
+ return this.each(function () {
+ // Only allow the plugin to be instantiated once, so we check that the element has no plugin instantiation yet
+ if (!$.data(this, 'plugin_' + pluginName)) {
+ // 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.
+ $.data(this, 'plugin_' + pluginName, new Plugin( this, options ));
+ }
+ });
+ // Treat this as a call to a public method
+ } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
+ // Cache the method call to make it possible to return a value
+ var returns;
+
+ this.each(function () {
+ var instance = $.data(this, 'plugin_' + pluginName);
+
+ // Tests that there's already a plugin-instance and checks that the requested public method exists
+ if (instance instanceof Plugin && typeof instance[options] === 'function') {
+
+ // Call the method of our plugin instance, and pass it the supplied arguments.
+ returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
+ }
+
+ // Allow instances to be destroyed via the 'destroy' method
+ if (options === 'destroy') {
+ $.data(this, 'plugin_' + pluginName, null);
+ }
+ });
+
+ // If the earlier cached method gives a value back return the value, otherwise return this to preserve chainability.
+ return returns !== undefined ? returns : this;
+ }
+ };
+
+
+})(jQuery, window, document);
diff --git a/dist/assets/js/plugins/storeLocator/jquery.storelocator.min.js b/dist/assets/js/plugins/storeLocator/jquery.storelocator.min.js
new file mode 100644
index 0000000..ba38032
--- /dev/null
+++ b/dist/assets/js/plugins/storeLocator/jquery.storelocator.min.js
@@ -0,0 +1,5 @@
+/*! jQuery Google Maps Store Locator - v3.4.1 - 2024-12-20
+* http://www.bjornblog.com/web/jquery-store-locator-plugin
+* Copyright (c) 2024 Bjorn Holine; Licensed MIT */
+
+!function(B,N,j,t){"use strict";var _,a,i,c,z,Q,V,q,U,K,g,h,H,n,m,f,s,W,J,$,o,Z,X,Y,tt,et,st,r,l,it,u,d,nt="storeLocator",b=!1,e=null;function p(t,s){var i,e;_=B(t),this.element=t,this.settings=B.extend({},d,s),this._defaults=d,this._name=nt,""!==this.settings.mapSettingsID&&(this.settings.mapSettings.mapId=this.settings.mapSettingsID),this.settings.lazyLoadMap&&null!==this.settings.apiKey&&"undefined"==typeof google?(i=this,e={},e.libraries="marker",!0===this.settings.autoComplete&&(e.libraries="places,marker"),this.settings.callbackBeforeMapInject?new Promise(function(t,e){i.settings.callbackBeforeMapInject.call(this,s,t)}).then(function(){i.triggerMapLoad(e)}):i.triggerMapLoad(e)):this.init()}void 0===B.fn[nt]&&(d={ajaxData:null,altDistanceNoResult:!(u={}),apiKey:null,autoComplete:!(it={}),autoCompleteDisableListener:!(l={}),autoCompleteOptions:{},autoGeocode:!(r={}),bounceMarker:!0,catMarkers:null,dataLocation:"data/locations.json",dataRaw:null,dataType:"json",debug:!(st={}),defaultLat:null,defaultLng:null,defaultLoc:!(et=[]),disableAlphaMarkers:!(tt=[]),distanceAlert:60,dragSearch:!(Y=[]),exclusiveFiltering:!(X=[]),exclusiveTax:null,featuredDistance:null,featuredLocations:!1,fullMapStart:!1,fullMapStartBlank:!1,fullMapStartListLimit:!1,infoBubble:null,inlineDirections:!1,lazyLoadMap:!1,lengthUnit:"m",listColor1:"#ffffff",listColor2:"#eeeeee",loading:!1,locationsPerPage:10,mapSettings:{mapTypeId:"roadmap",zoom:12},mapSettingsID:"",markerCluster:null,markerImg:null,markerDim:null,maxDistance:!1,modal:!1,nameAttribute:"name",nameSearch:!1,noForm:!1,openNearest:!1,originMarker:!1,originMarkerDim:null,originMarkerImg:null,pagination:!1,querystringParams:!1,selectedMarkerImg:null,selectedMarkerImgDim:null,sessionStorage:!1,slideMap:!0,sortBy:null,storeLimit:26,taxonomyFilters:null,visibleMarkersList:!1,xmlElement:"marker",addressID:"bh-sl-address",closeIcon:"bh-sl-close-icon",formContainer:"bh-sl-form-container",formID:"bh-sl-user-location",geocodeID:null,lengthSwapID:"bh-sl-length-swap",loadingContainer:"bh-sl-loading",locationList:"bh-sl-loc-list",mapID:"bh-sl-map",maxDistanceID:"bh-sl-maxdistance",modalContent:"bh-sl-modal-content",modalWindow:"bh-sl-modal-window",orderID:"bh-sl-order",overlay:"bh-sl-overlay",regionID:"bh-sl-region",searchID:"bh-sl-search",sortID:"bh-sl-sort",taxonomyFiltersContainer:"bh-sl-filters-container",infowindowTemplatePath:"assets/js/plugins/storeLocator/templates/infowindow-description.html",listTemplatePath:"assets/js/plugins/storeLocator/templates/location-list-description.html",KMLinfowindowTemplatePath:"assets/js/plugins/storeLocator/templates/kml-infowindow-description.html",KMLlistTemplatePath:"assets/js/plugins/storeLocator/templates/kml-location-list-description.html",listTemplateID:null,infowindowTemplateID:null,callbackAutoGeoSuccess:null,callbackBeforeMapInject:null,callbackBeforeSend:null,callbackCloseDirections:null,callbackCreateMarker:null,callbackDirectionsRequest:null,callbackFilters:null,callbackFormVals:null,callbackGeocodeRestrictions:null,callbackJsonp:null,callbackListClick:null,callbackMapSet:null,callbackMarkerClick:null,callbackModalClose:null,callbackModalOpen:null,callbackModalReady:null,callbackNearestLoc:null,callbackNoResults:null,callbackNotify:null,callbackOrder:null,callbackPageChange:null,callbackRegion:null,callbackSorting:null,callbackSuccess:null,addressErrorAlert:"Unable to find address",autoGeocodeErrorAlert:"Automatic location detection failed. Please fill in your address or zip code.",distanceErrorAlert:"Unfortunately, our closest location is more than ",kilometerLang:"kilometer",kilometersLang:"kilometers",mileLang:"mile",milesLang:"miles",noResultsTitle:"No results",noResultsDesc:"No locations were found with the given criteria. Please modify your selections or input.",nextPage:"Next »",prevPage:"« Prev"},B.extend(p.prototype,{init:function(){var t,e=this;this.writeDebug("init"),"km"===this.settings.lengthUnit?l.EarthRadius=6367:l.EarthRadius=3956,c="kml"===this.settings.dataType?"xml":this.settings.dataType,!0===this.settings.inlineDirections&&B("."+this.settings.locationList).prepend('
'),V=this.settings.mapSettings.zoom,Handlebars.registerHelper("niceURL",function(t){if(t)return t.replace("https://","").replace("http://","")}),!0===this.settings.maxDistance&&this.distanceFiltering(),null!==this.settings.taxonomyFilters&&this.taxonomyFiltering(),this.sorting(),this.order(),!0===this.settings.modal&&(null!==this.settings.taxonomyFilters&&B("."+this.settings.taxonomyFiltersContainer).clone(!0,!0).prependTo(_),_.wrap('
'),B("."+this.settings.modalWindow).prepend('
'),B("."+this.settings.overlay).hide()),!0===this.settings.autoComplete&&(t=j.getElementById(this.settings.addressID),t=new google.maps.places.Autocomplete(t,this.settings.autoCompleteOptions),!0===this.settings.autoComplete)&&!0!==this.settings.autoCompleteDisableListener&&t.addListener("place_changed",function(t){e.processForm(t)}),this._loadTemplates()},triggerMapLoad:function(t){this.writeDebug("triggerMapLoad");var e=this;this.loadMapsAPI(this.settings.apiKey,t).then(function(t){e.map=t,e.init()})},injectGoogleMapsScript:function(e){if(this.writeDebug("injectGoogleMapsScript"),e=void 0!==e?e:{},b)throw new Error("Google Maps API is already loaded.");var t="https://maps.googleapis.com/maps/api/js?"+Object.keys(e).map(t=>encodeURIComponent(t)+"="+encodeURIComponent(e[t])).join("&"),s=j.createElement("script");s.setAttribute("src",t),s.setAttribute("async",""),s.setAttribute("defer",""),j.head.appendChild(s),b=!0},loadMapsAPI:function(s,i){this.writeDebug("loadMapsAPI"),i=void 0!==i?i:{};var n=this;return e=e||new Promise(function(t,e){try{N.onGoogleMapsAPILoaded=t,n.injectGoogleMapsScript({key:s,loading:"async",callback:"onGoogleMapsAPILoaded",...i})}catch(t){e(t)}}).then(function(){N.google.maps})},destroy:function(){this.writeDebug("destroy"),this.reset();var t=B("#"+this.settings.mapID);if(et.length)for(var e=0;e<=et.length;e++)google.maps.event.removeListener(et[e]);B("."+this.settings.locationList+" ul").empty(),t.hasClass("bh-sl-map-open")&&t.empty().removeClass("bh-sl-map-open"),!0===this.settings.modal&&B(". "+this.settings.overlay).remove(),t.attr("style",""),_.hide(),B.removeData(_.get(0)),B(j).off(nt),_.unbind()},reset:function(){var t;this.writeDebug("reset"),Y=[],X=[],tt=[],W=!(et=[]),B(j).off("click."+nt,"."+this.settings.locationList+" li"),B("."+this.settings.locationList+" .bh-sl-close-directions-container").length&&B(".bh-sl-close-directions-container").remove(),!0===this.settings.inlineDirections&&(0<(t=B("."+this.settings.locationList+" .adp")).length&&(t.remove(),B("."+this.settings.locationList+" ul").fadeIn()),B(j).off("click","."+this.settings.locationList+" li .loc-directions a")),!0===this.settings.pagination&&B(j).off("click."+nt,".bh-sl-pagination li a")},formFiltersReset:function(){var t,e;this.writeDebug("formFiltersReset"),null!==this.settings.taxonomyFilters&&(t=B("."+this.settings.taxonomyFiltersContainer+" input"),e=B("."+this.settings.taxonomyFiltersContainer+" select"),"object"==typeof t)&&(t.each(function(){(B(this).is('input[type="checkbox"]')||B(this).is('input[type="radio"]'))&&B(this).prop("checked",!1)}),e.each(function(){B(this).prop("selectedIndex",0)}))},mapReload:function(){this.writeDebug("mapReload"),this.reset(),J=!0,null!==this.settings.taxonomyFilters&&(this.formFiltersReset(),this.resetDisabledFilterVals(),this.taxonomyFiltersInit()),g&&h?(this.settings.mapSettings.zoom=V,this.processForm()):this.mapping(it)},notify:function(t){this.writeDebug("notify",t),this.settings.callbackNotify?this.settings.callbackNotify.call(this,t):alert(t)},geoCodeCalcToRadian:function(t){return this.writeDebug("geoCodeCalcToRadian",t),t*(Math.PI/180)},geoCodeCalcDiffRadian:function(t,e){return this.writeDebug("geoCodeCalcDiffRadian",arguments),this.geoCodeCalcToRadian(e)-this.geoCodeCalcToRadian(t)},geoCodeCalcCalcDistance:function(t,e,s,i,n){return this.writeDebug("geoCodeCalcCalcDistance",arguments),2*n*Math.asin(Math.min(1,Math.sqrt(Math.pow(Math.sin(this.geoCodeCalcDiffRadian(t,s)/2),2)+Math.cos(this.geoCodeCalcToRadian(t))*Math.cos(this.geoCodeCalcToRadian(s))*Math.pow(Math.sin(this.geoCodeCalcDiffRadian(e,i)/2),2))))},inRange:function(t,e,s){return this.writeDebug("inRange",arguments),e=Math.abs(e),isFinite(e)&&t<=e&&e<=s},coordinatesInRange:function(t,e){return this.writeDebug("coordinatesInRange",arguments),this.inRange(-90,t,90)&&this.inRange(-180,e,180)},getQueryString:function(t){if(this.writeDebug("getQueryString",t),t)return t=t.replace(/[\[]/,"\\[").replace(/[\]]/,"\\]"),null===(t=new RegExp("[\\?&]"+t+"=([^]*)").exec(location.search))?"":decodeURIComponent(t[1].replace(/\+/g," "))},getMap:function(){return this.map},_loadTemplates:function(){this.writeDebug("_loadTemplates");var e,t=this,s='
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.
';"kml"===this.settings.dataType&&null===this.settings.listTemplateID&&null===this.settings.infowindowTemplateID?B.when(B.get(this.settings.KMLinfowindowTemplatePath,function(t){e=t,i=Handlebars.compile(e)}),B.get(this.settings.KMLlistTemplatePath,function(t){e=t,a=Handlebars.compile(e)})).then(function(){t.locator()},function(){throw B("."+t.settings.formContainer).append(s),new Error("Could not load storeLocator plugin templates")}):null!==this.settings.listTemplateID&&null!==this.settings.infowindowTemplateID?(i=Handlebars.compile(B("#"+this.settings.infowindowTemplateID).html()),a=Handlebars.compile(B("#"+this.settings.listTemplateID).html()),t.locator()):B.when(B.get(this.settings.infowindowTemplatePath,function(t){e=t,i=Handlebars.compile(e)}),B.get(this.settings.listTemplatePath,function(t){e=t,a=Handlebars.compile(e)})).then(function(){t.locator()},function(){throw B("."+t.settings.formContainer).append(s),new Error("Could not load storeLocator plugin templates")})},locator:function(){this.writeDebug("locator"),!0===this.settings.slideMap&&_.hide(),this._start(),this._formEventHandler()},_formEventHandler:function(){this.writeDebug("_formEventHandler");var e=this;!0===this.settings.noForm?(B(j).on("click."+nt,"."+this.settings.formContainer+" button",function(t){e.processForm(t)}),B(j).on("keydown."+nt,function(t){13===t.keyCode&&B("#"+e.settings.addressID).is(":focus")&&e.processForm(t)})):B(j).on("submit."+nt,"#"+this.settings.formID,function(t){e.processForm(t)}),B(".bh-sl-reset").length&&B("#"+this.settings.mapID).length&&B(j).on("click."+nt,".bh-sl-reset",function(){e.mapReload()}),B("#"+this.settings.addressID).on("change."+nt,function(){o=t,u={},""!==B.trim(B("#"+e.settings.addressID).val())||void 0!==U&&""!==U||null!==e.settings.taxonomyFilters&&!1===e.settings.exclusiveFiltering&&(z=h=g=t,it={},e.resetDisabledFilterVals(),e.taxonomyFiltersInit(),e.mapping(null))})},_getData:function(t,e,s,i,n){this.writeDebug("_getData",arguments);var a,o=this,r="",l="",g="";return void 0!==i&&void 0!==i.geometry.bounds&&(g=i.formatted_address,r=JSON.stringify(i.geometry.bounds.getNorthEast()),l=JSON.stringify(i.geometry.bounds.getSouthWest())),this.settings.callbackBeforeSend&&this.settings.callbackBeforeSend.call(this,t,e,s,g,r,l,n),null!==o.settings.dataRaw?"xml"===c?B.parseXML(o.settings.dataRaw):"json"===c?Array.isArray&&Array.isArray(o.settings.dataRaw)?o.settings.dataRaw:"string"==typeof o.settings.dataRaw?JSON.parse(o.settings.dataRaw):[]:void 0:(a=B.Deferred(),!0===this.settings.loading&&B("."+this.settings.formContainer).append('
'),i={origLat:t,origLng:e,origAddress:s,formattedAddress:g,boundsNorthEast:r,boundsSouthWest:l},null!==this.settings.ajaxData&&"object"==typeof this.settings.ajaxData&&B.extend(i,this.settings.ajaxData),B.ajax({type:"GET",url:this.settings.dataLocation+("jsonp"===this.settings.dataType?(this.settings.dataLocation.match(/\?/)?"&":"?")+"callback=?":""),data:i,dataType:c,jsonpCallback:"jsonp"===this.settings.dataType?this.settings.callbackJsonp:null}).done(function(t){a.resolve(t),!0===o.settings.loading&&B("."+o.settings.formContainer+" ."+o.settings.loadingContainer).remove()}).fail(a.reject),a.promise())},_start:function(){this.writeDebug("_start");var t,e,s=this,i=this.settings.autoGeocode;!1!==s.settings.fullMapStartBlank?(B("#"+s.settings.mapID).addClass("bh-sl-map-open"),(e=s.settings.mapSettings).zoom=s.settings.fullMapStartBlank,t=new google.maps.LatLng(this.settings.defaultLat,this.settings.defaultLng),e.center=t,s.map=new google.maps.Map(j.getElementById(s.settings.mapID),e),N.addEventListener("resize",function(){var t=s.map.getCenter();google.maps.event.trigger(s.map,"resize"),s.map.setCenter(t)}),s.settings.fullMapStartBlank=!1,e.zoom=V):(!0===this.settings.defaultLoc&&this.defaultLocation(),""!==B.trim(B("#"+this.settings.addressID).val())?(s.writeDebug("Using Address Field"),s.processForm(null),i=!1):!0===this.settings.fullMapStart&&!1===this.settings.defaultLoc&&(!0===this.settings.querystringParams&&this.getQueryString(this.settings.addressID)||!0===this.settings.querystringParams&&this.getQueryString(this.settings.searchID)||!0===this.settings.querystringParams&&this.getQueryString(this.settings.maxDistanceID)?(s.writeDebug("Using Query String"),this.processForm(null),i=!1):this.mapping(null))),!0===this.settings.autoGeocode&&!0===i&&(s.writeDebug("Auto Geo"),s.htmlGeocode()),null!==this.settings.autoGeocode&&(s.writeDebug("Button Geo"),B(j).on("click."+nt,"#"+this.settings.geocodeID,function(){s.htmlGeocode()}))},htmlGeocode:function(){this.writeDebug("htmlGeocode",arguments);var e=this;if(!0===e.settings.sessionStorage&&N.sessionStorage&&N.sessionStorage.getItem("myGeo"))return e.writeDebug("Using Session Saved Values for GEO"),e.autoGeocodeQuery(JSON.parse(N.sessionStorage.getItem("myGeo"))),!1;navigator.geolocation&&navigator.geolocation.getCurrentPosition(function(t){e.writeDebug("Current Position Result");t={coords:{latitude:t.coords.latitude,longitude:t.coords.longitude,accuracy:t.coords.accuracy}};!0===e.settings.sessionStorage&&N.sessionStorage&&N.sessionStorage.setItem("myGeo",JSON.stringify(t)),e.settings.callbackAutoGeoSuccess&&e.settings.callbackAutoGeoSuccess.call(this,t),e.autoGeocodeQuery(t)},function(t){e._autoGeocodeError(t)})},googleGeocode:function(t){t.writeDebug("googleGeocode",arguments);var e=new google.maps.Geocoder;this.geocode=function(t,s){e.geocode(t,function(t,e){if(e!==google.maps.GeocoderStatus.OK)throw s(null),new Error("Geocode was not successful for the following reason: "+e);e={};e.latitude=t[0].geometry.location.lat(),e.longitude=t[0].geometry.location.lng(),e.geocodeResult=t[0],s(e)})}},reverseGoogleGeocode:function(t){t.writeDebug("reverseGoogleGeocode",arguments);var e=new google.maps.Geocoder;this.geocode=function(t,s){e.geocode(t,function(t,e){if(e!==google.maps.GeocoderStatus.OK)throw s(null),new Error("Reverse geocode was not successful for the following reason: "+e);t[0]&&((e={}).address=t[0].formatted_address,e.fullResult=t[0],s(e))})}},roundNumber:function(t,e){return this.writeDebug("roundNumber",arguments),Math.round(t*Math.pow(10,e))/Math.pow(10,e)},isEmptyObject:function(t){for(var e in this.writeDebug("isEmptyObject",arguments),t)if(t.hasOwnProperty(e))return!1;return!0},hasEmptyObjectVals:function(t){this.writeDebug("hasEmptyObjectVals",arguments);var e,s=!0;for(e in t)t.hasOwnProperty(e)&&""!==t[e]&&0!==t[e].length&&(s=!1);return s},hasSingleGroupFilterVal:function(t,e){this.writeDebug("hasSingleGroupFilterVal",arguments);t=Object.assign({},t);return!this.hasEmptyObjectVals(t[e])},modalClose:function(){this.writeDebug("modalClose"),this.settings.callbackModalClose&&this.settings.callbackModalClose.call(this),st={},B("."+this.settings.overlay+" select").prop("selectedIndex",0),B("."+this.settings.overlay+" input").prop("checked",!1),B("."+this.settings.overlay).hide()},_createLocationVariables:function(t){var e,s;for(s in this.writeDebug("_createLocationVariables",arguments),r={},Y[t])Y[t].hasOwnProperty(s)&&(e=Y[t][s],"distance"!==s&&"altdistance"!==s||(e=this.roundNumber(e,2)),r[s]=e)},sortAlpha:function(t){this.writeDebug("sortAlpha",arguments);var s=this.settings.sortBy.hasOwnProperty("prop")&&void 0!==this.settings.sortBy.prop?this.settings.sortBy.prop:"name";this.settings.sortBy.hasOwnProperty("order")&&"desc"===this.settings.sortBy.order.toString()?t.sort(function(t,e){return e[s].toLowerCase().localeCompare(t[s].toLowerCase())}):t.sort(function(t,e){return t[s].toLowerCase().localeCompare(e[s].toLowerCase())})},sortDate:function(t){this.writeDebug("sortDate",arguments);var s=this.settings.sortBy.hasOwnProperty("prop")&&void 0!==this.settings.sortBy.prop?this.settings.sortBy.prop:"date";this.settings.sortBy.hasOwnProperty("order")&&"desc"===this.settings.sortBy.order.toString()?t.sort(function(t,e){return new Date(e[s]).getTime()-new Date(t[s]).getTime()}):t.sort(function(t,e){return new Date(t[s]).getTime()-new Date(e[s]).getTime()})},sortNumerically:function(t,e){this.writeDebug("sortNumerically",arguments);var s=null!==this.settings.sortBy&&this.settings.sortBy.hasOwnProperty("prop")&&void 0!==this.settings.sortBy.prop?this.settings.sortBy.prop:"distance";void 0!==e&&!0===e&&(s="distance"),null!==this.settings.sortBy&&this.settings.sortBy.hasOwnProperty("order")&&"desc"===this.settings.sortBy.order.toString()?t.sort(function(t,e){return e[s]
t[s]?1:0}):t.sort(function(t,e){return t[s]e[s]?1:0})},sortCustom:function(t){this.writeDebug("sortCustom",arguments),this.settings.sortBy.hasOwnProperty("method")&&"alpha"===this.settings.sortBy.method.toString()?this.sortAlpha(t):this.settings.sortBy.hasOwnProperty("method")&&"date"===this.settings.sortBy.method.toString()?this.sortDate(t):this.sortNumerically(t)},filterMatching:function(t,e,s){return this.writeDebug("inclusiveFilter",arguments),void 0!==e&&(s=!0===(s=void 0===s||s)?t.join(""):t.join("|"),!!new RegExp(s,"i").test(e.replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g,"")))},filterData:function(t,e){this.writeDebug("filterData",arguments);var s,i=!0;for(s in e)if(e.hasOwnProperty(s)){for(var n=[],a=0;a')+''+this.settings.prevPage+" "),5<=t+1&&51 … ');for(var g=s;g')+''+c+" ":(i+='')+''+c+" ";return t+o<=e&&5… ')+'')+''+e+" "),i=n')+''+this.settings.nextPage+" ":i},paginationReset:function(){this.writeDebug("paginationReset",arguments);var t=N.location.href,t=new URL(t);t.searchParams.delete("bhsl-page"),history.pushState&&N.history.pushState({path:t.href},"",t.href)},totalPages:function(){return this.writeDebug("totalPages",arguments),!(void 0!==z&&0this.settings.storeLimit)&&(-1===this.settings.storeLimit||Y.length ul").append(e)},changeSelectedMarker:function(t){var e;void 0!==s&&s.setIcon(f),e=null===this.settings.selectedMarkerImgDim?this.markerImage(this.settings.selectedMarkerImg):this.markerImage(this.settings.selectedMarkerImg,this.settings.selectedMarkerImgDim.width,this.settings.selectedMarkerImgDim.height),f=t.icon,t.setIcon(e),s=t},createInfowindow:function(n,t,a,e,s){this.writeDebug("createInfowindow",arguments);var o=this,e=this._defineLocationData(n,e,s),r=i(e);"left"===t?(a.setContent(r),this.useLegacyMarkers()?a.open(n.get("map"),n):a.open(n.map,n)):this.useLegacyMarkers()?google.maps.event.addListener(n,"click",function(){a.setContent(r),a.open(n.get("map"),n);var t=n.get("id"),e=B("."+o.settings.locationList+" li[data-markerid="+t+"]");0
')),B(j).off("click","."+this.settings.locationList+" li .loc-directions a")},closeDirections:function(){this.writeDebug("closeDirections"),this.settings.callbackCloseDirections&&this.settings.callbackCloseDirections.call(this),this.reset(),g&&h&&(0===this.countFilters()?this.settings.mapSettings.zoom=V:this.settings.mapSettings.zoom=0,this.processForm(null)),B(j).off("click."+nt,"."+this.settings.locationList+" .bh-sl-close-icon")},lengthUnitSwap:function(t){this.writeDebug("lengthUnitSwap",arguments),"alt-distance"===t.val()?(B("."+this.settings.locationList+" .loc-alt-dist").show(),B("."+this.settings.locationList+" .loc-default-dist").hide()):"default-distance"===t.val()&&(B("."+this.settings.locationList+" .loc-default-dist").show(),B("."+this.settings.locationList+" .loc-alt-dist").hide())},processForm:function(t){this.writeDebug("processForm",arguments);var e=this,s=null,i={},n=B("#"+this.settings.addressID),a=B("#"+this.settings.searchID),o=B("#"+this.settings.maxDistanceID),r="";null!=t&&t.preventDefault(),B("."+e.settings.formContainer+" input, ."+e.settings.formContainer+" select").blur(),!0===this.settings.querystringParams&&(this.getQueryString(this.settings.addressID)||this.getQueryString(this.settings.searchID)||this.getQueryString(this.settings.maxDistanceID))?(K=this.getQueryString(this.settings.addressID),U=this.getQueryString(this.settings.searchID),(s=this.getQueryString(this.settings.maxDistanceID))&&B("#"+this.settings.maxDistanceID+" option[value="+s+"]").length&&o.val(s),K&&s&&(e.settings.mapSettings.zoom=0),""!==n.val()&&(K=n.val()),""!==a.val()&&(U=a.val()),""!==o.val()&&(s=o.val())):(K=n.val()||"",U=a.val()||"",!0===this.settings.maxDistance&&(s=o.val()||"")),r=this.settings.callbackRegion?this.settings.callbackRegion.call(this,K,U,s):B("#"+this.settings.regionID).val(),this.settings.callbackFormVals&&this.settings.callbackFormVals.call(this,K,U,s,r),void 0!==r&&(i={country:r}),"function"==typeof this.settings.callbackGeocodeRestrictions&&(i=this.settings.callbackGeocodeRestrictions.call(this,K,U,s)),""===K&&""===U&&!0!==this.settings.autoGeocode?this._start():""!==K?(""===U&&st.hasOwnProperty("name")&&delete st.name,void 0!==z&&void 0!==g&&void 0!==h&&K===z?(it.lat=g,it.lng=h,it.origin=K,it.name=U,it.distance=s,e.mapping(it)):new this.googleGeocode(this).geocode({address:K,componentRestrictions:i,region:r},function(t){null!==t?(g=t.latitude,h=t.longitude,it.lat=g,it.lng=h,it.origin=K,it.name=U,it.distance=s,it.geocodeResult=t.geocodeResult,e.mapping(it)):e.notify(e.settings.addressErrorAlert)})):""!==U?(""===K&&delete it.origin,it.name=U,e.mapping(it)):!0===this.settings.autoGeocode&&(it.lat=g,it.lng=h,it.origin=K,it.name=U,it.distance=s,e.mapping(it)),void 0!==z&&K!==z&&this.paginationReset()},locationsSetup:function(t,e,s,i,n){this.writeDebug("locationsSetup",arguments),void 0===i||t.distance||(t.distance=this.geoCodeCalcCalcDistance(e,s,t.lat,t.lng,l.EarthRadius),"m"===this.settings.lengthUnit?t.altdistance=1.609344*parseFloat(t.distance):"km"===this.settings.lengthUnit&&(t.altdistance=parseFloat(t.distance)/1.609344)),this.coordinatesInRange(t.lat,t.lng)?!0===this.settings.maxDistance&&null!=n?t.distance<=n?Y.push(t):this.writeDebug("locationsSetup","location ignored because it is out of maxDistance: "+n,t):!0!==this.settings.maxDistance||!0!==this.settings.querystringParams||null==n||t.distance<=n?Y.push(t):this.writeDebug("locationsSetup","location ignored because it is out of maxDistance: "+n,t):this.writeDebug("locationsSetup","location ignored because coordinates out of range: "+n,t)},sorting:function(){this.writeDebug("sorting",arguments);var s=this,i=B("#"+s.settings.mapID),t=B("#"+s.settings.sortID);0!==t.length&&t.on("change."+nt,function(t){var e;t.stopPropagation(),!0===s.settings.pagination&&s.paginationChange(0),t=void 0!==B(this).find(":selected").attr("data-method")?B(this).find(":selected").attr("data-method"):"distance",e=B(this).val(),s.settings.sortBy.method=t,s.settings.sortBy.prop=e,s.settings.callbackSorting&&s.settings.callbackSorting.call(this,s.settings.sortBy),i.hasClass("bh-sl-map-open")&&s.mapping(it)})},order:function(){this.writeDebug("order",arguments);var e=this,s=B("#"+e.settings.mapID),t=B("#"+e.settings.orderID);0!==t.length&&t.on("change."+nt,function(t){t.stopPropagation(),!0===e.settings.pagination&&e.paginationChange(0),e.settings.sortBy.order=B(this).val(),e.settings.callbackOrder&&e.settings.callbackOrder.call(this,e.settings.order),s.hasClass("bh-sl-map-open")&&e.mapping(it)})},distanceFiltering:function(){this.writeDebug("distanceFiltering");var e=this;B("#"+this.settings.maxDistanceID).on("change."+nt,function(t){t.stopPropagation(),!0===e.settings.querystringParams&&(t=N.location.href,(t=new URL(t)).searchParams.set(e.settings.maxDistanceID,this.value),history.pushState?N.history.pushState({path:t.href},"",t.href):N.location.replace(t.href)),!0===B("#"+e.settings.mapID).hasClass("bh-sl-map-open")&&(g&&h?(e.settings.mapSettings.zoom=0,e.processForm()):e.mapping(it))})},countFilters:function(){this.writeDebug("countFilters");var t=0;if(!this.isEmptyObject(st))for(var e in st)st.hasOwnProperty(e)&&(t+=st[e].length);return t},_existingCheckedFilters:function(e){this.writeDebug("_existingCheckedFilters",arguments),B("#"+this.settings.taxonomyFilters[e]+" input[type=checkbox]").each(function(){var t;B(this).prop("checked")&&void 0!==(t=B(this).val())&&""!==t&&-1===st[e].indexOf(t)&&st[e].push(t)})},_existingSelectedFilters:function(e){this.writeDebug("_existingSelectedFilters",arguments),B("#"+this.settings.taxonomyFilters[e]+" select").each(function(){var t=B(this).val();void 0!==t&&""!==t&&-1===st[e].indexOf(t)&&(st[e]=[t])})},_existingRadioFilters:function(e){this.writeDebug("_existingRadioFilters",arguments),B("#"+this.settings.taxonomyFilters[e]+" input[type=radio]").each(function(){var t;B(this).prop("checked")&&void 0!==(t=B(this).val())&&""!==t&&-1===st[e].indexOf(t)&&(st[e]=[t])})},checkFilters:function(){for(var t in this.writeDebug("checkFilters"),this.settings.taxonomyFilters)this.settings.taxonomyFilters.hasOwnProperty(t)&&(this._existingCheckedFilters(t),this._existingSelectedFilters(t),this._existingRadioFilters(t))},selectQueryStringFilters:function(t,e){this.writeDebug("selectQueryStringFilters",arguments);var s=B("#"+this.settings.taxonomyFilters[t]);if(s.find('input[type="checkbox"]').length)for(var i=0;i
ul").append(i)):s.getBounds().contains(e.position)&&(n.listSetup(e,0,0),i=a(void 0),B("."+n.settings.locationList+" > ul").append(i))}),B("."+this.settings.locationList+" ul li:even").css("background",this.settings.listColor1),B("."+this.settings.locationList+" ul li:odd").css("background",this.settings.listColor2)},dragSearch:function(t){this.writeDebug("dragSearch",arguments);var e=t.getCenter(),s=this,t=(this.settings.mapSettings.zoom=t.getZoom(),g=it.lat=e.lat(),h=it.lng=e.lng(),new this.reverseGoogleGeocode(this)),e=new google.maps.LatLng(it.lat,it.lng);t.geocode({latLng:e},function(t){null!==t?(it.origin=K=t.address,s.mapping(it)):s.notify(s.settings.addressErrorAlert)})},emptyResult:function(){this.writeDebug("emptyResult",arguments);var t=B("."+this.settings.locationList+" ul"),e=this.settings.mapSettings;this.map=new google.maps.Map(j.getElementById(this.settings.mapID),e),this.settings.callbackNoResults&&this.settings.callbackNoResults.call(this,this.map,e),t.empty(),e=B(''+this.settings.noResultsTitle+'
'+this.settings.noResultsDesc+"").hide().fadeIn(),t.append(e),t=g&&h?new google.maps.LatLng(g,h):new google.maps.LatLng(0,0),this.map.setCenter(t),V&&this.map.setZoom(V)},originMarker:function(t,e,s){var i;this.writeDebug("originMarker",arguments),!0===this.settings.originMarker&&void 0!==e&&(this.useLegacyMarkers()?(i=null!==this.settings.originMarkerImg?null===this.settings.originMarkerDim?this.markerImage(this.settings.originMarkerImg):this.markerImage(this.settings.originMarkerImg,this.settings.originMarkerDim.width,this.settings.originMarkerDim.height):{url:"https://mt.googleapis.com/vt/icon/name=icons/spotlight/spotlight-waypoint-a.png"},new google.maps.Marker({position:s,map:t,icon:i,draggable:!1})):(e=new google.maps.marker.PinElement({background:"#39b25e",borderColor:"#177d3d",glyphColor:"#177d3c"}),e=new google.maps.marker.AdvancedMarkerElement({content:e.element,draggable:!1,map:t,position:s,title:name}),null!==this.settings.originMarkerImg&&(i=j.createElement("img"),null===this.settings.originMarkerDim?i.src=this.settings.originMarkerImg:i=this.markerImage(this.settings.originMarkerImg,this.settings.originMarkerDim.width,this.settings.originMarkerDim.height),e.content=i)))},modalWindow:function(){var e;this.writeDebug("modalWindow"),!0===this.settings.modal&&((e=this).settings.callbackModalOpen&&e.settings.callbackModalOpen.call(this),B("."+e.settings.overlay).fadeIn(),B(j).on("click."+nt,"."+e.settings.closeIcon+", ."+e.settings.overlay,function(){e.modalClose()}),B(j).on("click."+nt,"."+e.settings.modalWindow,function(t){t.stopPropagation()}),B(j).on("keyup."+nt,function(t){27===t.keyCode&&e.modalClose()}))},openNearestLocation:function(t,e,s,i){var n,a;this.writeDebug("openNearestLocation",arguments),!0!==this.settings.openNearest||void 0===t||void 0===z||!0===this.settings.fullMapStart&&!0===W&&!1===this.settings.querystringParams||!0===this.settings.defaultLoc&&!0===W&&!1===this.settings.querystringParams||((n=this).settings.callbackNearestLoc&&n.settings.callbackNearestLoc.call(this,n.map,t,e,s,i),t=t.hasOwnProperty("markerid")?t.markerid:0,a=et[t],n.createInfowindow(a,"left",e,s,i),a=B("."+n.settings.locationList),e=B("."+n.settings.locationList+" li[data-markerid="+t+"]"),B("."+n.settings.locationList+" li").removeClass("list-focus"),e.addClass("list-focus"),a.animate({scrollTop:e.offset().top-a.offset().top+a.scrollTop()}))},listClick:function(s,i,n,a){this.writeDebug("listClick",arguments);var o=this;B(j).on("click."+nt,"."+o.settings.locationList+" li",function(){var t=B(this).data("markerid"),e=et[t];o.settings.callbackListClick&&o.settings.callbackListClick.call(this,t,e,Y[t],s),o.useLegacyMarkers()?s.panTo(e.getPosition()):s.panTo(e.position);o.createInfowindow(e,"left",i,n,a),null!==o.settings.selectedMarkerImg&&o.changeSelectedMarker(e),B("."+o.settings.locationList+" li").removeClass("list-focus"),B("."+o.settings.locationList+" li[data-markerid="+t+"]").addClass("list-focus")}),B(j).on("click."+nt,"."+o.settings.locationList+" li a",function(t){t.stopPropagation()})},resultsTotalCount:function(t){this.writeDebug("resultsTotalCount",arguments);var e=B(".bh-sl-total-results");void 0===t||t<=0||0===e.length||e.text(t)},inlineDirections:function(e,s){var i;this.writeDebug("inlineDirections",arguments),!0===this.settings.inlineDirections&&void 0!==s&&(i=this,B(j).on("click."+nt,"."+i.settings.locationList+" li .loc-directions a",function(t){t.preventDefault();t=B(this).closest("li").attr("data-markerid");i.directionsRequest(s,parseInt(t),e),B(j).on("click."+nt,"."+i.settings.locationList+" .bh-sl-close-icon",function(){i.closeDirections()})}))},visibleMarkersList:function(t,e){var s;this.writeDebug("visibleMarkersList",arguments),!0===this.settings.visibleMarkersList&&(s=this,google.maps.event.addListenerOnce(t,"idle",function(){s.checkVisibleMarkers(e,t)}),google.maps.event.addListener(t,"center_changed",function(){s.checkVisibleMarkers(e,t)}),google.maps.event.addListener(t,"zoom_changed",function(){s.checkVisibleMarkers(e,t)}))},featuredDistanceRestriction:function(){this.writeDebug("featuredDistanceRestriction",arguments);var e=this;return X=B.grep(X,function(t){if(t.hasOwnProperty("distance"))return parseFloat(t.distance)<=parseFloat(e.settings.featuredDistance)})},featuredRestrictions:function(t){return this.writeDebug("featuredRestrictions",arguments),X=null!==this.settings.featuredDistance&&null!==this.settings.featuredDistance?this.featuredDistanceRestriction(t):X},mapping:function(e){this.writeDebug("mapping",arguments);var t,s,i,n,a,o,r=this,l=(this.isEmptyObject(e)||(t=e.lat,s=e.lng,i=e.geocodeResult,n=e.origin,o=e.page),!0===r.settings.pagination&&(Z=o=void 0!==o&&z===K?o:0),r.getQueryString("bhsl-page"));""!==l&&(o=Z=parseInt(l)-1),q=void 0===n&&!0===this.settings.nameSearch?r._getData():(a=new google.maps.LatLng(t,s),void 0!==z&&n===z&&void 0!==Q&&!0!==this.settings.pagination?(n=z,Q):r._getData(g,h,n,i,e)),null!==r.settings.taxonomyFilters&&r.hasEmptyObjectVals(st)&&r.checkFilters(),null!==r.settings.dataRaw?r.processData(e,a,q,o):q.done(function(t){r.processData(e,a,t,o)})},resetDisabledFilterVals:function(){for(var t in this.writeDebug("resetDisabledFilterVals"),this.settings.taxonomyFilters)if(this.settings.taxonomyFilters.hasOwnProperty(t))for(var e=0;e
r)&&p.notify(p.settings.distanceErrorAlert+r+" "+v);else{if(void 0===Y[0])throw p.emptyResult(),new Error("No locations found. Please check the dataLocation setting and path.");-1!==p.settings.distanceAlert&&Y[0].distance>p.settings.distanceAlert&&(void 0===Z||1===parseInt(Z))&&(p.notify(p.settings.distanceErrorAlert+p.settings.distanceAlert+" "+v),h=!0)}void 0!==Y[0]&&(d=Y[0])}if(!0===p.settings.featuredLocations&&(X=B.grep(Y,function(t){if(t.hasOwnProperty("featured"))return"true"===t.featured}),X=p.featuredRestrictions(t),tt=B.grep(Y,function(t){if(t.hasOwnProperty("featured"))return"true"!==t.featured}),Y=[],Y=X.concat(tt)),(!0!==W&&!1===p.settings.exclusiveFiltering||!0===p.settings.fullMapStart&&!1===p.settings.exclusiveFiltering||!0===p.settings.defaultLoc&&!1===p.settings.exclusiveFiltering)&&p.maybeDisableFilterOptions(),!0===p.settings.slideMap&&_.slideDown(),!0===p.settings.pagination&&p.paginationSetup(i),!0===p.settings.altDistanceNoResult&&d.distance>p.settings.distanceAlert)p.emptyResult();else if(p.isEmptyObject(Y)||"none"===Y[0].result)p.emptyResult();else{p.modalWindow(),H=-1===p.settings.storeLimit||Y.lengthY.length&&(c=p.settings.locationsPerPage-(g+c-Y.length)),Y=Y.slice(g,g+c),H=Y.length):(c=H,g=0),p.resultsTotalCount(Y.length),!0===p.settings.fullMapStart&&!0===W&&!0!==p.settings.querystringParams||0===p.settings.mapSettings.zoom||void 0===o||!0===h||!0===p.settings.maxDistance&&!1===W&&0p.settings.fullMapStartListLimit)for(var G=0;G li:even").css("background",p.settings.listColor1),B("."+p.settings.locationList+" ul > li:odd").css("background",p.settings.listColor2),p.visibleMarkersList(p.map,et),!0===p.settings.querystringParams&&(F=B("#"+p.settings.addressID),P=B("#"+p.settings.searchID),void 0!==it&&it.hasOwnProperty("origin")&&""===F.val()&&F.val(it.origin),void 0!==it)&&it.hasOwnProperty("name")&&""===P.val()&&P.val(it.name),!0===p.settings.modal&&p.settings.callbackModalReady&&p.settings.callbackModalReady.call(this,t),p.settings.callbackFilters&&p.settings.callbackFilters.call(this,st,t)}},writeDebug:function(){N.console&&this.settings.debug&&(Function.prototype.bind?this.writeDebug=Function.prototype.bind.call(console.log,console,"StoreLocator :"):this.writeDebug=function(){arguments[0]="StoreLocator : "+arguments[0],Function.prototype.apply.call(console.log,console,arguments)},this.writeDebug.apply(this,arguments))}}),B.fn[nt]=function(e){var s,i=arguments;return e===t||"object"==typeof e?this.each(function(){B.data(this,"plugin_"+nt)||B.data(this,"plugin_"+nt,new p(this,e))}):"string"==typeof e&&"_"!==e[0]&&"init"!==e?(this.each(function(){var t=B.data(this,"plugin_"+nt);t instanceof p&&"function"==typeof t[e]&&(s=t[e].apply(t,Array.prototype.slice.call(i,1))),"destroy"===e&&B.data(this,"plugin_"+nt,null)}),s!==t?s:this):void 0})}(jQuery,window,document);
\ No newline at end of file
diff --git a/dist/assets/js/plugins/storeLocator/templates/infowindow-description.html b/dist/assets/js/plugins/storeLocator/templates/infowindow-description.html
new file mode 100644
index 0000000..5981a26
--- /dev/null
+++ b/dist/assets/js/plugins/storeLocator/templates/infowindow-description.html
@@ -0,0 +1,21 @@
+{{#location}}
+{{name}}
+{{address}}
+{{#if address2}}
+ {{address2}}
+{{/if}}
+{{city}}{{#if city}},{{/if}} {{state}} {{postal}}
+{{#if hours1}}
+ {{hours1}}
+{{/if}}
+{{#if hours2}}
+ {{hours2}}
+{{/if}}
+{{#if hours3}}
+ {{hours3}}
+{{/if}}
+{{#if phone}}
+ {{phone}}
+{{/if}}
+
+{{/location}}
diff --git a/dist/assets/js/plugins/storeLocator/templates/kml-infowindow-description.html b/dist/assets/js/plugins/storeLocator/templates/kml-infowindow-description.html
new file mode 100644
index 0000000..afababf
--- /dev/null
+++ b/dist/assets/js/plugins/storeLocator/templates/kml-infowindow-description.html
@@ -0,0 +1,6 @@
+{{#location}}
+{{name}}
+{{#if description}}
+ {{{description}}}
+{{/if}}
+{{/location}}
diff --git a/templates/kml-location-list-description.html b/dist/assets/js/plugins/storeLocator/templates/kml-location-list-description.html
similarity index 72%
rename from templates/kml-location-list-description.html
rename to dist/assets/js/plugins/storeLocator/templates/kml-location-list-description.html
index 17fe9bd..aeaa7ec 100644
--- a/templates/kml-location-list-description.html
+++ b/dist/assets/js/plugins/storeLocator/templates/kml-location-list-description.html
@@ -4,8 +4,10 @@
{{name}}
-
{{{description}}}
+ {{#if description}}
+
{{{description}}}
+ {{/if}}
-{{/location}}
\ No newline at end of file
+{{/location}}
diff --git a/dist/assets/js/plugins/storeLocator/templates/location-list-description.html b/dist/assets/js/plugins/storeLocator/templates/location-list-description.html
new file mode 100644
index 0000000..ab1f455
--- /dev/null
+++ b/dist/assets/js/plugins/storeLocator/templates/location-list-description.html
@@ -0,0 +1,26 @@
+{{#location}}
+
+ {{marker}}
+
+
+
{{name}}
+
{{address}}
+ {{#if address2}}
+
{{address2}}
+ {{/if}}
+
{{city}}{{#if city}},{{/if}} {{state}} {{postal}}
+ {{#if phone}}
+
{{phone}}
+ {{/if}}
+ {{#if web}}
+
+ {{/if}}
+ {{#if distance}}
+
{{distance}} {{length}}
+ {{#if altdistance}}
{{altdistance}} {{altlength}}
{{/if}}
+
+ {{/if}}
+
+
+
+{{/location}}
diff --git a/dist/autocomplete-example.html b/dist/autocomplete-example.html
new file mode 100644
index 0000000..d94d30f
--- /dev/null
+++ b/dist/autocomplete-example.html
@@ -0,0 +1,52 @@
+
+
+
+ Autocomplete Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/autogeocode-example.html b/dist/autogeocode-example.html
new file mode 100755
index 0000000..3249ebc
--- /dev/null
+++ b/dist/autogeocode-example.html
@@ -0,0 +1,52 @@
+
+
+
+ Map Example - Auto geocoding
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/bootstrap-example.html b/dist/bootstrap-example.html
new file mode 100644
index 0000000..0a2c3c4
--- /dev/null
+++ b/dist/bootstrap-example.html
@@ -0,0 +1,75 @@
+
+
+
+ Map Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Using Chipotle as an Example
+
I used locations around Minneapolis and the southwest suburbs. So, for example, Edina, Plymouth, Eden
+ Prarie, etc. would be good for testing the functionality. You can use just the city as the address - ex:
+ Edina, MN.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/categories-example.html b/dist/categories-example.html
new file mode 100644
index 0000000..36a5d03
--- /dev/null
+++ b/dist/categories-example.html
@@ -0,0 +1,166 @@
+
+
+
+ Map Example - Categories
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/category-markers-example.html b/dist/category-markers-example.html
new file mode 100644
index 0000000..306518c
--- /dev/null
+++ b/dist/category-markers-example.html
@@ -0,0 +1,55 @@
+
+
+
+ Map Example - Category Markers
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/cluster-example.html b/dist/cluster-example.html
new file mode 100644
index 0000000..5df6df5
--- /dev/null
+++ b/dist/cluster-example.html
@@ -0,0 +1,53 @@
+
+
+
+ Cluster Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/data/locations.json b/dist/data/locations.json
new file mode 100755
index 0000000..8eda7cd
--- /dev/null
+++ b/dist/data/locations.json
@@ -0,0 +1,422 @@
+[
+ {
+ "id": "1",
+ "name": "Chipotle Minneapolis",
+ "lat": "44.947464",
+ "lng": "-93.320826",
+ "category": "Restaurant",
+ "address": "3040 Excelsior Blvd",
+ "address2": "",
+ "city": "Minneapolis",
+ "state": "MN",
+ "postal": "55416",
+ "phone": "612-922-6662",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "",
+ "date": "10/17/18"
+ },
+ {
+ "id": "2",
+ "name": "Chipotle St. Louis Park",
+ "lat": "44.930810",
+ "lng": "-93.347877",
+ "category": "Restaurant",
+ "address": "5480 Excelsior Blvd.",
+ "address2": "",
+ "city": "St. Louis Park",
+ "state": "MN",
+ "postal": "55416",
+ "phone": "952-922-1970",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Online Ordering",
+ "date": "09/17/18"
+ },
+ {
+ "id": "3",
+ "name": "Chipotle Minneapolis",
+ "lat": "44.9553438",
+ "lng": "-93.29719699999998",
+ "category": "Restaurant, Bar",
+ "address": "2600 Hennepin Ave.",
+ "address2": "",
+ "city": "Minneapolis",
+ "state": "MN",
+ "postal": "55404",
+ "phone": "612-377-6035",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "true",
+ "features": "Online Ordering, Fresh Guacamole",
+ "date": "08/17/18"
+ },
+ {
+ "id": "4",
+ "name": "Chipotle Golden Valley",
+ "lat": "44.983935",
+ "lng": "-93.380542",
+ "category": "Restaurant, Something & Sons",
+ "address": "515 Winnetka Ave. N",
+ "address2": "",
+ "city": "Golden Valley",
+ "state": "MN",
+ "postal": "55427",
+ "phone": "763-544-2530",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Online Ordering",
+ "date": "07/17/18"
+ },
+ {
+ "id": "5",
+ "name": "Chipotle Hopkins",
+ "lat": "44.924363",
+ "lng": "-93.410158",
+ "category": "Cafe",
+ "address": "786 Mainstreet",
+ "address2": "",
+ "city": "Hopkins",
+ "state": "MN",
+ "postal": "55343",
+ "phone": "952-935-0044",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Margaritas",
+ "date": "06/17/18"
+ },
+ {
+ "id": "6",
+ "name": "Chipotle Minneapolis",
+ "lat": "44.973557",
+ "lng": "-93.275111",
+ "category": "Restaurant",
+ "address": "1040 Nicollet Ave",
+ "address2": "",
+ "city": "Minneapolis",
+ "state": "MN",
+ "postal": "55403",
+ "phone": "612-659-7955",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Online Ordering, Margaritas",
+ "date": "05/17/18"
+ },
+ {
+ "id": "7",
+ "name": "Chipotle Minneapolis",
+ "lat": "44.97774",
+ "lng": "-93.270909",
+ "category": "Restaurant, Cafe",
+ "address": "50 South 6th",
+ "address2": "",
+ "city": "Minneapolis",
+ "state": "MN",
+ "postal": "55402",
+ "phone": "612-333-0434",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Fresh Guacamole",
+ "date": "04/17/18"
+ },
+ {
+ "id": "8",
+ "name": "Chipotle Edina",
+ "lat": "44.879826",
+ "lng": "-93.321280",
+ "category": "Restaurant",
+ "address": "6801 York Avenue South",
+ "address2": "",
+ "city": "Edina",
+ "state": "MN",
+ "postal": "55435",
+ "phone": "952-926-6651",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Online Ordering, Fresh Guacamole",
+ "date": "03/17/18"
+ },
+ {
+ "id": "9",
+ "name": "Chipotle Minnetonka",
+ "lat": "44.970495",
+ "lng": "-93.437430",
+ "category": "Restaurant",
+ "address": "12509 Wayzata Blvd",
+ "address2": "",
+ "city": "Minnetonka",
+ "state": "MN",
+ "postal": "55305",
+ "phone": "952-252-4900",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Margaritas",
+ "date": "02/17/18"
+ },
+ {
+ "id": "10",
+ "name": "Chipotle Minneapolis",
+ "lat": "44.972808",
+ "lng": "-93.247153",
+ "category": "Restaurant, Coffee",
+ "address": "229 Cedar Ave S",
+ "address2": "",
+ "city": "Minneapolis",
+ "state": "MN",
+ "postal": "55454",
+ "phone": "612-659-7830",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "",
+ "date": "01/17/18"
+ },
+ {
+ "id": "11",
+ "name": "Chipotle Minneapolis",
+ "lat": "44.987687",
+ "lng": "-93.257581",
+ "category": "Restaurant",
+ "address": "225 Hennepin Ave E",
+ "address2": "",
+ "city": "Minneapolis",
+ "state": "MN",
+ "postal": "55414",
+ "phone": "612-331-6330",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Fresh Guacamole",
+ "date": "12/17/17"
+ },
+ {
+ "id": "12",
+ "name": "Chipotle Minneapolis",
+ "lat": "44.973665",
+ "lng": "-93.227023",
+ "category": "Restaurant",
+ "address": "800 Washington Ave SE",
+ "address2": "",
+ "city": "Minneapolis",
+ "state": "MN",
+ "postal": "55414",
+ "phone": "612-378-7078",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "",
+ "date": "11/17/17"
+ },
+ {
+ "id": "13",
+ "name": "Chipotle Bloomington",
+ "lat": "44.8458631",
+ "lng": "-93.2860161",
+ "category": "Restaurant",
+ "address": "322 South Ave",
+ "address2": "",
+ "city": "Bloomington",
+ "state": "MN",
+ "postal": "55425",
+ "phone": "952-252-3800",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Online Ordering",
+ "date": "10/17/17"
+ },
+ {
+ "id": "14",
+ "name": "Chipotle Wayzata",
+ "lat": "44.9716626",
+ "lng": "-93.4967757",
+ "category": "Restaurant",
+ "address": "1313 Wayzata Blvd",
+ "address2": "",
+ "city": "Wayzata",
+ "state": "MN",
+ "postal": "55391",
+ "phone": "952-473-7100",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Margaritas",
+ "date": "09/17/17"
+ },
+ {
+ "id": "15",
+ "name": "Chipotle Eden Prairie",
+ "lat": "44.859761",
+ "lng": "-93.436379",
+ "category": "Restaurant, Bar",
+ "address": "13250 Technology Dr",
+ "address2": "",
+ "city": "Eden Prairie",
+ "state": "MN",
+ "postal": "55344",
+ "phone": "952-934-5955",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "true",
+ "features": "Online Ordering",
+ "date": "08/17/17"
+ },
+ {
+ "id": "16",
+ "name": "Chipotle Plymouth",
+ "lat": "45.019846",
+ "lng": "-93.481832",
+ "category": "Restaurant",
+ "address": "3425 Vicksburg Lane N",
+ "address2": "",
+ "city": "Plymouth",
+ "state": "MN",
+ "postal": "55447",
+ "phone": "763-519-0063",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Online Ordering, Fresh Guacamole",
+ "date": "07/17/17"
+ },
+ {
+ "id": "17",
+ "name": "Chipotle Roseville",
+ "lat": "44.998965",
+ "lng": "-93.194622",
+ "category": "Restaurant, Coffee",
+ "address": "860 Rosedale Center Plaza",
+ "address2": "",
+ "city": "Roseville",
+ "state": "MN",
+ "postal": "55113",
+ "phone": "651-633-2300",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "",
+ "date": "06/17/17"
+ },
+ {
+ "id": "18",
+ "name": "Chipotle St. Paul",
+ "lat": "44.939865",
+ "lng": "-93.136768",
+ "category": "Restaurant, Bakery",
+ "address": "867 Grand Ave",
+ "address2": "",
+ "city": "St. Paul",
+ "state": "MN",
+ "postal": "55105",
+ "phone": "651-602-0560",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "",
+ "date": "05/17/17"
+ },
+ {
+ "id": "19",
+ "name": "Chipotle Chanhassen",
+ "lat": "44.858736",
+ "lng": "-93.533661",
+ "category": "Restaurant, Bar",
+ "address": "560 W 79th",
+ "address2": "",
+ "city": "Chanhassen",
+ "state": "MN",
+ "postal": "55317",
+ "phone": "952-294-0301",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Online Ordering, Fresh Guacamole",
+ "date": "04/17/17"
+ },
+ {
+ "id": "20",
+ "name": "Chipotle St. Paul",
+ "lat": "44.945127",
+ "lng": "-93.095368",
+ "category": "Restaurant, Coffee",
+ "address": "29 5th St West",
+ "address2": "",
+ "city": "St. Paul",
+ "state": "MN",
+ "postal": "55102",
+ "phone": "651-291-5411",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Fresh Guacamole",
+ "date": "03/17/17"
+ },
+ {
+ "id": "21",
+ "name": "Chipotle Hudson",
+ "lat": "44.9594046",
+ "lng": "-92.7208174",
+ "category": "Restaurant",
+ "address": "1021 Pearson Dr",
+ "address2": "",
+ "city": "Hudson",
+ "state": "WI",
+ "postal": "54016",
+ "phone": "715-386-3010",
+ "web": "http:\/\/www.chipotle.com",
+ "hours1": "Mon-Sun 11am-10pm",
+ "hours2": "",
+ "hours3": "",
+ "featured": "",
+ "features": "Online Ordering",
+ "date": "03/17/23"
+ }
+]
diff --git a/locations.kml b/dist/data/locations.kml
old mode 100644
new mode 100755
similarity index 99%
rename from locations.kml
rename to dist/data/locations.kml
index 3df98a8..1855c35
--- a/locations.kml
+++ b/dist/data/locations.kml
@@ -164,7 +164,7 @@
#style1
- -93.297470,44.955273,0.000000
+ -93.29719699999998,44.9553438,0.000000
diff --git a/locations.xml b/dist/data/locations.xml
old mode 100644
new mode 100755
similarity index 56%
rename from locations.xml
rename to dist/data/locations.xml
index 6857666..36eb49a
--- a/locations.xml
+++ b/dist/data/locations.xml
@@ -1,23 +1,23 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/default-location-example.html b/dist/default-location-example.html
new file mode 100755
index 0000000..318375d
--- /dev/null
+++ b/dist/default-location-example.html
@@ -0,0 +1,55 @@
+
+
+
+ Map Example - Default Location
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/fullmapstartblank-example.html b/dist/fullmapstartblank-example.html
new file mode 100644
index 0000000..7394d0e
--- /dev/null
+++ b/dist/fullmapstartblank-example.html
@@ -0,0 +1,56 @@
+
+
+
+ Map Example - fullMapStartBlank
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/geocode.html b/dist/geocode.html
new file mode 100755
index 0000000..7068a87
--- /dev/null
+++ b/dist/geocode.html
@@ -0,0 +1,44 @@
+
+
+
+ Geocode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/index.html b/dist/index.html
new file mode 100755
index 0000000..01976b3
--- /dev/null
+++ b/dist/index.html
@@ -0,0 +1,51 @@
+
+
+
+ Map Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/infobubble-example.html b/dist/infobubble-example.html
new file mode 100644
index 0000000..319e823
--- /dev/null
+++ b/dist/infobubble-example.html
@@ -0,0 +1,63 @@
+
+
+
+ Infobubble Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/inline-directions.html b/dist/inline-directions.html
new file mode 100644
index 0000000..0b94320
--- /dev/null
+++ b/dist/inline-directions.html
@@ -0,0 +1,52 @@
+
+
+
+ Map Example - Inline Directions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/json-example.html b/dist/json-example.html
new file mode 100755
index 0000000..3d70edb
--- /dev/null
+++ b/dist/json-example.html
@@ -0,0 +1,53 @@
+
+
+
+ Map Example - JSON Data
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/kml-example.html b/dist/kml-example.html
new file mode 100755
index 0000000..c6bb566
--- /dev/null
+++ b/dist/kml-example.html
@@ -0,0 +1,53 @@
+
+
+
+ Map Example - KML Data
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/lazy-load-example.html b/dist/lazy-load-example.html
new file mode 100644
index 0000000..df72247
--- /dev/null
+++ b/dist/lazy-load-example.html
@@ -0,0 +1,64 @@
+
+
+
+ Map Example
+
+
+
+
+
+
+
+
+
+
Agree to cookies
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/length-unit-swap-example.html b/dist/length-unit-swap-example.html
new file mode 100644
index 0000000..a21613e
--- /dev/null
+++ b/dist/length-unit-swap-example.html
@@ -0,0 +1,57 @@
+
+
+
+ Length Unit Swap Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/maxdistance-example.html b/dist/maxdistance-example.html
new file mode 100755
index 0000000..59130bf
--- /dev/null
+++ b/dist/maxdistance-example.html
@@ -0,0 +1,58 @@
+
+
+
+ Map Example - Maximum Distance
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/modal-example.html b/dist/modal-example.html
new file mode 100755
index 0000000..c5d17a9
--- /dev/null
+++ b/dist/modal-example.html
@@ -0,0 +1,53 @@
+
+
+
+ Map Example - Modal Window
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/namesearch-example.html b/dist/namesearch-example.html
new file mode 100644
index 0000000..d427962
--- /dev/null
+++ b/dist/namesearch-example.html
@@ -0,0 +1,56 @@
+
+
+
+ NameSearch Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/noform-example.html b/dist/noform-example.html
new file mode 100755
index 0000000..dd2bedc
--- /dev/null
+++ b/dist/noform-example.html
@@ -0,0 +1,49 @@
+
+
+
+ Map Example - No Form for ASP.net
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/pagination-example.html b/dist/pagination-example.html
new file mode 100644
index 0000000..c82b04d
--- /dev/null
+++ b/dist/pagination-example.html
@@ -0,0 +1,55 @@
+
+
+
+ Map Example - Pagination
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/query-string-example/index.html b/dist/query-string-example/index.html
new file mode 100644
index 0000000..9eaf051
--- /dev/null
+++ b/dist/query-string-example/index.html
@@ -0,0 +1,31 @@
+
+
+
+ Map Example
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/query-string-example/submit.html b/dist/query-string-example/submit.html
new file mode 100644
index 0000000..bc983c6
--- /dev/null
+++ b/dist/query-string-example/submit.html
@@ -0,0 +1,61 @@
+
+
+
+ Map Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/rawdata-example.php b/dist/rawdata-example.php
new file mode 100644
index 0000000..d1b38f3
--- /dev/null
+++ b/dist/rawdata-example.php
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ';
+
+?>
+
+
+
+
+ Map Example - Raw Data
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/sort-example.html b/dist/sort-example.html
new file mode 100644
index 0000000..aa23600
--- /dev/null
+++ b/dist/sort-example.html
@@ -0,0 +1,77 @@
+
+
+
+ Sorting Example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/geocode.html b/geocode.html
deleted file mode 100644
index 34bc45e..0000000
--- a/geocode.html
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
- Geocode
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/index.html b/index.html
deleted file mode 100644
index efb298c..0000000
--- a/index.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
- Map Example
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/js/handlebars-1.0.0.js b/js/handlebars-1.0.0.js
deleted file mode 100644
index 973d3c1..0000000
--- a/js/handlebars-1.0.0.js
+++ /dev/null
@@ -1,2278 +0,0 @@
-/*
-
-Copyright (C) 2011 by Yehuda Katz
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
-*/
-
-// lib/handlebars/browser-prefix.js
-var Handlebars = {};
-
-(function(Handlebars, undefined) {
-;
-// lib/handlebars/base.js
-
-Handlebars.VERSION = "1.0.0";
-Handlebars.COMPILER_REVISION = 4;
-
-Handlebars.REVISION_CHANGES = {
- 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
- 2: '== 1.0.0-rc.3',
- 3: '== 1.0.0-rc.4',
- 4: '>= 1.0.0'
-};
-
-Handlebars.helpers = {};
-Handlebars.partials = {};
-
-var toString = Object.prototype.toString,
- functionType = '[object Function]',
- objectType = '[object Object]';
-
-Handlebars.registerHelper = function(name, fn, inverse) {
- if (toString.call(name) === objectType) {
- if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
- Handlebars.Utils.extend(this.helpers, name);
- } else {
- if (inverse) { fn.not = inverse; }
- this.helpers[name] = fn;
- }
-};
-
-Handlebars.registerPartial = function(name, str) {
- if (toString.call(name) === objectType) {
- Handlebars.Utils.extend(this.partials, name);
- } else {
- this.partials[name] = str;
- }
-};
-
-Handlebars.registerHelper('helperMissing', function(arg) {
- if(arguments.length === 2) {
- return undefined;
- } else {
- throw new Error("Missing helper: '" + arg + "'");
- }
-});
-
-Handlebars.registerHelper('blockHelperMissing', function(context, options) {
- var inverse = options.inverse || function() {}, fn = options.fn;
-
- var type = toString.call(context);
-
- if(type === functionType) { context = context.call(this); }
-
- if(context === true) {
- return fn(this);
- } else if(context === false || context == null) {
- return inverse(this);
- } else if(type === "[object Array]") {
- if(context.length > 0) {
- return Handlebars.helpers.each(context, options);
- } else {
- return inverse(this);
- }
- } else {
- return fn(context);
- }
-});
-
-Handlebars.K = function() {};
-
-Handlebars.createFrame = Object.create || function(object) {
- Handlebars.K.prototype = object;
- var obj = new Handlebars.K();
- Handlebars.K.prototype = null;
- return obj;
-};
-
-Handlebars.logger = {
- DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
-
- methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
-
- // can be overridden in the host environment
- log: function(level, obj) {
- if (Handlebars.logger.level <= level) {
- var method = Handlebars.logger.methodMap[level];
- if (typeof console !== 'undefined' && console[method]) {
- console[method].call(console, obj);
- }
- }
- }
-};
-
-Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
-
-Handlebars.registerHelper('each', function(context, options) {
- var fn = options.fn, inverse = options.inverse;
- var i = 0, ret = "", data;
-
- var type = toString.call(context);
- if(type === functionType) { context = context.call(this); }
-
- if (options.data) {
- data = Handlebars.createFrame(options.data);
- }
-
- if(context && typeof context === 'object') {
- if(context instanceof Array){
- for(var j = context.length; i 2) {
- expected.push("'" + this.terminals_[p] + "'");
- }
- if (this.lexer.showPosition) {
- errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
- } else {
- errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
- }
- this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
- }
- }
- if (action[0] instanceof Array && action.length > 1) {
- throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
- }
- switch (action[0]) {
- case 1:
- stack.push(symbol);
- vstack.push(this.lexer.yytext);
- lstack.push(this.lexer.yylloc);
- stack.push(action[1]);
- symbol = null;
- if (!preErrorSymbol) {
- yyleng = this.lexer.yyleng;
- yytext = this.lexer.yytext;
- yylineno = this.lexer.yylineno;
- yyloc = this.lexer.yylloc;
- if (recovering > 0)
- recovering--;
- } else {
- symbol = preErrorSymbol;
- preErrorSymbol = null;
- }
- break;
- case 2:
- len = this.productions_[action[1]][1];
- yyval.$ = vstack[vstack.length - len];
- yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
- if (ranges) {
- yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
- }
- r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
- if (typeof r !== "undefined") {
- return r;
- }
- if (len) {
- stack = stack.slice(0, -1 * len * 2);
- vstack = vstack.slice(0, -1 * len);
- lstack = lstack.slice(0, -1 * len);
- }
- stack.push(this.productions_[action[1]][0]);
- vstack.push(yyval.$);
- lstack.push(yyval._$);
- newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
- stack.push(newState);
- break;
- case 3:
- return true;
- }
- }
- return true;
-}
-};
-/* Jison generated lexer */
-var lexer = (function(){
-var lexer = ({EOF:1,
-parseError:function parseError(str, hash) {
- if (this.yy.parser) {
- this.yy.parser.parseError(str, hash);
- } else {
- throw new Error(str);
- }
- },
-setInput:function (input) {
- this._input = input;
- this._more = this._less = this.done = false;
- this.yylineno = this.yyleng = 0;
- this.yytext = this.matched = this.match = '';
- this.conditionStack = ['INITIAL'];
- this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
- if (this.options.ranges) this.yylloc.range = [0,0];
- this.offset = 0;
- return this;
- },
-input:function () {
- var ch = this._input[0];
- this.yytext += ch;
- this.yyleng++;
- this.offset++;
- this.match += ch;
- this.matched += ch;
- var lines = ch.match(/(?:\r\n?|\n).*/g);
- if (lines) {
- this.yylineno++;
- this.yylloc.last_line++;
- } else {
- this.yylloc.last_column++;
- }
- if (this.options.ranges) this.yylloc.range[1]++;
-
- this._input = this._input.slice(1);
- return ch;
- },
-unput:function (ch) {
- var len = ch.length;
- var lines = ch.split(/(?:\r\n?|\n)/g);
-
- this._input = ch + this._input;
- this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
- //this.yyleng -= len;
- this.offset -= len;
- var oldLines = this.match.split(/(?:\r\n?|\n)/g);
- this.match = this.match.substr(0, this.match.length-1);
- this.matched = this.matched.substr(0, this.matched.length-1);
-
- if (lines.length-1) this.yylineno -= lines.length-1;
- var r = this.yylloc.range;
-
- this.yylloc = {first_line: this.yylloc.first_line,
- last_line: this.yylineno+1,
- first_column: this.yylloc.first_column,
- last_column: lines ?
- (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
- this.yylloc.first_column - len
- };
-
- if (this.options.ranges) {
- this.yylloc.range = [r[0], r[0] + this.yyleng - len];
- }
- return this;
- },
-more:function () {
- this._more = true;
- return this;
- },
-less:function (n) {
- this.unput(this.match.slice(n));
- },
-pastInput:function () {
- var past = this.matched.substr(0, this.matched.length - this.match.length);
- return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
- },
-upcomingInput:function () {
- var next = this.match;
- if (next.length < 20) {
- next += this._input.substr(0, 20-next.length);
- }
- return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
- },
-showPosition:function () {
- var pre = this.pastInput();
- var c = new Array(pre.length + 1).join("-");
- return pre + this.upcomingInput() + "\n" + c+"^";
- },
-next:function () {
- if (this.done) {
- return this.EOF;
- }
- if (!this._input) this.done = true;
-
- var token,
- match,
- tempMatch,
- index,
- col,
- lines;
- if (!this._more) {
- this.yytext = '';
- this.match = '';
- }
- var rules = this._currentRules();
- for (var i=0;i < rules.length; i++) {
- tempMatch = this._input.match(this.rules[rules[i]]);
- if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
- match = tempMatch;
- index = i;
- if (!this.options.flex) break;
- }
- }
- if (match) {
- lines = match[0].match(/(?:\r\n?|\n).*/g);
- if (lines) this.yylineno += lines.length;
- this.yylloc = {first_line: this.yylloc.last_line,
- last_line: this.yylineno+1,
- first_column: this.yylloc.last_column,
- last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
- this.yytext += match[0];
- this.match += match[0];
- this.matches = match;
- this.yyleng = this.yytext.length;
- if (this.options.ranges) {
- this.yylloc.range = [this.offset, this.offset += this.yyleng];
- }
- this._more = false;
- this._input = this._input.slice(match[0].length);
- this.matched += match[0];
- token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
- if (this.done && this._input) this.done = false;
- if (token) return token;
- else return;
- }
- if (this._input === "") {
- return this.EOF;
- } else {
- return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
- {text: "", token: null, line: this.yylineno});
- }
- },
-lex:function lex() {
- var r = this.next();
- if (typeof r !== 'undefined') {
- return r;
- } else {
- return this.lex();
- }
- },
-begin:function begin(condition) {
- this.conditionStack.push(condition);
- },
-popState:function popState() {
- return this.conditionStack.pop();
- },
-_currentRules:function _currentRules() {
- return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
- },
-topState:function () {
- return this.conditionStack[this.conditionStack.length-2];
- },
-pushState:function begin(condition) {
- this.begin(condition);
- }});
-lexer.options = {};
-lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
-
-var YYSTATE=YY_START
-switch($avoiding_name_collisions) {
-case 0: yy_.yytext = "\\"; return 14;
-break;
-case 1:
- if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
- if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu");
- if(yy_.yytext) return 14;
-
-break;
-case 2: return 14;
-break;
-case 3:
- if(yy_.yytext.slice(-1) !== "\\") this.popState();
- if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1);
- return 14;
-
-break;
-case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15;
-break;
-case 5: return 25;
-break;
-case 6: return 16;
-break;
-case 7: return 20;
-break;
-case 8: return 19;
-break;
-case 9: return 19;
-break;
-case 10: return 23;
-break;
-case 11: return 22;
-break;
-case 12: this.popState(); this.begin('com');
-break;
-case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
-break;
-case 14: return 22;
-break;
-case 15: return 37;
-break;
-case 16: return 36;
-break;
-case 17: return 36;
-break;
-case 18: return 40;
-break;
-case 19: /*ignore whitespace*/
-break;
-case 20: this.popState(); return 24;
-break;
-case 21: this.popState(); return 18;
-break;
-case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 31;
-break;
-case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 31;
-break;
-case 24: return 38;
-break;
-case 25: return 33;
-break;
-case 26: return 33;
-break;
-case 27: return 32;
-break;
-case 28: return 36;
-break;
-case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 36;
-break;
-case 30: return 'INVALID';
-break;
-case 31: return 5;
-break;
-}
-};
-lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
-lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"INITIAL":{"rules":[0,1,2,31],"inclusive":true}};
-return lexer;})()
-parser.lexer = lexer;
-function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
-return new Parser;
-})();;
-// lib/handlebars/compiler/base.js
-
-Handlebars.Parser = handlebars;
-
-Handlebars.parse = function(input) {
-
- // Just return if an already-compile AST was passed in.
- if(input.constructor === Handlebars.AST.ProgramNode) { return input; }
-
- Handlebars.Parser.yy = Handlebars.AST;
- return Handlebars.Parser.parse(input);
-};
-;
-// lib/handlebars/compiler/ast.js
-Handlebars.AST = {};
-
-Handlebars.AST.ProgramNode = function(statements, inverse) {
- this.type = "program";
- this.statements = statements;
- if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
-};
-
-Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
- this.type = "mustache";
- this.escaped = !unescaped;
- this.hash = hash;
-
- var id = this.id = rawParams[0];
- var params = this.params = rawParams.slice(1);
-
- // a mustache is an eligible helper if:
- // * its id is simple (a single part, not `this` or `..`)
- var eligibleHelper = this.eligibleHelper = id.isSimple;
-
- // a mustache is definitely a helper if:
- // * it is an eligible helper, and
- // * it has at least one parameter or hash segment
- this.isHelper = eligibleHelper && (params.length || hash);
-
- // if a mustache is an eligible helper but not a definite
- // helper, it is ambiguous, and will be resolved in a later
- // pass or at runtime.
-};
-
-Handlebars.AST.PartialNode = function(partialName, context) {
- this.type = "partial";
- this.partialName = partialName;
- this.context = context;
-};
-
-Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
- var verifyMatch = function(open, close) {
- if(open.original !== close.original) {
- throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
- }
- };
-
- verifyMatch(mustache.id, close);
- this.type = "block";
- this.mustache = mustache;
- this.program = program;
- this.inverse = inverse;
-
- if (this.inverse && !this.program) {
- this.isInverse = true;
- }
-};
-
-Handlebars.AST.ContentNode = function(string) {
- this.type = "content";
- this.string = string;
-};
-
-Handlebars.AST.HashNode = function(pairs) {
- this.type = "hash";
- this.pairs = pairs;
-};
-
-Handlebars.AST.IdNode = function(parts) {
- this.type = "ID";
-
- var original = "",
- dig = [],
- depth = 0;
-
- for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + original); }
- else if (part === "..") { depth++; }
- else { this.isScoped = true; }
- }
- else { dig.push(part); }
- }
-
- this.original = original;
- this.parts = dig;
- this.string = dig.join('.');
- this.depth = depth;
-
- // an ID is simple if it only has one part, and that part is not
- // `..` or `this`.
- this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
-
- this.stringModeValue = this.string;
-};
-
-Handlebars.AST.PartialNameNode = function(name) {
- this.type = "PARTIAL_NAME";
- this.name = name.original;
-};
-
-Handlebars.AST.DataNode = function(id) {
- this.type = "DATA";
- this.id = id;
-};
-
-Handlebars.AST.StringNode = function(string) {
- this.type = "STRING";
- this.original =
- this.string =
- this.stringModeValue = string;
-};
-
-Handlebars.AST.IntegerNode = function(integer) {
- this.type = "INTEGER";
- this.original =
- this.integer = integer;
- this.stringModeValue = Number(integer);
-};
-
-Handlebars.AST.BooleanNode = function(bool) {
- this.type = "BOOLEAN";
- this.bool = bool;
- this.stringModeValue = bool === "true";
-};
-
-Handlebars.AST.CommentNode = function(comment) {
- this.type = "comment";
- this.comment = comment;
-};
-;
-// lib/handlebars/utils.js
-
-var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
-
-Handlebars.Exception = function(message) {
- var tmp = Error.prototype.constructor.apply(this, arguments);
-
- // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
- for (var idx = 0; idx < errorProps.length; idx++) {
- this[errorProps[idx]] = tmp[errorProps[idx]];
- }
-};
-Handlebars.Exception.prototype = new Error();
-
-// Build out our basic SafeString type
-Handlebars.SafeString = function(string) {
- this.string = string;
-};
-Handlebars.SafeString.prototype.toString = function() {
- return this.string.toString();
-};
-
-var escape = {
- "&": "&",
- "<": "<",
- ">": ">",
- '"': """,
- "'": "'",
- "`": "`"
-};
-
-var badChars = /[&<>"'`]/g;
-var possible = /[&<>"'`]/;
-
-var escapeChar = function(chr) {
- return escape[chr] || "&";
-};
-
-Handlebars.Utils = {
- extend: function(obj, value) {
- for(var key in value) {
- if(value.hasOwnProperty(key)) {
- obj[key] = value[key];
- }
- }
- },
-
- escapeExpression: function(string) {
- // don't escape SafeStrings, since they're already safe
- if (string instanceof Handlebars.SafeString) {
- return string.toString();
- } else if (string == null || string === false) {
- return "";
- }
-
- // Force a string conversion as this will be done by the append regardless and
- // the regex test will do this transparently behind the scenes, causing issues if
- // an object's to string has escaped characters in it.
- string = string.toString();
-
- if(!possible.test(string)) { return string; }
- return string.replace(badChars, escapeChar);
- },
-
- isEmpty: function(value) {
- if (!value && value !== 0) {
- return true;
- } else if(toString.call(value) === "[object Array]" && value.length === 0) {
- return true;
- } else {
- return false;
- }
- }
-};
-;
-// lib/handlebars/compiler/compiler.js
-
-/*jshint eqnull:true*/
-var Compiler = Handlebars.Compiler = function() {};
-var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {};
-
-// the foundHelper register will disambiguate helper lookup from finding a
-// function in a context. This is necessary for mustache compatibility, which
-// requires that context functions in blocks are evaluated by blockHelperMissing,
-// and then proceed as if the resulting value was provided to blockHelperMissing.
-
-Compiler.prototype = {
- compiler: Compiler,
-
- disassemble: function() {
- var opcodes = this.opcodes, opcode, out = [], params, param;
-
- for (var i=0, l=opcodes.length; i< len; i++) {
- var opcode = this.opcodes[i],
- otherOpcode = other.opcodes[i];
- if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
- return false;
- }
- for (var j = 0; j < opcode.args.length; j++) {
- if (opcode.args[j] !== otherOpcode.args[j]) {
- return false;
- }
- }
- }
-
- len = this.children.length;
- if (other.children.length !== len) {
- return false;
- }
- for (i = 0; i < len; i++) {
- if (!this.children[i].equals(other.children[i])) {
- return false;
- }
- }
-
- return true;
- },
-
- guid: 0,
-
- compile: function(program, options) {
- this.children = [];
- this.depths = {list: []};
- this.options = options;
-
- // These changes will propagate to the other compiler components
- var knownHelpers = this.options.knownHelpers;
- this.options.knownHelpers = {
- 'helperMissing': true,
- 'blockHelperMissing': true,
- 'each': true,
- 'if': true,
- 'unless': true,
- 'with': true,
- 'log': true
- };
- if (knownHelpers) {
- for (var name in knownHelpers) {
- this.options.knownHelpers[name] = knownHelpers[name];
- }
- }
-
- return this.program(program);
- },
-
- accept: function(node) {
- return this[node.type](node);
- },
-
- program: function(program) {
- var statements = program.statements, statement;
- this.opcodes = [];
-
- for(var i=0, l=statements.length; i< 2) { continue; }
- else { this.addDepth(depth - 1); }
- }
-
- return guid;
- },
-
- block: function(block) {
- var mustache = block.mustache,
- program = block.program,
- inverse = block.inverse;
-
- if (program) {
- program = this.compileProgram(program);
- }
-
- if (inverse) {
- inverse = this.compileProgram(inverse);
- }
-
- var type = this.classifyMustache(mustache);
-
- if (type === "helper") {
- this.helperMustache(mustache, program, inverse);
- } else if (type === "simple") {
- this.simpleMustache(mustache);
-
- // now that the simple mustache is resolved, we need to
- // evaluate it by executing `blockHelperMissing`
- this.opcode('pushProgram', program);
- this.opcode('pushProgram', inverse);
- this.opcode('emptyHash');
- this.opcode('blockValue');
- } else {
- this.ambiguousMustache(mustache, program, inverse);
-
- // now that the simple mustache is resolved, we need to
- // evaluate it by executing `blockHelperMissing`
- this.opcode('pushProgram', program);
- this.opcode('pushProgram', inverse);
- this.opcode('emptyHash');
- this.opcode('ambiguousBlockValue');
- }
-
- this.opcode('append');
- },
-
- hash: function(hash) {
- var pairs = hash.pairs, pair, val;
-
- this.opcode('pushHash');
-
- for(var i=0, l=pairs.length; i 0) {
- this.source[1] = this.source[1] + ", " + locals.join(", ");
- }
-
- // Generate minimizer alias mappings
- if (!this.isChild) {
- for (var alias in this.context.aliases) {
- if (this.context.aliases.hasOwnProperty(alias)) {
- this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
- }
- }
- }
-
- if (this.source[1]) {
- this.source[1] = "var " + this.source[1].substring(2) + ";";
- }
-
- // Merge children
- if (!this.isChild) {
- this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
- }
-
- if (!this.environment.isSimple) {
- this.source.push("return buffer;");
- }
-
- var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
-
- for(var i=0, l=this.environment.depths.list.length; i < len; i++) {
- var line = this.source[i];
- if (line.appendToBuffer) {
- if (buffer) {
- buffer = buffer + '\n + ' + line.content;
- } else {
- buffer = line.content;
- }
- } else {
- if (buffer) {
- source += 'buffer += ' + buffer + ';\n ';
- buffer = undefined;
- }
- source += line + '\n ';
- }
- }
- return source;
- },
-
- // [blockValue]
- //
- // On stack, before: hash, inverse, program, value
- // On stack, after: return value of blockHelperMissing
- //
- // The purpose of this opcode is to take a block of the form
- // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
- // replace it on the stack with the result of properly
- // invoking blockHelperMissing.
- blockValue: function() {
- this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
-
- var params = ["depth0"];
- this.setupParams(0, params);
-
- this.replaceStack(function(current) {
- params.splice(1, 0, current);
- return "blockHelperMissing.call(" + params.join(", ") + ")";
- });
- },
-
- // [ambiguousBlockValue]
- //
- // On stack, before: hash, inverse, program, value
- // Compiler value, before: lastHelper=value of last found helper, if any
- // On stack, after, if no lastHelper: same as [blockValue]
- // On stack, after, if lastHelper: value
- ambiguousBlockValue: function() {
- this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
-
- var params = ["depth0"];
- this.setupParams(0, params);
-
- var current = this.topStack();
- params.splice(1, 0, current);
-
- // Use the options value generated from the invocation
- params[params.length-1] = 'options';
-
- this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
- },
-
- // [appendContent]
- //
- // On stack, before: ...
- // On stack, after: ...
- //
- // Appends the string value of `content` to the current buffer
- appendContent: function(content) {
- this.source.push(this.appendToBuffer(this.quotedString(content)));
- },
-
- // [append]
- //
- // On stack, before: value, ...
- // On stack, after: ...
- //
- // Coerces `value` to a String and appends it to the current buffer.
- //
- // If `value` is truthy, or 0, it is coerced into a string and appended
- // Otherwise, the empty string is appended
- append: function() {
- // Force anything that is inlined onto the stack so we don't have duplication
- // when we examine local
- this.flushInline();
- var local = this.popStack();
- this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
- if (this.environment.isSimple) {
- this.source.push("else { " + this.appendToBuffer("''") + " }");
- }
- },
-
- // [appendEscaped]
- //
- // On stack, before: value, ...
- // On stack, after: ...
- //
- // Escape `value` and append it to the buffer
- appendEscaped: function() {
- this.context.aliases.escapeExpression = 'this.escapeExpression';
-
- this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
- },
-
- // [getContext]
- //
- // On stack, before: ...
- // On stack, after: ...
- // Compiler value, after: lastContext=depth
- //
- // Set the value of the `lastContext` compiler value to the depth
- getContext: function(depth) {
- if(this.lastContext !== depth) {
- this.lastContext = depth;
- }
- },
-
- // [lookupOnContext]
- //
- // On stack, before: ...
- // On stack, after: currentContext[name], ...
- //
- // Looks up the value of `name` on the current context and pushes
- // it onto the stack.
- lookupOnContext: function(name) {
- this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
- },
-
- // [pushContext]
- //
- // On stack, before: ...
- // On stack, after: currentContext, ...
- //
- // Pushes the value of the current context onto the stack.
- pushContext: function() {
- this.pushStackLiteral('depth' + this.lastContext);
- },
-
- // [resolvePossibleLambda]
- //
- // On stack, before: value, ...
- // On stack, after: resolved value, ...
- //
- // If the `value` is a lambda, replace it on the stack by
- // the return value of the lambda
- resolvePossibleLambda: function() {
- this.context.aliases.functionType = '"function"';
-
- this.replaceStack(function(current) {
- return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
- });
- },
-
- // [lookup]
- //
- // On stack, before: value, ...
- // On stack, after: value[name], ...
- //
- // Replace the value on the stack with the result of looking
- // up `name` on `value`
- lookup: function(name) {
- this.replaceStack(function(current) {
- return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
- });
- },
-
- // [lookupData]
- //
- // On stack, before: ...
- // On stack, after: data[id], ...
- //
- // Push the result of looking up `id` on the current data
- lookupData: function(id) {
- this.push('data');
- },
-
- // [pushStringParam]
- //
- // On stack, before: ...
- // On stack, after: string, currentContext, ...
- //
- // This opcode is designed for use in string mode, which
- // provides the string value of a parameter along with its
- // depth rather than resolving it immediately.
- pushStringParam: function(string, type) {
- this.pushStackLiteral('depth' + this.lastContext);
-
- this.pushString(type);
-
- if (typeof string === 'string') {
- this.pushString(string);
- } else {
- this.pushStackLiteral(string);
- }
- },
-
- emptyHash: function() {
- this.pushStackLiteral('{}');
-
- if (this.options.stringParams) {
- this.register('hashTypes', '{}');
- this.register('hashContexts', '{}');
- }
- },
- pushHash: function() {
- this.hash = {values: [], types: [], contexts: []};
- },
- popHash: function() {
- var hash = this.hash;
- this.hash = undefined;
-
- if (this.options.stringParams) {
- this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
- this.register('hashTypes', '{' + hash.types.join(',') + '}');
- }
- this.push('{\n ' + hash.values.join(',\n ') + '\n }');
- },
-
- // [pushString]
- //
- // On stack, before: ...
- // On stack, after: quotedString(string), ...
- //
- // Push a quoted version of `string` onto the stack
- pushString: function(string) {
- this.pushStackLiteral(this.quotedString(string));
- },
-
- // [push]
- //
- // On stack, before: ...
- // On stack, after: expr, ...
- //
- // Push an expression onto the stack
- push: function(expr) {
- this.inlineStack.push(expr);
- return expr;
- },
-
- // [pushLiteral]
- //
- // On stack, before: ...
- // On stack, after: value, ...
- //
- // Pushes a value onto the stack. This operation prevents
- // the compiler from creating a temporary variable to hold
- // it.
- pushLiteral: function(value) {
- this.pushStackLiteral(value);
- },
-
- // [pushProgram]
- //
- // On stack, before: ...
- // On stack, after: program(guid), ...
- //
- // Push a program expression onto the stack. This takes
- // a compile-time guid and converts it into a runtime-accessible
- // expression.
- pushProgram: function(guid) {
- if (guid != null) {
- this.pushStackLiteral(this.programExpression(guid));
- } else {
- this.pushStackLiteral(null);
- }
- },
-
- // [invokeHelper]
- //
- // On stack, before: hash, inverse, program, params..., ...
- // On stack, after: result of helper invocation
- //
- // Pops off the helper's parameters, invokes the helper,
- // and pushes the helper's return value onto the stack.
- //
- // If the helper is not found, `helperMissing` is called.
- invokeHelper: function(paramSize, name) {
- this.context.aliases.helperMissing = 'helpers.helperMissing';
-
- var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
- var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
-
- this.push(helper.name + ' || ' + nonHelper);
- this.replaceStack(function(name) {
- return name + ' ? ' + name + '.call(' +
- helper.callParams + ") " + ": helperMissing.call(" +
- helper.helperMissingParams + ")";
- });
- },
-
- // [invokeKnownHelper]
- //
- // On stack, before: hash, inverse, program, params..., ...
- // On stack, after: result of helper invocation
- //
- // This operation is used when the helper is known to exist,
- // so a `helperMissing` fallback is not required.
- invokeKnownHelper: function(paramSize, name) {
- var helper = this.setupHelper(paramSize, name);
- this.push(helper.name + ".call(" + helper.callParams + ")");
- },
-
- // [invokeAmbiguous]
- //
- // On stack, before: hash, inverse, program, params..., ...
- // On stack, after: result of disambiguation
- //
- // This operation is used when an expression like `{{foo}}`
- // is provided, but we don't know at compile-time whether it
- // is a helper or a path.
- //
- // This operation emits more code than the other options,
- // and can be avoided by passing the `knownHelpers` and
- // `knownHelpersOnly` flags at compile-time.
- invokeAmbiguous: function(name, helperCall) {
- this.context.aliases.functionType = '"function"';
-
- this.pushStackLiteral('{}'); // Hash value
- var helper = this.setupHelper(0, name, helperCall);
-
- var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
-
- var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
- var nextStack = this.nextStack();
-
- this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
- this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }');
- },
-
- // [invokePartial]
- //
- // On stack, before: context, ...
- // On stack after: result of partial invocation
- //
- // This operation pops off a context, invokes a partial with that context,
- // and pushes the result of the invocation back.
- invokePartial: function(name) {
- var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
-
- if (this.options.data) {
- params.push("data");
- }
-
- this.context.aliases.self = "this";
- this.push("self.invokePartial(" + params.join(", ") + ")");
- },
-
- // [assignToHash]
- //
- // On stack, before: value, hash, ...
- // On stack, after: hash, ...
- //
- // Pops a value and hash off the stack, assigns `hash[key] = value`
- // and pushes the hash back onto the stack.
- assignToHash: function(key) {
- var value = this.popStack(),
- context,
- type;
-
- if (this.options.stringParams) {
- type = this.popStack();
- context = this.popStack();
- }
-
- var hash = this.hash;
- if (context) {
- hash.contexts.push("'" + key + "': " + context);
- }
- if (type) {
- hash.types.push("'" + key + "': " + type);
- }
- hash.values.push("'" + key + "': (" + value + ")");
- },
-
- // HELPERS
-
- compiler: JavaScriptCompiler,
-
- compileChildren: function(environment, options) {
- var children = environment.children, child, compiler;
-
- for(var i=0, l=children.length; i < len; i++) {
- var environment = this.context.environments[i];
- if (environment && environment.equals(child)) {
- return i;
- }
- }
- },
-
- programExpression: function(guid) {
- this.context.aliases.self = "this";
-
- if(guid == null) {
- return "self.noop";
- }
-
- var child = this.environment.children[guid],
- depths = child.depths.list, depth;
-
- var programParams = [child.index, child.name, "data"];
-
- for(var i=0, l = depths.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
- return this.topStackName();
- },
- topStackName: function() {
- return "stack" + this.stackSlot;
- },
- flushInline: function() {
- var inlineStack = this.inlineStack;
- if (inlineStack.length) {
- this.inlineStack = [];
- for (var i = 0, len = inlineStack.length; i < len; i++) {
- var entry = inlineStack[i];
- if (entry instanceof Literal) {
- this.compileStack.push(entry);
- } else {
- this.pushStack(entry);
- }
- }
- }
- },
- isInline: function() {
- return this.inlineStack.length;
- },
-
- popStack: function(wrapped) {
- var inline = this.isInline(),
- item = (inline ? this.inlineStack : this.compileStack).pop();
-
- if (!wrapped && (item instanceof Literal)) {
- return item.value;
- } else {
- if (!inline) {
- this.stackSlot--;
- }
- return item;
- }
- },
-
- topStack: function(wrapped) {
- var stack = (this.isInline() ? this.inlineStack : this.compileStack),
- item = stack[stack.length - 1];
-
- if (!wrapped && (item instanceof Literal)) {
- return item.value;
- } else {
- return item;
- }
- },
-
- quotedString: function(str) {
- return '"' + str
- .replace(/\\/g, '\\\\')
- .replace(/"/g, '\\"')
- .replace(/\n/g, '\\n')
- .replace(/\r/g, '\\r')
- .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
- .replace(/\u2029/g, '\\u2029') + '"';
- },
-
- setupHelper: function(paramSize, name, missingParams) {
- var params = [];
- this.setupParams(paramSize, params, missingParams);
- var foundHelper = this.nameLookup('helpers', name, 'helper');
-
- return {
- params: params,
- name: foundHelper,
- callParams: ["depth0"].concat(params).join(", "),
- helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
- };
- },
-
- // the params and contexts arguments are passed in arrays
- // to fill in
- setupParams: function(paramSize, params, useRegister) {
- var options = [], contexts = [], types = [], param, inverse, program;
-
- options.push("hash:" + this.popStack());
-
- inverse = this.popStack();
- program = this.popStack();
-
- // Avoid setting fn and inverse if neither are set. This allows
- // helpers to do a check for `if (options.fn)`
- if (program || inverse) {
- if (!program) {
- this.context.aliases.self = "this";
- program = "self.noop";
- }
-
- if (!inverse) {
- this.context.aliases.self = "this";
- inverse = "self.noop";
- }
-
- options.push("inverse:" + inverse);
- options.push("fn:" + program);
- }
-
- for(var i=0; i < currentRevision) {
- var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
- compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
- throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
- "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
- } else {
- // Use the embedded version info since the runtime doesn't know about this revision yet
- throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
- "Please update your runtime to a newer version ("+compilerInfo[1]+").";
- }
- }
-
- return result;
- };
- },
-
- programWithDepth: function(i, fn, data /*, $depth */) {
- var args = Array.prototype.slice.call(arguments, 3);
-
- var program = function(context, options) {
- options = options || {};
-
- return fn.apply(this, [context, options.data || data].concat(args));
- };
- program.program = i;
- program.depth = args.length;
- return program;
- },
- program: function(i, fn, data) {
- var program = function(context, options) {
- options = options || {};
-
- return fn(context, options.data || data);
- };
- program.program = i;
- program.depth = 0;
- return program;
- },
- noop: function() { return ""; },
- invokePartial: function(partial, name, context, helpers, partials, data) {
- var options = { helpers: helpers, partials: partials, data: data };
-
- if(partial === undefined) {
- throw new Handlebars.Exception("The partial " + name + " could not be found");
- } else if(partial instanceof Function) {
- return partial(context, options);
- } else if (!Handlebars.compile) {
- throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
- } else {
- partials[name] = Handlebars.compile(partial, {data: data !== undefined});
- return partials[name](context, options);
- }
- }
-};
-
-Handlebars.template = Handlebars.VM.template;
-;
-// lib/handlebars/browser-suffix.js
-})(Handlebars);
-;
\ No newline at end of file
diff --git a/js/handlebars-1.0.0.min.js b/js/handlebars-1.0.0.min.js
deleted file mode 100644
index a762495..0000000
--- a/js/handlebars-1.0.0.min.js
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
-
-Copyright (C) 2011 by Yehuda Katz
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
-*/
-
-var Handlebars={};(function(d,c){d.VERSION="1.0.0";d.COMPILER_REVISION=4;d.REVISION_CHANGES={1:"<= 1.0.rc.2",2:"== 1.0.0-rc.3",3:"== 1.0.0-rc.4",4:">= 1.0.0"};
-d.helpers={};d.partials={};var n=Object.prototype.toString,b="[object Function]",h="[object Object]";d.registerHelper=function(l,u,i){if(n.call(l)===h){if(i||u){throw new d.Exception("Arg not supported with multiple helpers");
-}d.Utils.extend(this.helpers,l);}else{if(i){u.not=i;}this.helpers[l]=u;}};d.registerPartial=function(i,l){if(n.call(i)===h){d.Utils.extend(this.partials,i);
-}else{this.partials[i]=l;}};d.registerHelper("helperMissing",function(i){if(arguments.length===2){return c;}else{throw new Error("Missing helper: '"+i+"'");
-}});d.registerHelper("blockHelperMissing",function(u,l){var i=l.inverse||function(){},w=l.fn;var v=n.call(u);if(v===b){u=u.call(this);}if(u===true){return w(this);
-}else{if(u===false||u==null){return i(this);}else{if(v==="[object Array]"){if(u.length>0){return d.helpers.each(u,l);}else{return i(this);}}else{return w(u);
-}}}});d.K=function(){};d.createFrame=Object.create||function(i){d.K.prototype=i;var l=new d.K();d.K.prototype=null;return l;};d.logger={DEBUG:0,INFO:1,WARN:2,ERROR:3,level:3,methodMap:{0:"debug",1:"info",2:"warn",3:"error"},log:function(u,i){if(d.logger.level<=u){var l=d.logger.methodMap[u];
-if(typeof console!=="undefined"&&console[l]){console[l].call(console,i);}}}};d.log=function(l,i){d.logger.log(l,i);};d.registerHelper("each",function(l,C){var A=C.fn,v=C.inverse;
-var x=0,y="",w;var z=n.call(l);if(z===b){l=l.call(this);}if(C.data){w=d.createFrame(C.data);}if(l&&typeof l==="object"){if(l instanceof Array){for(var u=l.length;
-x2){G.push("'"+this.terminals_[Q]+"'");}}if(this.lexer.showPosition){S="Parse error on line "+(J+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+G.join(", ")+", got '"+(this.terminals_[V]||V)+"'";
-}else{S="Parse error on line "+(J+1)+": Unexpected "+(V==1?"end of input":"'"+(this.terminals_[V]||V)+"'");}this.parseError(S,{text:this.lexer.match,token:this.terminals_[V]||V,line:this.lexer.yylineno,loc:B,expected:G});
-}}if(U[0] instanceof Array&&U.length>1){throw new Error("Parse Error: multiple actions possible at state: "+E+", token: "+V);}switch(U[0]){case 1:F.push(V);
-Y.push(this.lexer.yytext);K.push(this.lexer.yylloc);F.push(U[1]);V=null;if(!R){W=this.lexer.yyleng;A=this.lexer.yytext;J=this.lexer.yylineno;B=this.lexer.yylloc;
-if(C>0){C--;}}else{V=R;R=null;}break;case 2:X=this.productions_[U[1]][1];T.$=Y[Y.length-X];T._$={first_line:K[K.length-(X||1)].first_line,last_line:K[K.length-1].last_line,first_column:K[K.length-(X||1)].first_column,last_column:K[K.length-1].last_column};
-if(D){T._$.range=[K[K.length-(X||1)].range[0],K[K.length-1].range[1]];}L=this.performAction.call(T,A,W,J,this.yy,U[1],Y,K);if(typeof L!=="undefined"){return L;
-}if(X){F=F.slice(0,-1*X*2);Y=Y.slice(0,-1*X);K=K.slice(0,-1*X);}F.push(this.productions_[U[1]][0]);Y.push(T.$);K.push(T._$);z=Z[F[F.length-2]][F[F.length-1]];
-F.push(z);break;case 3:return true;}}return true;}};var i=(function(){var C=({EOF:1,parseError:function E(H,G){if(this.yy.parser){this.yy.parser.parseError(H,G);
-}else{throw new Error(H);}},setInput:function(G){this._input=G;this._more=this._less=this.done=false;this.yylineno=this.yyleng=0;this.yytext=this.matched=this.match="";
-this.conditionStack=["INITIAL"];this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0};if(this.options.ranges){this.yylloc.range=[0,0];}this.offset=0;
-return this;},input:function(){var H=this._input[0];this.yytext+=H;this.yyleng++;this.offset++;this.match+=H;this.matched+=H;var G=H.match(/(?:\r\n?|\n).*/g);
-if(G){this.yylineno++;this.yylloc.last_line++;}else{this.yylloc.last_column++;}if(this.options.ranges){this.yylloc.range[1]++;}this._input=this._input.slice(1);
-return H;},unput:function(I){var G=I.length;var H=I.split(/(?:\r\n?|\n)/g);this._input=I+this._input;this.yytext=this.yytext.substr(0,this.yytext.length-G-1);
-this.offset-=G;var K=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1);this.matched=this.matched.substr(0,this.matched.length-1);
-if(H.length-1){this.yylineno-=H.length-1;}var J=this.yylloc.range;this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:H?(H.length===K.length?this.yylloc.first_column:0)+K[K.length-H.length].length-H[0].length:this.yylloc.first_column-G};
-if(this.options.ranges){this.yylloc.range=[J[0],J[0]+this.yyleng-G];}return this;},more:function(){this._more=true;return this;},less:function(G){this.unput(this.match.slice(G));
-},pastInput:function(){var G=this.matched.substr(0,this.matched.length-this.match.length);return(G.length>20?"...":"")+G.substr(-20).replace(/\n/g,"");
-},upcomingInput:function(){var G=this.match;if(G.length<20){G+=this._input.substr(0,20-G.length);}return(G.substr(0,20)+(G.length>20?"...":"")).replace(/\n/g,"");
-},showPosition:function(){var G=this.pastInput();var H=new Array(G.length+1).join("-");return G+this.upcomingInput()+"\n"+H+"^";},next:function(){if(this.done){return this.EOF;
-}if(!this._input){this.done=true;}var M,K,H,J,I,G;if(!this._more){this.yytext="";this.match="";}var N=this._currentRules();for(var L=0;LK[0].length)){K=H;J=L;if(!this.options.flex){break;}}}if(K){G=K[0].match(/(?:\r\n?|\n).*/g);if(G){this.yylineno+=G.length;}this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:G?G[G.length-1].length-G[G.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+K[0].length};
-this.yytext+=K[0];this.match+=K[0];this.matches=K;this.yyleng=this.yytext.length;if(this.options.ranges){this.yylloc.range=[this.offset,this.offset+=this.yyleng];
-}this._more=false;this._input=this._input.slice(K[0].length);this.matched+=K[0];M=this.performAction.call(this,this.yy,this,N[J],this.conditionStack[this.conditionStack.length-1]);
-if(this.done&&this._input){this.done=false;}if(M){return M;}else{return;}}if(this._input===""){return this.EOF;}else{return this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno});
-}},lex:function z(){var G=this.next();if(typeof G!=="undefined"){return G;}else{return this.lex();}},begin:function A(G){this.conditionStack.push(G);},popState:function F(){return this.conditionStack.pop();
-},_currentRules:function D(){return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;},topState:function(){return this.conditionStack[this.conditionStack.length-2];
-},pushState:function A(G){this.begin(G);}});C.options={};C.performAction=function B(K,H,J,G){var I=G;switch(J){case 0:H.yytext="\\";return 14;break;case 1:if(H.yytext.slice(-1)!=="\\"){this.begin("mu");
-}if(H.yytext.slice(-1)==="\\"){H.yytext=H.yytext.substr(0,H.yyleng-1),this.begin("emu");}if(H.yytext){return 14;}break;case 2:return 14;break;case 3:if(H.yytext.slice(-1)!=="\\"){this.popState();
-}if(H.yytext.slice(-1)==="\\"){H.yytext=H.yytext.substr(0,H.yyleng-1);}return 14;break;case 4:H.yytext=H.yytext.substr(0,H.yyleng-4);this.popState();return 15;
-break;case 5:return 25;break;case 6:return 16;break;case 7:return 20;break;case 8:return 19;break;case 9:return 19;break;case 10:return 23;break;case 11:return 22;
-break;case 12:this.popState();this.begin("com");break;case 13:H.yytext=H.yytext.substr(3,H.yyleng-5);this.popState();return 15;break;case 14:return 22;
-break;case 15:return 37;break;case 16:return 36;break;case 17:return 36;break;case 18:return 40;break;case 19:break;case 20:this.popState();return 24;break;
-case 21:this.popState();return 18;break;case 22:H.yytext=H.yytext.substr(1,H.yyleng-2).replace(/\\"/g,'"');return 31;break;case 23:H.yytext=H.yytext.substr(1,H.yyleng-2).replace(/\\'/g,"'");
-return 31;break;case 24:return 38;break;case 25:return 33;break;case 26:return 33;break;case 27:return 32;break;case 28:return 36;break;case 29:H.yytext=H.yytext.substr(1,H.yyleng-2);
-return 36;break;case 30:return"INVALID";break;case 31:return 5;break;}};C.rules=[/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
-C.conditions={mu:{rules:[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],inclusive:false},emu:{rules:[3],inclusive:false},com:{rules:[4],inclusive:false},INITIAL:{rules:[0,1,2,31],inclusive:true}};
-return C;})();y.lexer=i;function w(){this.yy={};}w.prototype=y;y.Parser=w;return new w;})();d.Parser=r;d.parse=function(i){if(i.constructor===d.AST.ProgramNode){return i;
-}d.Parser.yy=d.AST;return d.Parser.parse(i);};d.AST={};d.AST.ProgramNode=function(l,i){this.type="program";this.statements=l;if(i){this.inverse=new d.AST.ProgramNode(i);
-}};d.AST.MustacheNode=function(x,u,l){this.type="mustache";this.escaped=!l;this.hash=u;var w=this.id=x[0];var v=this.params=x.slice(1);var i=this.eligibleHelper=w.isSimple;
-this.isHelper=i&&(v.length||u);};d.AST.PartialNode=function(i,l){this.type="partial";this.partialName=i;this.context=l;};d.AST.BlockNode=function(u,l,i,w){var v=function(x,y){if(x.original!==y.original){throw new d.Exception(x.original+" doesn't match "+y.original);
-}};v(u.id,w);this.type="block";this.mustache=u;this.program=l;this.inverse=i;if(this.inverse&&!this.program){this.isInverse=true;}};d.AST.ContentNode=function(i){this.type="content";
-this.string=i;};d.AST.HashNode=function(i){this.type="hash";this.pairs=i;};d.AST.IdNode=function(z){this.type="ID";var y="",w=[],A=0;for(var x=0,u=z.length;
-x0){throw new d.Exception("Invalid path: "+y);}else{if(v===".."){A++;
-}else{this.isScoped=true;}}}else{w.push(v);}}this.original=y;this.parts=w;this.string=w.join(".");this.depth=A;this.isSimple=z.length===1&&!this.isScoped&&A===0;
-this.stringModeValue=this.string;};d.AST.PartialNameNode=function(i){this.type="PARTIAL_NAME";this.name=i.original;};d.AST.DataNode=function(i){this.type="DATA";
-this.id=i;};d.AST.StringNode=function(i){this.type="STRING";this.original=this.string=this.stringModeValue=i;};d.AST.IntegerNode=function(i){this.type="INTEGER";
-this.original=this.integer=i;this.stringModeValue=Number(i);};d.AST.BooleanNode=function(i){this.type="BOOLEAN";this.bool=i;this.stringModeValue=i==="true";
-};d.AST.CommentNode=function(i){this.type="comment";this.comment=i;};var q=["description","fileName","lineNumber","message","name","number","stack"];d.Exception=function(u){var l=Error.prototype.constructor.apply(this,arguments);
-for(var i=0;i":">",'"':""","'":"'","`":"`"};var e=/[&<>"'`]/g;var p=/[&<>"'`]/;var t=function(i){return k[i]||"&";
-};d.Utils={extend:function(u,l){for(var i in l){if(l.hasOwnProperty(i)){u[i]=l[i];}}},escapeExpression:function(i){if(i instanceof d.SafeString){return i.toString();
-}else{if(i==null||i===false){return"";}}i=i.toString();if(!p.test(i)){return i;}return i.replace(e,t);},isEmpty:function(i){if(!i&&i!==0){return true;}else{if(n.call(i)==="[object Array]"&&i.length===0){return true;
-}else{return false;}}}};var j=d.Compiler=function(){};var g=d.JavaScriptCompiler=function(){};j.prototype={compiler:j,disassemble:function(){var z=this.opcodes,y,w=[],B,A;
-for(var x=0,u=z.length;x0){this.source[1]=this.source[1]+", "+w.join(", ");}if(!this.isChild){for(var A in this.context.aliases){if(this.context.aliases.hasOwnProperty(A)){this.source[1]=this.source[1]+", "+A+"="+this.context.aliases[A];
-}}}if(this.source[1]){this.source[1]="var "+this.source[1].substring(2)+";";}if(!this.isChild){this.source[1]+="\n"+this.context.programs.join("\n")+"\n";
-}if(!this.environment.isSimple){this.source.push("return buffer;");}var y=this.isChild?["depth0","data"]:["Handlebars","depth0","helpers","partials","data"];
-for(var z=0,x=this.environment.depths.list.length;zthis.stackVars.length){this.stackVars.push("stack"+this.stackSlot);}return this.topStackName();},topStackName:function(){return"stack"+this.stackSlot;
-},flushInline:function(){var v=this.inlineStack;if(v.length){this.inlineStack=[];for(var u=0,l=v.length;u');
- $('#' + settings.modalWindowDiv).prepend('
<\/div>');
- $('#' + settings.overlayDiv).hide();
- }
-
- if(settings.slideMap === true){
- //Let's hide the map container to begin
- $this.hide();
- }
-
- //Calculate geocode distance functions - you could use Google's distance service instead
- var GeoCodeCalc = {};
- if(settings.lengthUnit === "km"){
- //Kilometers
- GeoCodeCalc.EarthRadius = 6367.0;
- }
- else{
- //Default is miles
- GeoCodeCalc.EarthRadius = 3956.0;
- }
- GeoCodeCalc.ToRadian = function(v) { return v * (Math.PI / 180);};
- GeoCodeCalc.DiffRadian = function(v1, v2) {
- return GeoCodeCalc.ToRadian(v2) - GeoCodeCalc.ToRadian(v1);
- };
- GeoCodeCalc.CalcDistance = function(lat1, lng1, lat2, lng2, radius) {
- return radius * 2 * Math.asin( Math.min(1, Math.sqrt( ( Math.pow(Math.sin((GeoCodeCalc.DiffRadian(lat1, lat2)) / 2.0), 2.0) + Math.cos(GeoCodeCalc.ToRadian(lat1)) * Math.cos(GeoCodeCalc.ToRadian(lat2)) * Math.pow(Math.sin((GeoCodeCalc.DiffRadian(lng1, lng2)) / 2.0), 2.0) ) ) ) );
- };
-
- start();
-
- function start(){
- //If a default location is set
- if(settings.defaultLoc === true){
- //The address needs to be determined for the directions link
- var r = new ReverseGoogleGeocode();
- var latlng = new google.maps.LatLng(settings.defaultLat, settings.defaultLng);
- r.geocode(latlng, function(data) {
- if(data !== null) {
- var originAddress = data.address;
- mapping(settings.defaultLat, settings.defaultLng, originAddress);
- } else {
- //Unable to geocode
- alert(settings.addressErrorAlert);
- }
- });
- }
-
- //If show full map option is true
- if(settings.fullMapStart === true){
- //Just do the mapping without an origin
- mapping();
- }
-
- //HTML5 geolocation API option
- if(settings.autoGeocode === true){
- if (navigator.geolocation) {
- navigator.geolocation.getCurrentPosition(autoGeocode_query, autoGeocode_error);
- }
- }
- }
-
- //Geocode function for the origin location
- function GoogleGeocode(){
- geocoder = new google.maps.Geocoder();
- this.geocode = function(address, callbackFunction) {
- geocoder.geocode( { 'address': address}, function(results, status) {
- if (status === google.maps.GeocoderStatus.OK) {
- var result = {};
- result.latitude = results[0].geometry.location.lat();
- result.longitude = results[0].geometry.location.lng();
- callbackFunction(result);
- } else {
- alert(settings.geocodeErrorAlert + status);
- callbackFunction(null);
- }
- });
- };
- }
-
- //Reverse geocode to get address for automatic options needed for directions link
- function ReverseGoogleGeocode(){
- geocoder = new google.maps.Geocoder();
- this.geocode = function(latlng, callbackFunction) {
- geocoder.geocode( {'latLng': latlng}, function(results, status) {
- if (status === google.maps.GeocoderStatus.OK) {
- if (results[0]) {
- var result = {};
- result.address = results[0].formatted_address;
- callbackFunction(result);
- }
- } else {
- alert(settings.geocodeErrorAlert + status);
- callbackFunction(null);
- }
- });
- };
- }
-
- //Used to round miles to display
- function roundNumber(num, dec){
- return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
- }
-
- //If location is detected automatically
- function autoGeocode_query(position){
- //The address needs to be determined for the directions link
- var r = new ReverseGoogleGeocode();
- var latlng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
- r.geocode(latlng, function(data) {
- if(data !== null) {
- var originAddress = data.address;
- mapping(position.coords.latitude, position.coords.longitude, originAddress);
- } else {
- //Unable to geocode
- alert(settings.addressErrorAlert);
- }
- });
- }
-
- function autoGeocode_error(error){
- //If automatic detection doesn't work show an error
- alert(settings.autoGeocodeErrorAlert);
- }
-
- //Set up the normal mapping
- function begin_mapping(distance){
- //Get the user input and use it
- var userinput = $('#' + settings.inputID).val();
-
- if (userinput === ""){
- start();
- }
- else{
- var g = new GoogleGeocode();
- var address = userinput;
- g.geocode(address, function(data) {
- if(data !== null) {
- olat = data.latitude;
- olng = data.longitude;
- mapping(olat, olng, userinput, distance);
- } else {
- //Unable to geocode
- alert(settings.addressErrorAlert);
- }
- });
- }
- }
-
- //Process form input
- $(function(){
- //Handle form submission
- function get_form_values(e){
- //Stop the form submission
- e.preventDefault();
-
- if(settings.maxDistance === true){
- var maxDistance = $('#' + settings.maxDistanceID).val();
- //Start the mapping
- begin_mapping(maxDistance);
- }
- else{
- //Start the mapping
- begin_mapping();
- }
- }
-
- //ASP.net or regular submission?
- if(settings.noForm === true){
- $(document).on('click.'+prefix, '#' + settings.formContainerDiv + ' #submit', function(e){
- get_form_values(e);
- });
- $(document).on('keyup.'+prefix, function(e){
- if (e.keyCode === 13 && $('#' + settings.inputID).is(':focus')) {
- get_form_values(e);
- }
- });
- }
- else{
- $(document).on('submit.'+prefix, '#' + settings.formID, function(e){
- get_form_values(e);
- });
- }
- });
-
- //Now all the mapping stuff
- function mapping(orig_lat, orig_lng, origin, maxDistance){
- $(function(){
-
- // Enable the visual refresh https://developers.google.com/maps/documentation/javascript/basics#VisualRefresh
- google.maps.visualRefresh = true;
-
- var dataTypeRead;
-
- //KML is read as XML
- if(settings.dataType === 'kml'){
- dataTypeRead = "xml";
- }
- else{
- dataTypeRead = settings.dataType;
- }
-
- //Process the data
- $.ajax({
- type: "GET",
- url: settings.dataLocation + (settings.dataType === 'jsonp' ? (settings.dataLocation.match(/\?/) ? '&' : '?') + 'callback=?' : ''),
- dataType: dataTypeRead,
- jsonpCallback: (settings.dataType === 'jsonp' ? settings.jsonpCallback : null),
- beforeSend: function (){
- // Callback
- if(settings.callbackBeforeSend){
- settings.callbackBeforeSend.call(this);
- }
-
- //Loading
- if(settings.loading === true){
- $('#' + settings.formContainerDiv).append('
<\/div>');
- }
-
- },
- complete: function (event, request, options){
- // Callback
- if(settings.callbackComplete){
- settings.callbackComplete.call(this, event, request, options);
- }
-
- //Loading remove
- if(settings.loading === true){
- $('#' + settings.loadingDiv).remove();
- }
- },
- success: function (data, xhr, options){
- // Callback
- if(settings.callbackSuccess){
- settings.callbackSuccess.call(this, data, xhr, options);
- }
-
- //After the store locations file has been read successfully
- var i = 0;
- var firstRun;
-
- //Set a variable for fullMapStart so we can detect the first run
- if(settings.fullMapStart === true && $('#' + settings.mapDiv).hasClass('mapOpen') === false){
- firstRun = true;
- }
- else{
- reset();
- }
-
- $('#' + settings.mapDiv).addClass('mapOpen');
-
- //Depending on your data structure and what you want to include in the maps, you may need to change the following variables or comment them out
- if(settings.dataType === 'json' || settings.dataType === 'jsonp'){
- //Process JSON
- $.each(data, function(){
- var key, value, locationData = {};
-
- // Parse each data variables
- for( key in this ){
- value = this[key];
-
- if(key === 'web'){
- if ( value ) value = value.replace("http://",""); // Remove scheme (todo: should NOT be done)
- }
-
- locationData[key] = value;
- }
-
- if(!locationData['distance']){
- locationData['distance'] = GeoCodeCalc.CalcDistance(orig_lat,orig_lng,locationData['lat'],locationData['lng'], GeoCodeCalc.EarthRadius);
- }
-
- //Create the array
- if(settings.maxDistance === true && firstRun !== true && maxDistance){
- if(locationData['distance'] < maxDistance){
- locationset[i] = locationData;
- }
- else{
- return;
- }
- }
- else{
- locationset[i] = locationData;
- }
-
- i++;
- });
- }
- else if(settings.dataType === 'kml'){
- //Process KML
- $(data).find('Placemark').each(function(){
- var locationData = {
- 'name': $(this).find('name').text(),
- 'lat': $(this).find('coordinates').text().split(",")[1],
- 'lng': $(this).find('coordinates').text().split(",")[0],
- 'description': $(this).find('description').text()
- };
-
- locationData['distance'] = GeoCodeCalc.CalcDistance(orig_lat,orig_lng,locationData['lat'],locationData['lng'], GeoCodeCalc.EarthRadius);
-
- //Create the array
- if(settings.maxDistance === true && firstRun !== true && maxDistance){
- if(locationData['distance'] < maxDistance){
- locationset[i] = locationData;
- }
- else{
- return;
- }
- }
- else{
- locationset[i] = locationData;
- }
-
- i++;
- });
- }
- else{
- //Process XML
- $(data).find('marker').each(function(){
- var locationData = {
- 'name': $(this).attr('name'),
- 'lat': $(this).attr('lat'),
- 'lng': $(this).attr('lng'),
- 'address': $(this).attr('address'),
- 'address2': $(this).attr('address2'),
- 'city': $(this).attr('city'),
- 'state': $(this).attr('state'),
- 'postal': $(this).attr('postal'),
- 'country': $(this).attr('country'),
- 'phone': $(this).attr('phone'),
- 'email': $(this).attr('email'),
- 'web': $(this).attr('web'),
- 'hours1': $(this).attr('hours1'),
- 'hours2': $(this).attr('hours2'),
- 'hours3': $(this).attr('hours3'),
- 'category': $(this).attr('category'),
- 'featured': $(this).attr('featured')
- };
-
- if(locationData['web']) locationData['web'] = locationData['web'].replace("http://",""); // Remove scheme (todo: should NOT be done)
-
- locationData['distance'] = GeoCodeCalc.CalcDistance(orig_lat,orig_lng,locationData['lat'],locationData['lng'], GeoCodeCalc.EarthRadius);
-
- //Create the array
- if(settings.maxDistance === true && firstRun !== true && maxDistance){
- if(locationData['distance'] < maxDistance){
- locationset[i] = locationData;
- }
- else{
- return;
- }
- }
- else{
- locationset[i] = locationData;
- }
-
- i++;
- });
- }
-
- //Distance sorting function
- function sort_numerically(locationsarray){
- locationsarray.sort(function(a, b){
- return ((a['distance'] < b['distance']) ? -1 : ((a['distance'] > b['distance']) ? 1 : 0));
- });
- }
-
- //Sort the multi-dimensional array by distance
- sort_numerically(locationset);
-
- //Featured locations filtering
- if(settings.featuredLocations === true){
- //Create array for featured locations
- featuredset = $.grep(locationset, function(val, i){
- return val['featured'] === "true";
- });
-
- //Create array for normal locations
- normalset = $.grep(locationset, function(val, i){
- return val['featured'] !== "true";
- });
-
- //Combine the arrays
- locationset = [];
- locationset = featuredset.concat(normalset);
- }
-
- //Get the length unit
- var distUnit = (settings.lengthUnit === "km") ? settings.kilometersLang : settings.milesLang ;
-
- //Check the closest marker
- if(settings.maxDistance === true && firstRun !== true && maxDistance){
- if(locationset[0] === undefined || locationset[0]['distance'] > maxDistance){
- alert(settings.distanceErrorAlert + maxDistance + " " + distUnit);
- return;
- }
- }
- else{
- if(settings.distanceAlert !== -1 && locationset[0]['distance'] > settings.distanceAlert){
- alert(settings.distanceErrorAlert + settings.distanceAlert + " " + distUnit);
- }
- }
-
- //Create the map with jQuery
- $(function(){
-
- var key, value, locationData = {};
-
- //Instead of repeating the same thing twice below
- function create_location_variables(loopcount){
- for ( key in locationset[loopcount] ) {
- value = locationset[loopcount][key];
-
- if(key === 'distance'){
- value = roundNumber(value,2);
- }
-
- locationData[key] = value;
- }
- }
-
- //Define the location data for the templates
- function define_location_data(currentMarker){
- create_location_variables(currentMarker.get("id"));
-
- var distLength;
- if(locationData['distance'] <= 1){
- if(settings.lengthUnit === "km"){
- distLength = settings.kilometerLang;
- }
- else{
- distLength = settings.mileLang;
- }
- }
- else{
- if(settings.lengthUnit === "km"){
- distLength = settings.kilometersLang;
- }
- else{
- distLength = settings.milesLang;
- }
- }
-
- //Set up alpha character
- var markerId = currentMarker.get("id");
- //Use dot markers instead of alpha if there are more than 26 locations
- if(settings.storeLimit === -1 || settings.storeLimit > 26){
- var indicator = markerId + 1;
- }
- else{
- var indicator = String.fromCharCode("A".charCodeAt(0) + markerId);
- }
-
- //Define location data
- var locations = {
- location: [$.extend(locationData, {
- 'markerid': markerId,
- 'marker': indicator,
- 'length': distLength,
- 'origin': origin
- })]
- };
-
- return locations;
- }
-
- //Slide in the map container
- if(settings.slideMap === true){
- $this.slideDown();
- }
- //Set up the modal window
- if(settings.modalWindow === true){
- // Callback
- if (settings.callbackModalOpen){
- settings.callbackModalOpen.call(this);
- }
-
- function modalClose(){
- // Callback
- if (settings.callbackModalOpen){
- settings.callbackModalOpen.call(this);
- }
-
- $('#' + settings.overlayDiv).hide();
- }
-
- //Pop up the modal window
- $('#' + settings.overlayDiv).fadeIn();
- //Close modal when close icon is clicked and when background overlay is clicked
- $(document).on('click.'+prefix, '#' + settings.modalCloseIconDiv + ', #' + settings.overlayDiv, function(){
- modalClose();
- });
- //Prevent clicks within the modal window from closing the entire thing
- $(document).on('click.'+prefix, '#' + settings.modalWindowDiv, function(e){
- e.stopPropagation();
- });
- //Close modal when escape key is pressed
- $(document).on('keyup.'+prefix, function(e){
- if (e.keyCode === 27) {
- modalClose();
- }
- });
- }
-
- //Google maps settings
- if((settings.fullMapStart === true && firstRun === true) || settings.zoomLevel === 0){
- var myOptions = {
- mapTypeId: google.maps.MapTypeId.ROADMAP
- };
- var bounds = new google.maps.LatLngBounds ();
- }
- else{
- var myOptions = {
- zoom: settings.zoomLevel,
- center: new google.maps.LatLng(orig_lat, orig_lng),
- mapTypeId: google.maps.MapTypeId.ROADMAP
- };
- }
-
- var map = new google.maps.Map(document.getElementById(settings.mapDiv),myOptions);
- $this.data('map', map);
-
- //Create one infowindow to fill later
- var infowindow = new google.maps.InfoWindow();
-
- //Avoid error if number of locations is less than the default of 26
- if(settings.storeLimit === -1 || (locationset.length-1) < settings.storeLimit-1){
- storenum = locationset.length-1;
- }
- else{
- storenum = settings.storeLimit-1;
- }
-
- //Add origin marker if the setting is set
- if(settings.originMarker === true && settings.fullMapStart === false){
- var originPoint = new google.maps.LatLng(orig_lat, orig_lng);
- var marker = new google.maps.Marker({
- position: originPoint,
- map: map,
- icon: 'http://maps.google.com/mapfiles/ms/icons/'+ settings.originpinColor +'-dot.png',
- draggable: false
- });
- }
-
- //Add markers and infowindows loop
- for(var y = 0; y <= storenum; y++) {
- var letter = String.fromCharCode("A".charCodeAt(0) + y);
- var point = new google.maps.LatLng(locationset[y]['lat'], locationset[y]['lng']);
- marker = createMarker(point, locationset[y]['name'], locationset[y]['address'], letter );
- marker.set("id", y);
- markers[y] = marker;
- if((settings.fullMapStart === true && firstRun === true) || settings.zoomLevel === 0){
- bounds.extend(point);
- }
- //Pass variables to the pop-up infowindows
- create_infowindow(marker);
- }
-
- //Center and zoom if no origin or zoom was provided
- if((settings.fullMapStart === true && firstRun === true) || settings.zoomLevel === 0){
- map.fitBounds(bounds);
- }
-
- //Create the links that focus on the related marker
- $("#" + settings.listDiv + ' ul').empty();
- $(markers).each(function(x, marker){
- var letter = String.fromCharCode("A".charCodeAt(0) + x);
- //This needs to happen outside the loop or there will be a closure problem with creating the infowindows attached to the list click
- var currentMarker = markers[x];
- listClick(currentMarker);
- });
-
- function listClick(marker){
- //Define the location data
- var locations = define_location_data(marker);
-
- //Set up the list template with the location data
- var listHtml = listTemplate(locations);
- $('#' + settings.listDiv + ' ul').append(listHtml);
- }
-
- //Handle clicks from the list
- $(document).on('click.'+prefix, '#' + settings.listDiv + ' li', function(){
- var markerId = $(this).data('markerid');
-
- var selectedMarker = markers[markerId];
-
- //Focus on the list
- $('#' + settings.listDiv + ' li').removeClass('list-focus');
- $('#' + settings.listDiv + ' li[data-markerid=' + markerId +']').addClass('list-focus');
-
- map.panTo(selectedMarker.getPosition());
- var listLoc = "left";
- if(settings.bounceMarker === true){
- selectedMarker.setAnimation(google.maps.Animation.BOUNCE);
- setTimeout(function() { selectedMarker.setAnimation(null); create_infowindow(selectedMarker, listLoc); }, 700);
- }
- else{
- create_infowindow(selectedMarker, listLoc);
- }
- });
-
- //Add the list li background colors
- $("#" + settings.listDiv + " ul li:even").css('background', "#" + settings.listColor1);
- $("#" + settings.listDiv + " ul li:odd").css('background', "#" + settings.listColor2);
-
- //Custom marker function - alphabetical
- function createMarker(point, name, address, letter){
- //Set up pin icon with the Google Charts API for all of our markers
- var pinImage = new google.maps.MarkerImage("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=" + letter + "|" + settings.pinColor + "|" + settings.pinTextColor,
- new google.maps.Size(21, 34),
- new google.maps.Point(0,0),
- new google.maps.Point(10, 34));
-
- //Create the markers
- if(settings.storeLimit === -1 || settings.storeLimit > 26){
- var marker = new google.maps.Marker({
- position: point,
- map: map,
- draggable: false
- });
- }
- else{
- var marker = new google.maps.Marker({
- position: point,
- map: map,
- icon: pinImage,
- draggable: false
- });
- }
-
- return marker;
- }
-
- //Infowindows
- function create_infowindow(marker, location){
-
- //Define the location data
- var locations = define_location_data(marker);
-
- //Set up the infowindow template with the location data
- var formattedAddress = infowindowTemplate(locations);
-
- //Opens the infowindow when list item is clicked
- if(location === "left"){
- infowindow.setContent(formattedAddress);
- infowindow.open(marker.get('map'), marker);
- }
- //Opens the infowindow when the marker is clicked
- else{
- google.maps.event.addListener(marker, 'click', function() {
- infowindow.setContent(formattedAddress);
- infowindow.open(marker.get('map'), marker);
- //Focus on the list
- $('#' + settings.listDiv + ' li').removeClass('list-focus');
- markerId = marker.get("id");
- $('#' + settings.listDiv + ' li[data-markerid=' + markerId +']').addClass('list-focus');
-
- //Scroll list to selected marker
- var container = $('#' + settings.listDiv),scrollTo = $('#' + settings.listDiv + ' li[data-markerid=' + markerId +']');
- $('#' + settings.listDiv).animate({
- scrollTop: scrollTo.offset().top - container.offset().top + container.scrollTop()
- });
- });
- }
-
- }
-
- });
- }
- });
- });
- }
-
- }
-
- });
-};
-})(jQuery);
\ No newline at end of file
diff --git a/js/jquery.storelocator.min.js b/js/jquery.storelocator.min.js
deleted file mode 100644
index 0ca2967..0000000
--- a/js/jquery.storelocator.min.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-* storeLocator v1.4.9 - jQuery Google Maps Store Locator Plugin
-* (c) Copyright 2013, Bjorn Holine (http://www.bjornblog.com)
-* Released under the MIT license
-* Distance calculation function by Chris Pietschmann: http://pietschsoft.com/post/2008/02/01/Calculate-Distance-Between-Geocodes-in-C-and-JavaScript.aspx
-*/
-
-(function(a){a.fn.storeLocator=function(b){var c=a.extend({mapDiv:"map",listDiv:"loc-list",formContainerDiv:"form-container",formID:"user-location",inputID:"address",zoomLevel:12,pinColor:"fe7569",pinTextColor:"000000",lengthUnit:"m",storeLimit:26,distanceAlert:60,dataType:"xml",dataLocation:"locations.xml",listColor1:"ffffff",listColor2:"eeeeee",originMarker:false,originpinColor:"blue",bounceMarker:true,slideMap:true,modalWindow:false,overlayDiv:"overlay",modalWindowDiv:"modal-window",modalContentDiv:"modal-content",modalCloseIconDiv:"close-icon",defaultLoc:false,defaultLat:"",defaultLng:"",autoGeocode:false,maxDistance:false,maxDistanceID:"maxdistance",fullMapStart:false,noForm:false,loading:false,loadingDiv:"loading-map",featuredLocations:false,infowindowTemplatePath:"templates/infowindow-description.html",listTemplatePath:"templates/location-list-description.html",KMLinfowindowTemplatePath:"templates/kml-infowindow-description.html",KMLlistTemplatePath:"templates/kml-location-list-description.html",callbackBeforeSend:null,callbackComplete:null,callbackSuccess:null,callbackModalOpen:null,callbackModalClose:null,jsonpCallback:null,geocodeErrorAlert:"Geocode was not successful for the following reason: ",addressErrorAlert:"Unable to find address",autoGeocodeErrorAlert:"Automatic location detection failed. Please fill in your address or zip code.",distanceErrorAlert:"Unfortunately, our closest location is more than ",mileLang:"mile",milesLang:"miles",kilometerLang:"kilometer",kilometersLang:"kilometers"},b);
-return this.each(function(){var h=a(this);var e,g;f();function f(){if(c.dataType==="kml"){a.get(c.KMLinfowindowTemplatePath,function(i){var j=i;g=Handlebars.compile(j);
-});a.get(c.KMLlistTemplatePath,function(i){var j=i;e=Handlebars.compile(j);d();});}else{a.get(c.infowindowTemplatePath,function(i){var j=i;g=Handlebars.compile(j);
-});a.get(c.listTemplatePath,function(i){var j=i;e=Handlebars.compile(j);d();});}}function d(){var i,y,s,o,B,m;var r=[];var k=[];var w=[];var x=[];var u="storeLocator";
-function z(){r=[];k=[];w=[];x=[];a(document).off("click."+u,"#"+c.listDiv+" li");}if(c.modalWindow===true){h.wrap('
');
-a("#"+c.modalWindowDiv).prepend('
');a("#"+c.overlayDiv).hide();}if(c.slideMap===true){h.hide();}var l={};if(c.lengthUnit==="km"){l.EarthRadius=6367;
-}else{l.EarthRadius=3956;}l.ToRadian=function(D){return D*(Math.PI/180);};l.DiffRadian=function(E,D){return l.ToRadian(D)-l.ToRadian(E);};l.CalcDistance=function(H,G,F,E,D){return D*2*Math.asin(Math.min(1,Math.sqrt((Math.pow(Math.sin((l.DiffRadian(H,F))/2),2)+Math.cos(l.ToRadian(H))*Math.cos(l.ToRadian(F))*Math.pow(Math.sin((l.DiffRadian(G,E))/2),2)))));
-};j();function j(){if(c.defaultLoc===true){var D=new C();var E=new google.maps.LatLng(c.defaultLat,c.defaultLng);D.geocode(E,function(G){if(G!==null){var F=G.address;
-p(c.defaultLat,c.defaultLng,F);}else{alert(c.addressErrorAlert);}});}if(c.fullMapStart===true){p();}if(c.autoGeocode===true){if(navigator.geolocation){navigator.geolocation.getCurrentPosition(q,A);
-}}}function v(){geocoder=new google.maps.Geocoder();this.geocode=function(D,E){geocoder.geocode({address:D},function(H,G){if(G===google.maps.GeocoderStatus.OK){var F={};
-F.latitude=H[0].geometry.location.lat();F.longitude=H[0].geometry.location.lng();E(F);}else{alert(c.geocodeErrorAlert+G);E(null);}});};}function C(){geocoder=new google.maps.Geocoder();
-this.geocode=function(E,D){geocoder.geocode({latLng:E},function(H,G){if(G===google.maps.GeocoderStatus.OK){if(H[0]){var F={};F.address=H[0].formatted_address;
-D(F);}}else{alert(c.geocodeErrorAlert+G);D(null);}});};}function t(D,E){return Math.round(D*Math.pow(10,E))/Math.pow(10,E);}function q(D){var E=new C();
-var F=new google.maps.LatLng(D.coords.latitude,D.coords.longitude);E.geocode(F,function(H){if(H!==null){var G=H.address;p(D.coords.latitude,D.coords.longitude,G);
-}else{alert(c.addressErrorAlert);}});}function A(D){alert(c.autoGeocodeErrorAlert);}function n(G){var F=a("#"+c.inputID).val();if(F===""){j();}else{var E=new v();
-var D=F;E.geocode(D,function(H){if(H!==null){y=H.latitude;s=H.longitude;p(y,s,F,G);}else{alert(c.addressErrorAlert);}});}}a(function(){function D(F){F.preventDefault();
-if(c.maxDistance===true){var E=a("#"+c.maxDistanceID).val();n(E);}else{n();}}if(c.noForm===true){a(document).on("click."+u,"#"+c.formContainerDiv+" #submit",function(E){D(E);
-});a(document).on("keyup."+u,function(E){if(E.keyCode===13&&a("#"+c.inputID).is(":focus")){D(E);}});}else{a(document).on("submit."+u,"#"+c.formID,function(E){D(E);
-});}});function p(D,F,E,G){a(function(){google.maps.visualRefresh=true;var H;if(c.dataType==="kml"){H="xml";}else{H=c.dataType;}a.ajax({type:"GET",url:c.dataLocation+(c.dataType==="jsonp"?(c.dataLocation.match(/\?/)?"&":"?")+"callback=?":""),dataType:H,jsonpCallback:(c.dataType==="jsonp"?c.jsonpCallback:null),beforeSend:function(){if(c.callbackBeforeSend){c.callbackBeforeSend.call(this);
-}if(c.loading===true){a("#"+c.formContainerDiv).append('
');}},complete:function(K,J,I){if(c.callbackComplete){c.callbackComplete.call(this,K,J,I);
-}if(c.loading===true){a("#"+c.loadingDiv).remove();}},success:function(L,N,I){if(c.callbackSuccess){c.callbackSuccess.call(this,L,N,I);}var K=0;var M;if(c.fullMapStart===true&&a("#"+c.mapDiv).hasClass("mapOpen")===false){M=true;
-}else{z();}a("#"+c.mapDiv).addClass("mapOpen");if(c.dataType==="json"||c.dataType==="jsonp"){a.each(L,function(){var P,Q,R={};for(P in this){Q=this[P];
-if(P==="web"){if(Q){Q=Q.replace("http://","");}}R[P]=Q;}if(!R.distance){R.distance=l.CalcDistance(D,F,R.lat,R.lng,l.EarthRadius);}if(c.maxDistance===true&&M!==true&&G){if(R.distance
Q.distance)?1:0));});}J(r);if(c.featuredLocations===true){k=a.grep(r,function(Q,P){return Q.featured==="true";
-});w=a.grep(r,function(Q,P){return Q.featured!=="true";});r=[];r=k.concat(w);}var O=(c.lengthUnit==="km")?c.kilometersLang:c.milesLang;if(c.maxDistance===true&&M!==true&&G){if(r[0]===undefined||r[0]["distance"]>G){alert(c.distanceErrorAlert+G+" "+O);
-return;}}else{if(c.distanceAlert!==-1&&r[0]["distance"]>c.distanceAlert){alert(c.distanceErrorAlert+c.distanceAlert+" "+O);}}a(function(){var ag,ac,V={};
-function W(ah){for(ag in r[ah]){ac=r[ah][ag];if(ag==="distance"){ac=t(ac,2);}V[ag]=ac;}}function R(al){W(al.get("id"));var aj;if(V.distance<=1){if(c.lengthUnit==="km"){aj=c.kilometerLang;
-}else{aj=c.mileLang;}}else{if(c.lengthUnit==="km"){aj=c.kilometersLang;}else{aj=c.milesLang;}}var ak=al.get("id");if(c.storeLimit===-1||c.storeLimit>26){var ai=ak+1;
-}else{var ai=String.fromCharCode("A".charCodeAt(0)+ak);}var ah={location:[a.extend(V,{markerid:ak,marker:ai,length:aj,origin:E})]};return ah;}if(c.slideMap===true){h.slideDown();
-}if(c.modalWindow===true){if(c.callbackModalOpen){c.callbackModalOpen.call(this);}function Q(){if(c.callbackModalOpen){c.callbackModalOpen.call(this);}a("#"+c.overlayDiv).hide();
-}a("#"+c.overlayDiv).fadeIn();a(document).on("click."+u,"#"+c.modalCloseIconDiv+", #"+c.overlayDiv,function(){Q();});a(document).on("click."+u,"#"+c.modalWindowDiv,function(ah){ah.stopPropagation();
-});a(document).on("keyup."+u,function(ah){if(ah.keyCode===27){Q();}});}if((c.fullMapStart===true&&M===true)||c.zoomLevel===0){var aa={mapTypeId:google.maps.MapTypeId.ROADMAP};
-var T=new google.maps.LatLngBounds();}else{var aa={zoom:c.zoomLevel,center:new google.maps.LatLng(D,F),mapTypeId:google.maps.MapTypeId.ROADMAP};}var ae=new google.maps.Map(document.getElementById(c.mapDiv),aa);
-h.data("map",ae);var Y=new google.maps.InfoWindow();if(c.storeLimit===-1||(r.length-1)<=m;X++){var af=String.fromCharCode("A".charCodeAt(0)+X);var ad=new google.maps.LatLng(r[X]["lat"],r[X]["lng"]);U=P(ad,r[X]["name"],r[X]["address"],af);
-U.set("id",X);x[X]=U;if((c.fullMapStart===true&&M===true)||c.zoomLevel===0){T.extend(ad);}S(U);}if((c.fullMapStart===true&&M===true)||c.zoomLevel===0){ae.fitBounds(T);
-}a("#"+c.listDiv+" ul").empty();a(x).each(function(ah,ai){var aj=String.fromCharCode("A".charCodeAt(0)+ah);var ak=x[ah];ab(ak);});function ab(ai){var ah=R(ai);
-var aj=e(ah);a("#"+c.listDiv+" ul").append(aj);}a(document).on("click."+u,"#"+c.listDiv+" li",function(){var aj=a(this).data("markerid");var ai=x[aj];a("#"+c.listDiv+" li").removeClass("list-focus");
-a("#"+c.listDiv+" li[data-markerid="+aj+"]").addClass("list-focus");ae.panTo(ai.getPosition());var ah="left";if(c.bounceMarker===true){ai.setAnimation(google.maps.Animation.BOUNCE);
-setTimeout(function(){ai.setAnimation(null);S(ai,ah);},700);}else{S(ai,ah);}});a("#"+c.listDiv+" ul li:even").css("background","#"+c.listColor1);a("#"+c.listDiv+" ul li:odd").css("background","#"+c.listColor2);
-function P(ah,ak,aj,am){var al=new google.maps.MarkerImage("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld="+am+"|"+c.pinColor+"|"+c.pinTextColor,new google.maps.Size(21,34),new google.maps.Point(0,0),new google.maps.Point(10,34));
-if(c.storeLimit===-1||c.storeLimit>26){var ai=new google.maps.Marker({position:ah,map:ae,draggable:false});}else{var ai=new google.maps.Marker({position:ah,map:ae,icon:al,draggable:false});
-}return ai;}function S(ak,aj){var ai=R(ak);var ah=g(ai);if(aj==="left"){Y.setContent(ah);Y.open(ak.get("map"),ak);}else{google.maps.event.addListener(ak,"click",function(){Y.setContent(ah);
-Y.open(ak.get("map"),ak);a("#"+c.listDiv+" li").removeClass("list-focus");markerId=ak.get("id");a("#"+c.listDiv+" li[data-markerid="+markerId+"]").addClass("list-focus");
-var al=a("#"+c.listDiv),am=a("#"+c.listDiv+" li[data-markerid="+markerId+"]");a("#"+c.listDiv).animate({scrollTop:am.offset().top-al.offset().top+al.scrollTop()});
-});}}});}});});}}});};})(jQuery);
\ No newline at end of file
diff --git a/json-example.html b/json-example.html
deleted file mode 100644
index 44a2c6b..0000000
--- a/json-example.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
- Map Example - JSON Data
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/kml-example.html b/kml-example.html
deleted file mode 100644
index 8ab54fa..0000000
--- a/kml-example.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
- Map Example - KML Data
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/libs/handlebars/handlebars-4.7.7.js b/libs/handlebars/handlebars-4.7.7.js
new file mode 100644
index 0000000..fb23399
--- /dev/null
+++ b/libs/handlebars/handlebars-4.7.7.js
@@ -0,0 +1,29 @@
+/**!
+
+ @license
+ handlebars v4.7.7
+
+ Copyright (C) 2011-2019 by Yehuda Katz
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+ */
+!function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define([],b):"object"==typeof exports?exports.Handlebars=b():a.Handlebars=b()}(this,function(){return function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={exports:{},id:d,loaded:!1};return a[d].call(e.exports,e,e.exports,b),e.loaded=!0,e.exports}var c={};return b.m=a,b.c=c,b.p="",b(0)}([function(a,b,c){"use strict";function d(){var a=r();return a.compile=function(b,c){return k.compile(b,c,a)},a.precompile=function(b,c){return k.precompile(b,c,a)},a.AST=i["default"],a.Compiler=k.Compiler,a.JavaScriptCompiler=m["default"],a.Parser=j.parser,a.parse=j.parse,a.parseWithoutProcessing=j.parseWithoutProcessing,a}var e=c(1)["default"];b.__esModule=!0;var f=c(2),g=e(f),h=c(45),i=e(h),j=c(46),k=c(51),l=c(52),m=e(l),n=c(49),o=e(n),p=c(44),q=e(p),r=g["default"].create,s=d();s.create=d,q["default"](s),s.Visitor=o["default"],s["default"]=s,b["default"]=s,a.exports=b["default"]},function(a,b){"use strict";b["default"]=function(a){return a&&a.__esModule?a:{"default":a}},b.__esModule=!0},function(a,b,c){"use strict";function d(){var a=new h.HandlebarsEnvironment;return n.extend(a,h),a.SafeString=j["default"],a.Exception=l["default"],a.Utils=n,a.escapeExpression=n.escapeExpression,a.VM=p,a.template=function(b){return p.template(b,a)},a}var e=c(3)["default"],f=c(1)["default"];b.__esModule=!0;var g=c(4),h=e(g),i=c(37),j=f(i),k=c(6),l=f(k),m=c(5),n=e(m),o=c(38),p=e(o),q=c(44),r=f(q),s=d();s.create=d,r["default"](s),s["default"]=s,b["default"]=s,a.exports=b["default"]},function(a,b){"use strict";b["default"]=function(a){if(a&&a.__esModule)return a;var b={};if(null!=a)for(var c in a)Object.prototype.hasOwnProperty.call(a,c)&&(b[c]=a[c]);return b["default"]=a,b},b.__esModule=!0},function(a,b,c){"use strict";function d(a,b,c){this.helpers=a||{},this.partials=b||{},this.decorators=c||{},i.registerDefaultHelpers(this),j.registerDefaultDecorators(this)}var e=c(1)["default"];b.__esModule=!0,b.HandlebarsEnvironment=d;var f=c(5),g=c(6),h=e(g),i=c(10),j=c(30),k=c(32),l=e(k),m=c(33),n="4.7.7";b.VERSION=n;var o=8;b.COMPILER_REVISION=o;var p=7;b.LAST_COMPATIBLE_COMPILER_REVISION=p;var q={1:"<= 1.0.rc.2",2:"== 1.0.0-rc.3",3:"== 1.0.0-rc.4",4:"== 1.x.x",5:"== 2.0.0-alpha.x",6:">= 2.0.0-beta.1",7:">= 4.0.0 <4.3.0",8:">= 4.3.0"};b.REVISION_CHANGES=q;var r="[object Object]";d.prototype={constructor:d,logger:l["default"],log:l["default"].log,registerHelper:function(a,b){if(f.toString.call(a)===r){if(b)throw new h["default"]("Arg not supported with multiple helpers");f.extend(this.helpers,a)}else this.helpers[a]=b},unregisterHelper:function(a){delete this.helpers[a]},registerPartial:function(a,b){if(f.toString.call(a)===r)f.extend(this.partials,a);else{if("undefined"==typeof b)throw new h["default"]('Attempting to register a partial called "'+a+'" as undefined');this.partials[a]=b}},unregisterPartial:function(a){delete this.partials[a]},registerDecorator:function(a,b){if(f.toString.call(a)===r){if(b)throw new h["default"]("Arg not supported with multiple decorators");f.extend(this.decorators,a)}else this.decorators[a]=b},unregisterDecorator:function(a){delete this.decorators[a]},resetLoggedPropertyAccesses:function(){m.resetLoggedProperties()}};var s=l["default"].log;b.log=s,b.createFrame=f.createFrame,b.logger=l["default"]},function(a,b){"use strict";function c(a){return k[a]}function d(a){for(var b=1;b":">",'"':""","'":"'","`":"`","=":"="},l=/[&<>"'`=]/g,m=/[&<>"'`=]/,n=Object.prototype.toString;b.toString=n;var o=function(a){return"function"==typeof a};o(/x/)&&(b.isFunction=o=function(a){return"function"==typeof a&&"[object Function]"===n.call(a)}),b.isFunction=o;var p=Array.isArray||function(a){return!(!a||"object"!=typeof a)&&"[object Array]"===n.call(a)};b.isArray=p},function(a,b,c){"use strict";function d(a,b){var c=b&&b.loc,g=void 0,h=void 0,i=void 0,j=void 0;c&&(g=c.start.line,h=c.end.line,i=c.start.column,j=c.end.column,a+=" - "+g+":"+i);for(var k=Error.prototype.constructor.call(this,a),l=0;l0?(c.ids&&(c.ids=[c.name]),a.helpers.each(b,c)):e(this);if(c.data&&c.ids){var g=d.createFrame(c.data);g.contextPath=d.appendContextPath(c.data.contextPath,c.name),c={data:g}}return f(b,c)})},a.exports=b["default"]},function(a,b,c){(function(d){"use strict";var e=c(13)["default"],f=c(1)["default"];b.__esModule=!0;var g=c(5),h=c(6),i=f(h);b["default"]=function(a){a.registerHelper("each",function(a,b){function c(b,c,d){l&&(l.key=b,l.index=c,l.first=0===c,l.last=!!d,m&&(l.contextPath=m+b)),k+=f(a[b],{data:l,blockParams:g.blockParams([a[b],b],[m+b,null])})}if(!b)throw new i["default"]("Must pass iterator to #each");var f=b.fn,h=b.inverse,j=0,k="",l=void 0,m=void 0;if(b.data&&b.ids&&(m=g.appendContextPath(b.data.contextPath,b.ids[0])+"."),g.isFunction(a)&&(a=a.call(this)),b.data&&(l=g.createFrame(b.data)),a&&"object"==typeof a)if(g.isArray(a))for(var n=a.length;j=0?b:parseInt(a,10)}return a},log:function(a){if(a=e.lookupLevel(a),"undefined"!=typeof console&&e.lookupLevel(e.level)<=a){var b=e.methodMap[a];console[b]||(b="log");for(var c=arguments.length,d=Array(c>1?c-1:0),f=1;f=v.LAST_COMPATIBLE_COMPILER_REVISION&&b<=v.COMPILER_REVISION)){if(b2&&v.push("'"+this.terminals_[s]+"'");x=this.lexer.showPosition?"Parse error on line "+(i+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+v.join(", ")+", got '"+(this.terminals_[n]||n)+"'":"Parse error on line "+(i+1)+": Unexpected "+(1==n?"end of input":"'"+(this.terminals_[n]||n)+"'"),this.parseError(x,{text:this.lexer.match,token:this.terminals_[n]||n,line:this.lexer.yylineno,loc:l,expected:v})}}if(q[0]instanceof Array&&q.length>1)throw new Error("Parse Error: multiple actions possible at state: "+p+", token: "+n);switch(q[0]){case 1:d.push(n),e.push(this.lexer.yytext),f.push(this.lexer.yylloc),d.push(q[1]),n=null,o?(n=o,o=null):(j=this.lexer.yyleng,h=this.lexer.yytext,i=this.lexer.yylineno,l=this.lexer.yylloc,k>0&&k--);break;case 2:if(t=this.productions_[q[1]][1],w.$=e[e.length-t],w._$={first_line:f[f.length-(t||1)].first_line,last_line:f[f.length-1].last_line,first_column:f[f.length-(t||1)].first_column,last_column:f[f.length-1].last_column},m&&(w._$.range=[f[f.length-(t||1)].range[0],f[f.length-1].range[1]]),r=this.performAction.call(w,h,j,i,this.yy,q[1],e,f),"undefined"!=typeof r)return r;t&&(d=d.slice(0,-1*t*2),e=e.slice(0,-1*t),f=f.slice(0,-1*t)),d.push(this.productions_[q[1]][0]),e.push(w.$),f.push(w._$),u=g[d[d.length-2]][d[d.length-1]],d.push(u);break;case 3:return!0}}return!0}},c=function(){var a={EOF:1,parseError:function(a,b){if(!this.yy.parser)throw new Error(a);this.yy.parser.parseError(a,b)},setInput:function(a){return this._input=a,this._more=this._less=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var a=this._input[0];this.yytext+=a,this.yyleng++,this.offset++,this.match+=a,this.matched+=a;var b=a.match(/(?:\r\n?|\n).*/g);return b?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),a},unput:function(a){var b=a.length,c=a.split(/(?:\r\n?|\n)/g);this._input=a+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-b-1),this.offset-=b;var d=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),c.length-1&&(this.yylineno-=c.length-1);var e=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:c?(c.length===d.length?this.yylloc.first_column:0)+d[d.length-c.length].length-c[0].length:this.yylloc.first_column-b},this.options.ranges&&(this.yylloc.range=[e[0],e[0]+this.yyleng-b]),this},more:function(){return this._more=!0,this},less:function(a){this.unput(this.match.slice(a))},pastInput:function(){var a=this.matched.substr(0,this.matched.length-this.match.length);return(a.length>20?"...":"")+a.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var a=this.match;return a.length<20&&(a+=this._input.substr(0,20-a.length)),(a.substr(0,20)+(a.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var a=this.pastInput(),b=new Array(a.length+1).join("-");return a+this.upcomingInput()+"\n"+b+"^"},next:function(){if(this.done)return this.EOF;this._input||(this.done=!0);var a,b,c,d,e;this._more||(this.yytext="",this.match="");for(var f=this._currentRules(),g=0;gb[0].length)||(b=c,d=g,this.options.flex));g++);return b?(e=b[0].match(/(?:\r\n?|\n).*/g),e&&(this.yylineno+=e.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:e?e[e.length-1].length-e[e.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+b[0].length},this.yytext+=b[0],this.match+=b[0],this.matches=b,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._input=this._input.slice(b[0].length),this.matched+=b[0],a=this.performAction.call(this,this.yy,this,f[d],this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),a?a:void 0):""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var a=this.next();return"undefined"!=typeof a?a:this.lex()},begin:function(a){this.conditionStack.push(a)},popState:function(){return this.conditionStack.pop()},_currentRules:function(){return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules},topState:function(){return this.conditionStack[this.conditionStack.length-2]},pushState:function(a){this.begin(a)}};return a.options={},a.performAction=function(a,b,c,d){function e(a,c){return b.yytext=b.yytext.substring(a,b.yyleng-c+a)}switch(c){case 0:if("\\\\"===b.yytext.slice(-2)?(e(0,1),this.begin("mu")):"\\"===b.yytext.slice(-1)?(e(0,1),this.begin("emu")):this.begin("mu"),b.yytext)return 15;break;case 1:return 15;case 2:return this.popState(),15;case 3:return this.begin("raw"),15;case 4:return this.popState(),"raw"===this.conditionStack[this.conditionStack.length-1]?15:(e(5,9),"END_RAW_BLOCK");case 5:return 15;case 6:return this.popState(),14;case 7:return 65;case 8:return 68;case 9:return 19;case 10:return this.popState(),this.begin("raw"),23;case 11:return 55;case 12:return 60;case 13:return 29;case 14:return 47;case 15:return this.popState(),44;case 16:return this.popState(),44;case 17:return 34;case 18:return 39;case 19:return 51;case 20:return 48;case 21:this.unput(b.yytext),this.popState(),this.begin("com");break;case 22:return this.popState(),14;case 23:return 48;case 24:return 73;case 25:return 72;case 26:return 72;case 27:return 87;case 28:break;case 29:return this.popState(),54;case 30:return this.popState(),33;case 31:return b.yytext=e(1,2).replace(/\\"/g,'"'),80;case 32:return b.yytext=e(1,2).replace(/\\'/g,"'"),80;case 33:return 85;case 34:return 82;case 35:return 82;case 36:return 83;case 37:return 84;case 38:return 81;case 39:return 75;case 40:return 77;case 41:return 72;case 42:return b.yytext=b.yytext.replace(/\\([\\\]])/g,"$1"),72;case 43:return"INVALID";case 44:return 5}},a.rules=[/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:\{\{\{\{(?=[^\/]))/,/^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/,/^(?:[^\x00]+?(?=(\{\{\{\{)))/,/^(?:[\s\S]*?--(~)?\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{\{\{)/,/^(?:\}\}\}\})/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#>)/,/^(?:\{\{(~)?#\*?)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^\s*(~)?\}\})/,/^(?:\{\{(~)?\s*else\s*(~)?\}\})/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{(~)?!--)/,/^(?:\{\{(~)?![\s\S]*?\}\})/,/^(?:\{\{(~)?\*?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)|])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:undefined(?=([~}\s)])))/,/^(?:null(?=([~}\s)])))/,/^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/,/^(?:as\s+\|)/,/^(?:\|)/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/,/^(?:\[(\\\]|[^\]])*\])/,/^(?:.)/,/^(?:$)/],a.conditions={mu:{rules:[7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],inclusive:!1},emu:{rules:[2],inclusive:!1},com:{rules:[6],inclusive:!1},raw:{rules:[3,4,5],inclusive:!1},INITIAL:{rules:[0,1,44],inclusive:!0}},a}();return b.lexer=c,a.prototype=b,b.Parser=a,new a}();b["default"]=c,a.exports=b["default"]},function(a,b,c){"use strict";function d(){var a=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];this.options=a}function e(a,b,c){void 0===b&&(b=a.length);var d=a[b-1],e=a[b-2];return d?"ContentStatement"===d.type?(e||!c?/\r?\n\s*?$/:/(^|\r?\n)\s*?$/).test(d.original):void 0:c}function f(a,b,c){void 0===b&&(b=-1);var d=a[b+1],e=a[b+2];return d?"ContentStatement"===d.type?(e||!c?/^\s*?\r?\n/:/^\s*?(\r?\n|$)/).test(d.original):void 0:c}function g(a,b,c){var d=a[null==b?0:b+1];if(d&&"ContentStatement"===d.type&&(c||!d.rightStripped)){var e=d.value;d.value=d.value.replace(c?/^\s+/:/^[ \t]*\r?\n?/,""),d.rightStripped=d.value!==e}}function h(a,b,c){var d=a[null==b?a.length-1:b-1];if(d&&"ContentStatement"===d.type&&(c||!d.leftStripped)){var e=d.value;return d.value=d.value.replace(c?/\s+$/:/[ \t]+$/,""),d.leftStripped=d.value!==e,d.leftStripped}}var i=c(1)["default"];b.__esModule=!0;var j=c(49),k=i(j);d.prototype=new k["default"],d.prototype.Program=function(a){var b=!this.options.ignoreStandalone,c=!this.isRootSeen;this.isRootSeen=!0;for(var d=a.body,i=0,j=d.length;i0)throw new q["default"]("Invalid path: "+d,{loc:c});".."===i&&f++}}return{type:"PathExpression",data:a,depth:f,parts:e,original:d,loc:c}}function j(a,b,c,d,e,f){var g=d.charAt(3)||d.charAt(2),h="{"!==g&&"&"!==g,i=/\*/.test(d);return{type:i?"Decorator":"MustacheStatement",path:a,params:b,hash:c,escaped:h,strip:e,loc:this.locInfo(f)}}function k(a,b,c,e){d(a,c),e=this.locInfo(e);var f={type:"Program",body:b,strip:{},loc:e};return{type:"BlockStatement",path:a.path,params:a.params,hash:a.hash,program:f,openStrip:{},inverseStrip:{},closeStrip:{},loc:e}}function l(a,b,c,e,f,g){e&&e.path&&d(a,e);var h=/\*/.test(a.open);b.blockParams=a.blockParams;var i=void 0,j=void 0;if(c){if(h)throw new q["default"]("Unexpected inverse block on decorator",c);c.chain&&(c.program.body[0].closeStrip=e.strip),j=c.strip,i=c.program}return f&&(f=i,i=b,b=f),{type:h?"DecoratorBlock":"BlockStatement",path:a.path,params:a.params,hash:a.hash,program:b,inverse:i,openStrip:a.strip,inverseStrip:j,closeStrip:e&&e.strip,loc:this.locInfo(g)}}function m(a,b){if(!b&&a.length){var c=a[0].loc,d=a[a.length-1].loc;c&&d&&(b={source:c.source,start:{line:c.start.line,column:c.start.column},end:{line:d.end.line,column:d.end.column}})}return{type:"Program",body:a,strip:{},loc:b}}function n(a,b,c,e){return d(a,c),{type:"PartialBlockStatement",name:a.path,params:a.params,hash:a.hash,program:b,openStrip:a.strip,closeStrip:c&&c.strip,loc:this.locInfo(e)}}var o=c(1)["default"];b.__esModule=!0,b.SourceLocation=e,b.id=f,b.stripFlags=g,b.stripComment=h,b.preparePath=i,b.prepareMustache=j,b.prepareRawBlock=k,b.prepareBlock=l,b.prepareProgram=m,b.preparePartialBlock=n;var p=c(6),q=o(p)},function(a,b,c){"use strict";function d(){}function e(a,b,c){if(null==a||"string"!=typeof a&&"Program"!==a.type)throw new l["default"]("You must pass a string or Handlebars AST to Handlebars.precompile. You passed "+a);b=b||{},"data"in b||(b.data=!0),b.compat&&(b.useDepths=!0);var d=c.parse(a,b),e=(new c.Compiler).compile(d,b);return(new c.JavaScriptCompiler).compile(e,b)}function f(a,b,c){function d(){var d=c.parse(a,b),e=(new c.Compiler).compile(d,b),f=(new c.JavaScriptCompiler).compile(e,b,void 0,!0);return c.template(f)}function e(a,b){return f||(f=d()),f.call(this,a,b)}if(void 0===b&&(b={}),null==a||"string"!=typeof a&&"Program"!==a.type)throw new l["default"]("You must pass a string or Handlebars AST to Handlebars.compile. You passed "+a);b=m.extend({},b),"data"in b||(b.data=!0),b.compat&&(b.useDepths=!0);var f=void 0;return e._setup=function(a){return f||(f=d()),f._setup(a)},e._child=function(a,b,c,e){return f||(f=d()),f._child(a,b,c,e)},e}function g(a,b){if(a===b)return!0;if(m.isArray(a)&&m.isArray(b)&&a.length===b.length){for(var c=0;c1)throw new l["default"]("Unsupported number of partial arguments: "+c.length,a);c.length||(this.options.explicitPartialContext?this.opcode("pushLiteral","undefined"):c.push({type:"PathExpression",parts:[],depth:0}));var d=a.name.original,e="SubExpression"===a.name.type;e&&this.accept(a.name),this.setupFullMustacheParams(a,b,void 0,!0);var f=a.indent||"";this.options.preventIndent&&f&&(this.opcode("appendContent",f),f=""),this.opcode("invokePartial",e,d,f),this.opcode("append")},PartialBlockStatement:function(a){this.PartialStatement(a)},MustacheStatement:function(a){this.SubExpression(a),a.escaped&&!this.options.noEscape?this.opcode("appendEscaped"):this.opcode("append")},Decorator:function(a){this.DecoratorBlock(a)},ContentStatement:function(a){a.value&&this.opcode("appendContent",a.value)},CommentStatement:function(){},SubExpression:function(a){h(a);var b=this.classifySexpr(a);"simple"===b?this.simpleSexpr(a):"helper"===b?this.helperSexpr(a):this.ambiguousSexpr(a)},ambiguousSexpr:function(a,b,c){var d=a.path,e=d.parts[0],f=null!=b||null!=c;this.opcode("getContext",d.depth),this.opcode("pushProgram",b),this.opcode("pushProgram",c),d.strict=!0,this.accept(d),this.opcode("invokeAmbiguous",e,f)},simpleSexpr:function(a){var b=a.path;b.strict=!0,this.accept(b),this.opcode("resolvePossibleLambda")},helperSexpr:function(a,b,c){var d=this.setupFullMustacheParams(a,b,c),e=a.path,f=e.parts[0];if(this.options.knownHelpers[f])this.opcode("invokeKnownHelper",d.length,f);else{if(this.options.knownHelpersOnly)throw new l["default"]("You specified knownHelpersOnly, but used the unknown helper "+f,a);e.strict=!0,e.falsy=!0,this.accept(e),this.opcode("invokeHelper",d.length,e.original,o["default"].helpers.simpleId(e))}},PathExpression:function(a){this.addDepth(a.depth),this.opcode("getContext",a.depth);var b=a.parts[0],c=o["default"].helpers.scopedId(a),d=!a.depth&&!c&&this.blockParamIndex(b);d?this.opcode("lookupBlockParam",d,a.parts):b?a.data?(this.options.data=!0,this.opcode("lookupData",a.depth,a.parts,a.strict)):this.opcode("lookupOnContext",a.parts,a.falsy,a.strict,c):this.opcode("pushContext")},StringLiteral:function(a){this.opcode("pushString",a.value)},NumberLiteral:function(a){this.opcode("pushLiteral",a.value)},BooleanLiteral:function(a){this.opcode("pushLiteral",a.value)},UndefinedLiteral:function(){this.opcode("pushLiteral","undefined")},NullLiteral:function(){this.opcode("pushLiteral","null")},Hash:function(a){var b=a.pairs,c=0,d=b.length;for(this.opcode("pushHash");c=0)return[b,e]}}}},function(a,b,c){"use strict";function d(a){this.value=a}function e(){}function f(a,b,c,d){var e=b.popStack(),f=0,g=c.length;for(a&&g--;f0&&(c+=", "+d.join(", "));var e=0;g(this.aliases).forEach(function(a){var d=b.aliases[a];d.children&&d.referenceCount>1&&(c+=", alias"+ ++e+"="+a,d.children[0]="alias"+e)}),this.lookupPropertyFunctionIsUsed&&(c+=", "+this.lookupPropertyFunctionVarDeclaration());var f=["container","depth0","helpers","partials","data"];(this.useBlockParams||this.useDepths)&&f.push("blockParams"),this.useDepths&&f.push("depths");var h=this.mergeSource(c);return a?(f.push(h),Function.apply(this,f)):this.source.wrap(["function(",f.join(","),") {\n ",h,"}"])},mergeSource:function(a){var b=this.environment.isSimple,c=!this.forceBuffer,d=void 0,e=void 0,f=void 0,g=void 0;return this.source.each(function(a){a.appendToBuffer?(f?a.prepend(" + "):f=a,g=a):(f&&(e?f.prepend("buffer += "):d=!0,g.add(";"),f=g=void 0),e=!0,b||(c=!1))}),c?f?(f.prepend("return "),g.add(";")):e||this.source.push('return "";'):(a+=", buffer = "+(d?"":this.initializeBuffer()),f?(f.prepend("return buffer + "),g.add(";")):this.source.push("return buffer;")),a&&this.source.prepend("var "+a.substring(2)+(d?"":";\n")),this.source.merge()},lookupPropertyFunctionVarDeclaration:function(){return"\n lookupProperty = container.lookupProperty || function(parent, propertyName) {\n if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {\n return parent[propertyName];\n }\n return undefined\n }\n ".trim()},blockValue:function(a){var b=this.aliasable("container.hooks.blockHelperMissing"),c=[this.contextName(0)];this.setupHelperArgs(a,0,c);var d=this.popStack();c.splice(1,0,d),this.push(this.source.functionCall(b,"call",c))},ambiguousBlockValue:function(){var a=this.aliasable("container.hooks.blockHelperMissing"),b=[this.contextName(0)];this.setupHelperArgs("",0,b,!0),this.flushInline();var c=this.topStack();b.splice(1,0,c),this.pushSource(["if (!",this.lastHelper,") { ",c," = ",this.source.functionCall(a,"call",b),"}"])},appendContent:function(a){this.pendingContent?a=this.pendingContent+a:this.pendingLocation=this.source.currentLocation,this.pendingContent=a},append:function(){if(this.isInline())this.replaceStack(function(a){return[" != null ? ",a,' : ""']}),this.pushSource(this.appendToBuffer(this.popStack()));else{var a=this.popStack();this.pushSource(["if (",a," != null) { ",this.appendToBuffer(a,void 0,!0)," }"]),this.environment.isSimple&&this.pushSource(["else { ",this.appendToBuffer("''",void 0,!0)," }"])}},appendEscaped:function(){this.pushSource(this.appendToBuffer([this.aliasable("container.escapeExpression"),"(",this.popStack(),")"]))},getContext:function(a){this.lastContext=a},pushContext:function(){this.pushStackLiteral(this.contextName(this.lastContext))},lookupOnContext:function(a,b,c,d){var e=0;d||!this.options.compat||this.lastContext?this.pushContext():this.push(this.depthedLookup(a[e++])),this.resolvePath("context",a,e,b,c)},lookupBlockParam:function(a,b){this.useBlockParams=!0,this.push(["blockParams[",a[0],"][",a[1],"]"]),this.resolvePath("context",b,1)},lookupData:function(a,b,c){a?this.pushStackLiteral("container.data(data, "+a+")"):this.pushStackLiteral("data"),this.resolvePath("data",b,0,!0,c)},resolvePath:function(a,b,c,d,e){var g=this;if(this.options.strict||this.options.assumeObjects)return void this.push(f(this.options.strict&&e,this,b,a));for(var h=b.length;cthis.stackVars.length&&this.stackVars.push("stack"+this.stackSlot),this.topStackName()},topStackName:function(){return"stack"+this.stackSlot},flushInline:function(){var a=this.inlineStack;this.inlineStack=[];for(var b=0,c=a.length;b <0&&(s[r]=t[r]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var o=0;for(r=Object.getOwnPropertySymbols(t);o<0&&Object.prototype.propertyIsEnumerable.call(t,r[o])&&(s[r[o]]=t[r[o]])}return s}class s{static isAdvancedMarkerAvailable(t){return google.maps.marker&&!0===t.getMapCapabilities().isAdvancedMarkersAvailable}static isAdvancedMarker(t){return google.maps.marker&&t instanceof google.maps.marker.AdvancedMarkerElement}static setMap(t,e){this.isAdvancedMarker(t)?t.map=e:t.setMap(e)}static getPosition(t){if(this.isAdvancedMarker(t)){if(t.position){if(t.position instanceof google.maps.LatLng)return t.position;if(t.position.lat&&t.position.lng)return new google.maps.LatLng(t.position.lat,t.position.lng)}return new google.maps.LatLng(null)}return t.getPosition()}static getVisible(t){return!!this.isAdvancedMarker(t)||t.getVisible()}}class r{constructor(t){let{markers:e,position:s}=t;this.markers=e,s&&(s instanceof google.maps.LatLng?this._position=s:this._position=new google.maps.LatLng(s))}get bounds(){if(0===this.markers.length&&!this._position)return;const t=new google.maps.LatLngBounds(this._position,this._position);for(const e of this.markers)t.extend(s.getPosition(e));return t}get position(){return this._position||this.bounds.getCenter()}get count(){return this.markers.filter((t=>s.getVisible(t))).length}push(t){this.markers.push(t)}delete(){this.marker&&(s.setMap(this.marker,null),this.marker=void 0),this.markers.length=0}}const o=(t,e,r,o)=>{const n=i(t.getBounds(),e,o);return r.filter((t=>n.contains(s.getPosition(t))))},i=(t,e,s)=>{const{northEast:r,southWest:o}=h(t,e),i=l({northEast:r,southWest:o},s);return c(i,e)},n=(t,e,s)=>{const r=i(t,e,s),o=r.getNorthEast(),n=r.getSouthWest();return[n.lng(),n.lat(),o.lng(),o.lat()]},a=(t,e)=>{const s=(e.lat-t.lat)*Math.PI/180,r=(e.lng-t.lng)*Math.PI/180,o=Math.sin(s/2),i=Math.sin(r/2),n=o*o+Math.cos(t.lat*Math.PI/180)*Math.cos(e.lat*Math.PI/180)*i*i;return 6371*(2*Math.atan2(Math.sqrt(n),Math.sqrt(1-n)))},h=(t,e)=>({northEast:e.fromLatLngToDivPixel(t.getNorthEast()),southWest:e.fromLatLngToDivPixel(t.getSouthWest())}),l=(t,e)=>{let{northEast:s,southWest:r}=t;return s.x+=e,s.y-=e,r.x-=e,r.y+=e,{northEast:s,southWest:r}},c=(t,e)=>{let{northEast:s,southWest:r}=t;const o=e.fromDivPixelToLatLng(r),i=e.fromDivPixelToLatLng(s);return new google.maps.LatLngBounds(o,i)};class u{constructor(t){let{maxZoom:e=16}=t;this.maxZoom=e}noop(t){let{markers:e}=t;return m(e)}}class p extends u{constructor(t){var{viewportPadding:s=60}=t;super(e(t,["viewportPadding"])),this.viewportPadding=60,this.viewportPadding=s}calculate(t){let{markers:e,map:s,mapCanvasProjection:r}=t;return s.getZoom()>=this.maxZoom?{clusters:this.noop({markers:e}),changed:!1}:{clusters:this.cluster({markers:o(s,r,e,this.viewportPadding),map:s,mapCanvasProjection:r})}}}const m=t=>t.map((t=>new r({position:s.getPosition(t),markers:[t]})));function d(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var g=function t(e,s){if(e===s)return!0;if(e&&s&&"object"==typeof e&&"object"==typeof s){if(e.constructor!==s.constructor)return!1;var r,o,i;if(Array.isArray(e)){if((r=e.length)!=s.length)return!1;for(o=r;0!=o--;)if(!t(e[o],s[o]))return!1;return!0}if(e.constructor===RegExp)return e.source===s.source&&e.flags===s.flags;if(e.valueOf!==Object.prototype.valueOf)return e.valueOf()===s.valueOf();if(e.toString!==Object.prototype.toString)return e.toString()===s.toString();if((r=(i=Object.keys(e)).length)!==Object.keys(s).length)return!1;for(o=r;0!=o--;)if(!Object.prototype.hasOwnProperty.call(s,i[o]))return!1;for(o=r;0!=o--;){var n=i[o];if(!t(e[n],s[n]))return!1}return!0}return e!=e&&s!=s},f=d(g);const k=[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];class w{static from(t){if(!(t instanceof ArrayBuffer))throw new Error("Data must be an instance of ArrayBuffer.");const[e,s]=new Uint8Array(t,0,2);if(219!==e)throw new Error("Data does not appear to be in a KDBush format.");const r=s>>4;if(1!==r)throw new Error(`Got v${r} data when expected v1.`);const o=k[15&s];if(!o)throw new Error("Unrecognized array type.");const[i]=new Uint16Array(t,2,1),[n]=new Uint32Array(t,4,1);return new w(n,i,o,t)}constructor(t,e=64,s=Float64Array,r){if(isNaN(t)||t<0)throw new Error(`Unpexpected numItems value: ${t}.`);this.numItems=+t,this.nodeSize=Math.min(Math.max(+e,2),65535),this.ArrayType=s,this.IndexArrayType=t<65536?Uint16Array:Uint32Array;const o=k.indexOf(this.ArrayType),i=2*t*this.ArrayType.BYTES_PER_ELEMENT,n=t*this.IndexArrayType.BYTES_PER_ELEMENT,a=(8-n%8)%8;if(o<0)throw new Error(`Unexpected typed array class: ${s}.`);r&&r instanceof ArrayBuffer?(this.data=r,this.ids=new this.IndexArrayType(this.data,8,t),this.coords=new this.ArrayType(this.data,8+n+a,2*t),this._pos=2*t,this._finished=!0):(this.data=new ArrayBuffer(8+i+n+a),this.ids=new this.IndexArrayType(this.data,8,t),this.coords=new this.ArrayType(this.data,8+n+a,2*t),this._pos=0,this._finished=!1,new Uint8Array(this.data,0,2).set([219,16+o]),new Uint16Array(this.data,2,1)[0]=e,new Uint32Array(this.data,4,1)[0]=t)}add(t,e){const s=this._pos>>1;return this.ids[s]=s,this.coords[this._pos++]=t,this.coords[this._pos++]=e,s}finish(){const t=this._pos>>1;if(t!==this.numItems)throw new Error(`Added ${t} items when expected ${this.numItems}.`);return y(this.ids,this.coords,this.nodeSize,0,this.numItems-1,0),this._finished=!0,this}range(t,e,s,r){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");const{ids:o,coords:i,nodeSize:n}=this,a=[0,o.length-1,0],h=[];for(;a.length;){const l=a.pop()||0,c=a.pop()||0,u=a.pop()||0;if(c-u<=n){for(let n=u;n<=c;n++){const a=i[2*n],l=i[2*n+1];a>=t&&a<=s&&l>=e&&l<=r&&h.push(o[n])}continue}const p=u+c>>1,m=i[2*p],d=i[2*p+1];m>=t&&m<=s&&d>=e&&d<=r&&h.push(o[p]),(0===l?t<=m:e<=d)&&(a.push(u),a.push(p-1),a.push(1-l)),(0===l?s>=m:r>=d)&&(a.push(p+1),a.push(c),a.push(1-l))}return h}within(t,e,s){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");const{ids:r,coords:o,nodeSize:i}=this,n=[0,r.length-1,0],a=[],h=s*s;for(;n.length;){const l=n.pop()||0,c=n.pop()||0,u=n.pop()||0;if(c-u<=i){for(let s=u;s<=c;s++)C(o[2*s],o[2*s+1],t,e)<=h&&a.push(r[s]);continue}const p=u+c>>1,m=o[2*p],d=o[2*p+1];C(m,d,t,e)<=h&&a.push(r[p]),(0===l?t-s<=m:e-s<=d)&&(n.push(u),n.push(p-1),n.push(1-l)),(0===l?t+s>=m:e+s>=d)&&(n.push(p+1),n.push(c),n.push(1-l))}return a}}function y(t,e,s,r,o,i){if(o-r<=s)return;const n=r+o>>1;M(t,e,n,r,o,i),y(t,e,s,r,n-1,1-i),y(t,e,s,n+1,o,1-i)}function M(t,e,s,r,o,i){for(;o>r;){if(o-r>600){const n=o-r+1,a=s-r+1,h=Math.log(n),l=.5*Math.exp(2*h/3),c=.5*Math.sqrt(h*l*(n-l)/n)*(a-n/2<0?-1:1);M(t,e,s,Math.max(r,Math.floor(s-a*l/n+c)),Math.min(o,Math.floor(s+(n-a)*l/n+c)),i)}const n=e[2*s+i];let a=r,h=o;for(v(t,e,r,s),e[2*o+i]>n&&v(t,e,r,o);an;)h--}e[2*r+i]===n?v(t,e,r,h):(h++,v(t,e,h,o)),h<=s&&(r=h+1),s<=h&&(o=h-1)}}function v(t,e,s,r){x(t,s,r),x(e,2*s,2*r),x(e,2*s+1,2*r+1)}function x(t,e,s){const r=t[e];t[e]=t[s],t[s]=r}function C(t,e,s,r){const o=t-s,i=e-r;return o*o+i*i}const P={minZoom:0,maxZoom:16,minPoints:2,radius:40,extent:512,nodeSize:64,log:!1,generateId:!1,reduce:null,map:t=>t},_=Math.fround||(E=new Float32Array(1),t=>(E[0]=+t,E[0]));var E;const A=3,b=5,L=6;class O{constructor(t){this.options=Object.assign(Object.create(P),t),this.trees=new Array(this.options.maxZoom+1),this.stride=this.options.reduce?7:6,this.clusterProps=[]}load(t){const{log:e,minZoom:s,maxZoom:r}=this.options;e&&console.time("total time");const o=`prepare ${t.length} points`;e&&console.time(o),this.points=t;const i=[];for(let e=0;e=s;t--){const s=+Date.now();n=this.trees[t]=this._createTree(this._cluster(n,t)),e&&console.log("z%d: %d clusters in %dms",t,n.numItems,+Date.now()-s)}return e&&console.timeEnd("total time"),this}getClusters(t,e){let s=((t[0]+180)%360+360)%360-180;const r=Math.max(-90,Math.min(90,t[1]));let o=180===t[2]?180:((t[2]+180)%360+360)%360-180;const i=Math.max(-90,Math.min(90,t[3]));if(t[2]-t[0]>=360)s=-180,o=180;else if(s>o){const t=this.getClusters([s,r,180,i],e),n=this.getClusters([-180,r,o,i],e);return t.concat(n)}const n=this.trees[this._limitZoom(e)],a=n.range(T(s),j(i),T(o),j(r)),h=n.data,l=[];for(const t of a){const e=this.stride*t;l.push(h[e+b]>1?Z(h,e,this.clusterProps):this.points[h[e+A]])}return l}getChildren(t){const e=this._getOriginId(t),s=this._getOriginZoom(t),r="No cluster with the specified id.",o=this.trees[s];if(!o)throw new Error(r);const i=o.data;if(e*this.stride>=i.length)throw new Error(r);const n=this.options.radius/(this.options.extent*Math.pow(2,s-1)),a=i[e*this.stride],h=i[e*this.stride+1],l=o.within(a,h,n),c=[];for(const e of l){const s=e*this.stride;i[s+4]===t&&c.push(i[s+b]>1?Z(i,s,this.clusterProps):this.points[i[s+A]])}if(0===c.length)throw new Error(r);return c}getLeaves(t,e,s){e=e||10,s=s||0;const r=[];return this._appendLeaves(r,t,e,s,0),r}getTile(t,e,s){const r=this.trees[this._limitZoom(t)],o=Math.pow(2,t),{extent:i,radius:n}=this.options,a=n/i,h=(s-a)/o,l=(s+1+a)/o,c={features:[]};return this._addTileFeatures(r.range((e-a)/o,h,(e+1+a)/o,l),r.data,e,s,o,c),0===e&&this._addTileFeatures(r.range(1-a/o,h,1,l),r.data,o,s,o,c),e===o-1&&this._addTileFeatures(r.range(0,h,a/o,l),r.data,-1,s,o,c),c.features.length?c:null}getClusterExpansionZoom(t){let e=this._getOriginZoom(t)-1;for(;e<=this.options.maxZoom;){const s=this.getChildren(t);if(e++,1!==s.length)break;t=s[0].properties.cluster_id}return e}_appendLeaves(t,e,s,r,o){const i=this.getChildren(e);for(const e of i){const i=e.properties;if(i&&i.cluster?o+i.point_count<=r?o+=i.point_count:o=this._appendLeaves(t,i.cluster_id,s,r,o):o1;let h,l,c;if(a)h=I(e,t,this.clusterProps),l=e[t],c=e[t+1];else{const s=this.points[e[t+A]];h=s.properties;const[r,o]=s.geometry.coordinates;l=T(r),c=j(o)}const u={type:1,geometry:[[Math.round(this.options.extent*(l*o-s)),Math.round(this.options.extent*(c*o-r))]],tags:h};let p;p=a||this.options.generateId?e[t+A]:this.points[e[t+A]].id,void 0!==p&&(u.id=p),i.features.push(u)}}_limitZoom(t){return Math.max(this.options.minZoom,Math.min(Math.floor(+t),this.options.maxZoom+1))}_cluster(t,e){const{radius:s,extent:r,reduce:o,minPoints:i}=this.options,n=s/(r*Math.pow(2,e)),a=t.data,h=[],l=this.stride;for(let s=0;s<=e)continue;a[s+2]=e;const r=a[s],c=a[s+1],u=t.within(a[s],a[s+1],n),p=a[s+b];let m=p;for(const t of u){const s=t*l;a[s+2]>e&&(m+=a[s+b])}if(m>p&&m>=i){let t,i=r*p,n=c*p,d=-1;const g=((s/l|0)<<5)+(e+1)+this.points.length;for(const r of u){const h=r*l;if(a[h+2]<=e)continue;a[h+2]=e;const c=a[h+b];i+=a[h]*c,n+=a[h+1]*c,a[h+4]=g,o&&(t||(t=this._map(a,s,!0),d=this.clusterProps.length,this.clusterProps.push(t)),o(t,this._map(a,h)))}a[s+4]=g,h.push(i/m,n/m,1/0,g,-1,m),o&&h.push(d)}else{for(let t=0;t1)for(const t of u){const s=t*l;if(!(a[s+2]<=e)){a[s+2]=e;for(let t=0;t>5}_getOriginZoom(t){return(t-this.points.length)%32}_map(t,e,s){if(t[e+b]>1){const r=this.clusterProps[t[e+L]];return s?Object.assign({},r):r}const r=this.points[t[e+A]].properties,o=this.options.map(r);return s&&o===r?Object.assign({},o):o}}function Z(t,e,s){return{type:"Feature",id:t[e+A],properties:I(t,e,s),geometry:{type:"Point",coordinates:[(r=t[e],360*(r-.5)),S(t[e+1])]}};var r}function I(t,e,s){const r=t[e+b],o=r>=1e4?`${Math.round(r/1e3)}k`:r>=1e3?Math.round(r/100)/10+"k":r,i=t[e+L],n=-1===i?{}:Object.assign({},s[i]);return Object.assign(n,{cluster:!0,cluster_id:t[e+A],point_count:r,point_count_abbreviated:o})}function T(t){return t/360+.5}function j(t){const e=Math.sin(t*Math.PI/180),s=.5-.25*Math.log((1+e)/(1-e))/Math.PI;return s<0?0:s>1?1:s}function S(t){const e=(180-360*t)*Math.PI/180;return 360*Math.atan(Math.exp(e))/Math.PI-90}class z extends u{constructor(t){var{maxZoom:s,radius:r=60}=t,o=e(t,["maxZoom","radius"]);super({maxZoom:s}),this.state={zoom:-1},this.superCluster=new O(Object.assign({maxZoom:this.maxZoom,radius:r},o))}calculate(t){let e=!1;const r={zoom:t.map.getZoom()};if(!f(t.markers,this.markers)){e=!0,this.markers=[...t.markers];const r=this.markers.map((t=>{const e=s.getPosition(t);return{type:"Feature",geometry:{type:"Point",coordinates:[e.lng(),e.lat()]},properties:{marker:t}}}));this.superCluster.load(r)}return e||(this.state.zoom<=this.maxZoom||r.zoom<=this.maxZoom)&&(e=!f(this.state,r)),this.state=r,e&&(this.clusters=this.cluster(t)),{clusters:this.clusters,changed:e}}cluster(t){let{map:e}=t;return this.superCluster.getClusters([-180,-90,180,90],Math.round(e.getZoom())).map((t=>this.transformCluster(t)))}transformCluster(t){let{geometry:{coordinates:[e,o]},properties:i}=t;if(i.cluster)return new r({markers:this.superCluster.getLeaves(i.cluster_id,1/0).map((t=>t.properties.marker)),position:{lat:o,lng:e}});const n=i.marker;return new r({markers:[n],position:s.getPosition(n)})}}class U{constructor(t,e){this.markers={sum:t.length};const s=e.map((t=>t.count)),r=s.reduce(((t,e)=>t+e),0);this.clusters={count:e.length,markers:{mean:r/e.length,sum:r,min:Math.min(...s),max:Math.max(...s)}}}}class B{render(t,e,r){let{count:o,position:i}=t;const n=`\n \n \n \n${o} \n `,a=`Cluster of ${o} markers`,h=Number(google.maps.Marker.MAX_ZINDEX)+o;if(s.isAdvancedMarkerAvailable(r)){const t=(new DOMParser).parseFromString(n,"image/svg+xml").documentElement;t.setAttribute("transform","translate(0 25)");const e={map:r,position:i,zIndex:h,title:a,content:t};return new google.maps.marker.AdvancedMarkerElement(e)}const l={position:i,zIndex:h,title:a,icon:{url:`data:image/svg+xml;base64,${btoa(n)}`,anchor:new google.maps.Point(25,25)}};return new google.maps.Marker(l)}}class D{constructor(){!function(t,e){for(let s in e.prototype)t.prototype[s]=e.prototype[s]}(D,google.maps.OverlayView)}}var N;t.MarkerClustererEvents=void 0,(N=t.MarkerClustererEvents||(t.MarkerClustererEvents={})).CLUSTERING_BEGIN="clusteringbegin",N.CLUSTERING_END="clusteringend",N.CLUSTER_CLICK="click";const F=(t,e,s)=>{s.fitBounds(e.bounds)};return t.AbstractAlgorithm=u,t.AbstractViewportAlgorithm=p,t.Cluster=r,t.ClusterStats=U,t.DefaultRenderer=B,t.GridAlgorithm=class extends p{constructor(t){var{maxDistance:s=4e4,gridSize:r=40}=t;super(e(t,["maxDistance","gridSize"])),this.clusters=[],this.state={zoom:-1},this.maxDistance=s,this.gridSize=r}calculate(t){let{markers:e,map:s,mapCanvasProjection:r}=t;const i={zoom:s.getZoom()};let n=!1;return this.state.zoom>=this.maxZoom&&i.zoom>=this.maxZoom||(n=!f(this.state,i)),this.state=i,s.getZoom()>=this.maxZoom?{clusters:this.noop({markers:e}),changed:n}:{clusters:this.cluster({markers:o(s,r,e,this.viewportPadding),map:s,mapCanvasProjection:r})}}cluster(t){let{markers:e,map:s,mapCanvasProjection:r}=t;return this.clusters=[],e.forEach((t=>{this.addToClosestCluster(t,s,r)})),this.clusters}addToClosestCluster(t,e,o){let n=this.maxDistance,h=null;for(let e=0;e{this.addMarker(t,!0)})),e||this.render()}removeMarker(t,e){const r=this.markers.indexOf(t);return-1!==r&&(s.setMap(t,null),this.markers.splice(r,1),e||this.render(),!0)}removeMarkers(t,e){let s=!1;return t.forEach((t=>{s=this.removeMarker(t,!0)||s})),s&&!e&&this.render(),s}clearMarkers(t){this.markers.length=0,t||this.render()}render(){const e=this.getMap();if(e instanceof google.maps.Map&&e.getProjection()){google.maps.event.trigger(this,t.MarkerClustererEvents.CLUSTERING_BEGIN,this);const{clusters:r,changed:o}=this.algorithm.calculate({markers:this.markers,map:e,mapCanvasProjection:this.getProjection()});if(o||null==o){const t=new Set;for(const e of r)1==e.markers.length&&t.add(e.markers[0]);const e=[];for(const r of this.clusters)null!=r.marker&&(1==r.markers.length?t.has(r.marker)||s.setMap(r.marker,null):e.push(r.marker));this.clusters=r,this.renderClusters(),requestAnimationFrame((()=>e.forEach((t=>s.setMap(t,null)))))}google.maps.event.trigger(this,t.MarkerClustererEvents.CLUSTERING_END,this)}}onAdd(){this.idleListener=this.getMap().addListener("idle",this.render.bind(this)),this.render()}onRemove(){google.maps.event.removeListener(this.idleListener),this.reset()}reset(){this.markers.forEach((t=>s.setMap(t,null))),this.clusters.forEach((t=>t.delete())),this.clusters=[]}renderClusters(){const e=new U(this.markers,this.clusters),r=this.getMap();this.clusters.forEach((o=>{1===o.markers.length?o.marker=o.markers[0]:(o.marker=this.renderer.render(o,e,r),o.markers.forEach((t=>s.setMap(t,null))),this.onClusterClick&&o.marker.addListener("click",(e=>{google.maps.event.trigger(this,t.MarkerClustererEvents.CLUSTER_CLICK,o),this.onClusterClick(e,o,r)}))),s.setMap(o.marker,r)}))}},t.MarkerUtils=s,t.NoopAlgorithm=class extends u{constructor(t){super(e(t,[]))}calculate(t){let{markers:e,map:s,mapCanvasProjection:r}=t;return{clusters:this.cluster({markers:e,map:s,mapCanvasProjection:r}),changed:!1}}cluster(t){return this.noop(t)}},t.SuperClusterAlgorithm=z,t.SuperClusterViewportAlgorithm=class extends p{constructor(t){var{maxZoom:s,radius:r=60,viewportPadding:o=60}=t,i=e(t,["maxZoom","radius","viewportPadding"]);super({maxZoom:s,viewportPadding:o}),this.superCluster=new O(Object.assign({maxZoom:this.maxZoom,radius:r},i)),this.state={zoom:-1,view:[0,0,0,0]}}calculate(t){const e={zoom:Math.round(t.map.getZoom()),view:n(t.map.getBounds(),t.mapCanvasProjection,this.viewportPadding)};let r=!f(this.state,e);if(!f(t.markers,this.markers)){r=!0,this.markers=[...t.markers];const e=this.markers.map((t=>{const e=s.getPosition(t);return{type:"Feature",geometry:{type:"Point",coordinates:[e.lng(),e.lat()]},properties:{marker:t}}}));this.superCluster.load(e)}return r&&(this.clusters=this.cluster(t),this.state=e),{clusters:this.clusters,changed:r}}cluster(t){let{map:e,mapCanvasProjection:s}=t;const r={zoom:Math.round(e.getZoom()),view:n(e.getBounds(),s,this.viewportPadding)};return this.superCluster.getClusters(r.view,r.zoom).map((t=>this.transformCluster(t)))}transformCluster(t){let{geometry:{coordinates:[e,o]},properties:i}=t;if(i.cluster)return new r({markers:this.superCluster.getLeaves(i.cluster_id,1/0).map((t=>t.properties.marker)),position:{lat:o,lng:e}});const n=i.marker;return new r({markers:[n],position:s.getPosition(n)})}},t.defaultOnClusterClickHandler=F,t.distanceBetweenPoints=a,t.extendBoundsToPaddedViewport=i,t.extendPixelBounds=l,t.filterMarkersToPaddedViewport=o,t.getPaddedViewport=n,t.noop=m,t.pixelBoundsToLatLngBounds=c,Object.defineProperty(t,"__esModule",{value:!0}),t}({});
+//# sourceMappingURL=index.min.js.map
diff --git a/libs/qunit/qunit.css b/libs/qunit/qunit.css
new file mode 100644
index 0000000..d7fc0c8
--- /dev/null
+++ b/libs/qunit/qunit.css
@@ -0,0 +1,244 @@
+/**
+ * QUnit v1.11.0 - A JavaScript Unit Testing Framework
+ *
+ * http://qunitjs.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
+ margin: 0;
+ padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+ padding: 0.5em 0 0.5em 1em;
+
+ color: #8699a4;
+ background-color: #0d3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: normal;
+
+ border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ -webkit-border-top-right-radius: 5px;
+ -webkit-border-top-left-radius: 5px;
+}
+
+#qunit-header a {
+ text-decoration: none;
+ color: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #fff;
+}
+
+#qunit-testrunner-toolbar label {
+ display: inline-block;
+ padding: 0 .5em 0 .1em;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 0 0.5em 2em;
+ color: #5E740B;
+ background-color: #eee;
+ overflow: hidden;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 0 0.5em 2.5em;
+ background-color: #2b81af;
+ color: #fff;
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+#qunit-modulefilter-container {
+ float: right;
+}
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+ list-style-position: inside;
+}
+
+#qunit-tests li {
+ padding: 0.4em 0.5em 0.4em 2.5em;
+ border-bottom: 1px solid #fff;
+ list-style-position: inside;
+}
+
+#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
+ display: none;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#qunit-tests li a {
+ padding: 0.5em;
+ color: #c2ccd1;
+ text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+ color: #000;
+}
+
+#qunit-tests li .runtime {
+ float: right;
+ font-size: smaller;
+}
+
+.qunit-assert-list {
+ margin-top: 0.5em;
+ padding: 0.5em;
+
+ background-color: #fff;
+
+ border-radius: 5px;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+}
+
+.qunit-collapsed {
+ display: none;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: .2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ background-color: #e0f2be;
+ color: #374e0c;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ background-color: #ffcaca;
+ color: #500;
+ text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts { color: black; }
+#qunit-tests b.passed { color: #5E740B; }
+#qunit-tests b.failed { color: #710909; }
+
+#qunit-tests li li {
+ padding: 5px;
+ background-color: #fff;
+ border-bottom: none;
+ list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+ color: #3c510c;
+ background-color: #fff;
+ border-left: 10px solid #C6E746;
+}
+
+#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected { color: #999999; }
+
+#qunit-banner.qunit-pass { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+ color: #710909;
+ background-color: #fff;
+ border-left: 10px solid #EE5757;
+ white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+ border-radius: 0 0 5px 5px;
+ -moz-border-radius: 0 0 5px 5px;
+ -webkit-border-bottom-right-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+}
+
+#qunit-tests .fail { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: green; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+ padding: 0.5em 0.5em 0.5em 2.5em;
+
+ color: #2b81af;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid white;
+}
+#qunit-testresult .module-name {
+ font-weight: bold;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ width: 1000px;
+ height: 1000px;
+}
diff --git a/libs/qunit/qunit.js b/libs/qunit/qunit.js
new file mode 100644
index 0000000..302545f
--- /dev/null
+++ b/libs/qunit/qunit.js
@@ -0,0 +1,2152 @@
+/**
+ * QUnit v1.11.0 - A JavaScript Unit Testing Framework
+ *
+ * http://qunitjs.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+(function( window ) {
+
+var QUnit,
+ assert,
+ config,
+ onErrorFnPrev,
+ testId = 0,
+ fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ // Keep a local reference to Date (GH-283)
+ Date = window.Date,
+ defined = {
+ setTimeout: typeof window.setTimeout !== "undefined",
+ sessionStorage: (function() {
+ var x = "qunit-test-string";
+ try {
+ sessionStorage.setItem( x, x );
+ sessionStorage.removeItem( x );
+ return true;
+ } catch( e ) {
+ return false;
+ }
+ }())
+ },
+ /**
+ * Provides a normalized error string, correcting an issue
+ * with IE 7 (and prior) where Error.prototype.toString is
+ * not properly implemented
+ *
+ * Based on http://es5.github.com/#x15.11.4.4
+ *
+ * @param {String|Error} error
+ * @return {String} error message
+ */
+ errorString = function( error ) {
+ var name, message,
+ errorString = error.toString();
+ if ( errorString.substring( 0, 7 ) === "[object" ) {
+ name = error.name ? error.name.toString() : "Error";
+ message = error.message ? error.message.toString() : "";
+ if ( name && message ) {
+ return name + ": " + message;
+ } else if ( name ) {
+ return name;
+ } else if ( message ) {
+ return message;
+ } else {
+ return "Error";
+ }
+ } else {
+ return errorString;
+ }
+ },
+ /**
+ * Makes a clone of an object using only Array or Object as base,
+ * and copies over the own enumerable properties.
+ *
+ * @param {Object} obj
+ * @return {Object} New object with only the own properties (recursively).
+ */
+ objectValues = function( obj ) {
+ // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
+ /*jshint newcap: false */
+ var key, val,
+ vals = QUnit.is( "array", obj ) ? [] : {};
+ for ( key in obj ) {
+ if ( hasOwn.call( obj, key ) ) {
+ val = obj[key];
+ vals[key] = val === Object(val) ? objectValues(val) : val;
+ }
+ }
+ return vals;
+ };
+
+function Test( settings ) {
+ extend( this, settings );
+ this.assertions = [];
+ this.testNumber = ++Test.count;
+}
+
+Test.count = 0;
+
+Test.prototype = {
+ init: function() {
+ var a, b, li,
+ tests = id( "qunit-tests" );
+
+ if ( tests ) {
+ b = document.createElement( "strong" );
+ b.innerHTML = this.nameHtml;
+
+ // `a` initialized at top of scope
+ a = document.createElement( "a" );
+ a.innerHTML = "Rerun";
+ a.href = QUnit.url({ testNumber: this.testNumber });
+
+ li = document.createElement( "li" );
+ li.appendChild( b );
+ li.appendChild( a );
+ li.className = "running";
+ li.id = this.id = "qunit-test-output" + testId++;
+
+ tests.appendChild( li );
+ }
+ },
+ setup: function() {
+ if ( this.module !== config.previousModule ) {
+ if ( config.previousModule ) {
+ runLoggingCallbacks( "moduleDone", QUnit, {
+ name: config.previousModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ });
+ }
+ config.previousModule = this.module;
+ config.moduleStats = { all: 0, bad: 0 };
+ runLoggingCallbacks( "moduleStart", QUnit, {
+ name: this.module
+ });
+ } else if ( config.autorun ) {
+ runLoggingCallbacks( "moduleStart", QUnit, {
+ name: this.module
+ });
+ }
+
+ config.current = this;
+
+ this.testEnvironment = extend({
+ setup: function() {},
+ teardown: function() {}
+ }, this.moduleTestEnvironment );
+
+ this.started = +new Date();
+ runLoggingCallbacks( "testStart", QUnit, {
+ name: this.testName,
+ module: this.module
+ });
+
+ // allow utility functions to access the current test environment
+ // TODO why??
+ QUnit.current_testEnvironment = this.testEnvironment;
+
+ if ( !config.pollution ) {
+ saveGlobal();
+ }
+ if ( config.notrycatch ) {
+ this.testEnvironment.setup.call( this.testEnvironment );
+ return;
+ }
+ try {
+ this.testEnvironment.setup.call( this.testEnvironment );
+ } catch( e ) {
+ QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
+ }
+ },
+ run: function() {
+ config.current = this;
+
+ var running = id( "qunit-testresult" );
+
+ if ( running ) {
+ running.innerHTML = "Running: " + this.nameHtml;
+ }
+
+ if ( this.async ) {
+ QUnit.stop();
+ }
+
+ this.callbackStarted = +new Date();
+
+ if ( config.notrycatch ) {
+ this.callback.call( this.testEnvironment, QUnit.assert );
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ return;
+ }
+
+ try {
+ this.callback.call( this.testEnvironment, QUnit.assert );
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ } catch( e ) {
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+
+ QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
+ // else next test will carry the responsibility
+ saveGlobal();
+
+ // Restart the tests if they're blocking
+ if ( config.blocking ) {
+ QUnit.start();
+ }
+ }
+ },
+ teardown: function() {
+ config.current = this;
+ if ( config.notrycatch ) {
+ if ( typeof this.callbackRuntime === "undefined" ) {
+ this.callbackRuntime = +new Date() - this.callbackStarted;
+ }
+ this.testEnvironment.teardown.call( this.testEnvironment );
+ return;
+ } else {
+ try {
+ this.testEnvironment.teardown.call( this.testEnvironment );
+ } catch( e ) {
+ QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
+ }
+ }
+ checkPollution();
+ },
+ finish: function() {
+ config.current = this;
+ if ( config.requireExpects && this.expected === null ) {
+ QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
+ } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
+ QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
+ } else if ( this.expected === null && !this.assertions.length ) {
+ QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
+ }
+
+ var i, assertion, a, b, time, li, ol,
+ test = this,
+ good = 0,
+ bad = 0,
+ tests = id( "qunit-tests" );
+
+ this.runtime = +new Date() - this.started;
+ config.stats.all += this.assertions.length;
+ config.moduleStats.all += this.assertions.length;
+
+ if ( tests ) {
+ ol = document.createElement( "ol" );
+ ol.className = "qunit-assert-list";
+
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ assertion = this.assertions[i];
+
+ li = document.createElement( "li" );
+ li.className = assertion.result ? "pass" : "fail";
+ li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
+ ol.appendChild( li );
+
+ if ( assertion.result ) {
+ good++;
+ } else {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+
+ // store result when possible
+ if ( QUnit.config.reorder && defined.sessionStorage ) {
+ if ( bad ) {
+ sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
+ } else {
+ sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
+ }
+ }
+
+ if ( bad === 0 ) {
+ addClass( ol, "qunit-collapsed" );
+ }
+
+ // `b` initialized at top of scope
+ b = document.createElement( "strong" );
+ b.innerHTML = this.nameHtml + " (" + bad + " , " + good + " , " + this.assertions.length + ") ";
+
+ addEvent(b, "click", function() {
+ var next = b.parentNode.lastChild,
+ collapsed = hasClass( next, "qunit-collapsed" );
+ ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
+ });
+
+ addEvent(b, "dblclick", function( e ) {
+ var target = e && e.target ? e.target : window.event.srcElement;
+ if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
+ target = target.parentNode;
+ }
+ if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+ window.location = QUnit.url({ testNumber: test.testNumber });
+ }
+ });
+
+ // `time` initialized at top of scope
+ time = document.createElement( "span" );
+ time.className = "runtime";
+ time.innerHTML = this.runtime + " ms";
+
+ // `li` initialized at top of scope
+ li = id( this.id );
+ li.className = bad ? "fail" : "pass";
+ li.removeChild( li.firstChild );
+ a = li.firstChild;
+ li.appendChild( b );
+ li.appendChild( a );
+ li.appendChild( time );
+ li.appendChild( ol );
+
+ } else {
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ if ( !this.assertions[i].result ) {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+ }
+
+ runLoggingCallbacks( "testDone", QUnit, {
+ name: this.testName,
+ module: this.module,
+ failed: bad,
+ passed: this.assertions.length - bad,
+ total: this.assertions.length,
+ duration: this.runtime
+ });
+
+ QUnit.reset();
+
+ config.current = undefined;
+ },
+
+ queue: function() {
+ var bad,
+ test = this;
+
+ synchronize(function() {
+ test.init();
+ });
+ function run() {
+ // each of these can by async
+ synchronize(function() {
+ test.setup();
+ });
+ synchronize(function() {
+ test.run();
+ });
+ synchronize(function() {
+ test.teardown();
+ });
+ synchronize(function() {
+ test.finish();
+ });
+ }
+
+ // `bad` initialized at top of scope
+ // defer when previous test run passed, if storage is available
+ bad = QUnit.config.reorder && defined.sessionStorage &&
+ +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
+
+ if ( bad ) {
+ run();
+ } else {
+ synchronize( run, true );
+ }
+ }
+};
+
+// Root QUnit object.
+// `QUnit` initialized at top of scope
+QUnit = {
+
+ // call on start of module test to prepend name to all tests
+ module: function( name, testEnvironment ) {
+ config.currentModule = name;
+ config.currentModuleTestEnvironment = testEnvironment;
+ config.modules[name] = true;
+ },
+
+ asyncTest: function( testName, expected, callback ) {
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ QUnit.test( testName, expected, callback, true );
+ },
+
+ test: function( testName, expected, callback, async ) {
+ var test,
+ nameHtml = "" + escapeText( testName ) + " ";
+
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ if ( config.currentModule ) {
+ nameHtml = "" + escapeText( config.currentModule ) + " : " + nameHtml;
+ }
+
+ test = new Test({
+ nameHtml: nameHtml,
+ testName: testName,
+ expected: expected,
+ async: async,
+ callback: callback,
+ module: config.currentModule,
+ moduleTestEnvironment: config.currentModuleTestEnvironment,
+ stack: sourceFromStacktrace( 2 )
+ });
+
+ if ( !validTest( test ) ) {
+ return;
+ }
+
+ test.queue();
+ },
+
+ // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
+ expect: function( asserts ) {
+ if (arguments.length === 1) {
+ config.current.expected = asserts;
+ } else {
+ return config.current.expected;
+ }
+ },
+
+ start: function( count ) {
+ // QUnit hasn't been initialized yet.
+ // Note: RequireJS (et al) may delay onLoad
+ if ( config.semaphore === undefined ) {
+ QUnit.begin(function() {
+ // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
+ setTimeout(function() {
+ QUnit.start( count );
+ });
+ });
+ return;
+ }
+
+ config.semaphore -= count || 1;
+ // don't start until equal number of stop-calls
+ if ( config.semaphore > 0 ) {
+ return;
+ }
+ // ignore if start is called more often then stop
+ if ( config.semaphore < 0 ) {
+ config.semaphore = 0;
+ QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
+ return;
+ }
+ // A slight delay, to avoid any current callbacks
+ if ( defined.setTimeout ) {
+ window.setTimeout(function() {
+ if ( config.semaphore > 0 ) {
+ return;
+ }
+ if ( config.timeout ) {
+ clearTimeout( config.timeout );
+ }
+
+ config.blocking = false;
+ process( true );
+ }, 13);
+ } else {
+ config.blocking = false;
+ process( true );
+ }
+ },
+
+ stop: function( count ) {
+ config.semaphore += count || 1;
+ config.blocking = true;
+
+ if ( config.testTimeout && defined.setTimeout ) {
+ clearTimeout( config.timeout );
+ config.timeout = window.setTimeout(function() {
+ QUnit.ok( false, "Test timed out" );
+ config.semaphore = 1;
+ QUnit.start();
+ }, config.testTimeout );
+ }
+ }
+};
+
+// `assert` initialized at top of scope
+// Asssert helpers
+// All of these must either call QUnit.push() or manually do:
+// - runLoggingCallbacks( "log", .. );
+// - config.current.assertions.push({ .. });
+// We attach it to the QUnit object *after* we expose the public API,
+// otherwise `assert` will become a global variable in browsers (#341).
+assert = {
+ /**
+ * Asserts rough true-ish result.
+ * @name ok
+ * @function
+ * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+ */
+ ok: function( result, msg ) {
+ if ( !config.current ) {
+ throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
+ }
+ result = !!result;
+
+ var source,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: result,
+ message: msg
+ };
+
+ msg = escapeText( msg || (result ? "okay" : "failed" ) );
+ msg = "" + msg + " ";
+
+ if ( !result ) {
+ source = sourceFromStacktrace( 2 );
+ if ( source ) {
+ details.source = source;
+ msg += "Source: " + escapeText( source ) + "
";
+ }
+ }
+ runLoggingCallbacks( "log", QUnit, details );
+ config.current.assertions.push({
+ result: result,
+ message: msg
+ });
+ },
+
+ /**
+ * Assert that the first two arguments are equal, with an optional message.
+ * Prints out both actual and expected values.
+ * @name equal
+ * @function
+ * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
+ */
+ equal: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ QUnit.push( expected == actual, actual, expected, message );
+ },
+
+ /**
+ * @name notEqual
+ * @function
+ */
+ notEqual: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ QUnit.push( expected != actual, actual, expected, message );
+ },
+
+ /**
+ * @name propEqual
+ * @function
+ */
+ propEqual: function( actual, expected, message ) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name notPropEqual
+ * @function
+ */
+ notPropEqual: function( actual, expected, message ) {
+ actual = objectValues(actual);
+ expected = objectValues(expected);
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name deepEqual
+ * @function
+ */
+ deepEqual: function( actual, expected, message ) {
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name notDeepEqual
+ * @function
+ */
+ notDeepEqual: function( actual, expected, message ) {
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+ },
+
+ /**
+ * @name strictEqual
+ * @function
+ */
+ strictEqual: function( actual, expected, message ) {
+ QUnit.push( expected === actual, actual, expected, message );
+ },
+
+ /**
+ * @name notStrictEqual
+ * @function
+ */
+ notStrictEqual: function( actual, expected, message ) {
+ QUnit.push( expected !== actual, actual, expected, message );
+ },
+
+ "throws": function( block, expected, message ) {
+ var actual,
+ expectedOutput = expected,
+ ok = false;
+
+ // 'expected' is optional
+ if ( typeof expected === "string" ) {
+ message = expected;
+ expected = null;
+ }
+
+ config.current.ignoreGlobalErrors = true;
+ try {
+ block.call( config.current.testEnvironment );
+ } catch (e) {
+ actual = e;
+ }
+ config.current.ignoreGlobalErrors = false;
+
+ if ( actual ) {
+ // we don't want to validate thrown error
+ if ( !expected ) {
+ ok = true;
+ expectedOutput = null;
+ // expected is a regexp
+ } else if ( QUnit.objectType( expected ) === "regexp" ) {
+ ok = expected.test( errorString( actual ) );
+ // expected is a constructor
+ } else if ( actual instanceof expected ) {
+ ok = true;
+ // expected is a validation function which returns true is validation passed
+ } else if ( expected.call( {}, actual ) === true ) {
+ expectedOutput = null;
+ ok = true;
+ }
+
+ QUnit.push( ok, actual, expectedOutput, message );
+ } else {
+ QUnit.pushFailure( message, null, 'No exception was thrown.' );
+ }
+ }
+};
+
+/**
+ * @deprecate since 1.8.0
+ * Kept assertion helpers in root for backwards compatibility.
+ */
+extend( QUnit, assert );
+
+/**
+ * @deprecated since 1.9.0
+ * Kept root "raises()" for backwards compatibility.
+ * (Note that we don't introduce assert.raises).
+ */
+QUnit.raises = assert[ "throws" ];
+
+/**
+ * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
+ * Kept to avoid TypeErrors for undefined methods.
+ */
+QUnit.equals = function() {
+ QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
+};
+QUnit.same = function() {
+ QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
+};
+
+// We want access to the constructor's prototype
+(function() {
+ function F() {}
+ F.prototype = QUnit;
+ QUnit = new F();
+ // Make F QUnit's constructor so that we can add to the prototype later
+ QUnit.constructor = F;
+}());
+
+/**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+config = {
+ // The queue of tests to run
+ queue: [],
+
+ // block until document ready
+ blocking: true,
+
+ // when enabled, show only failing tests
+ // gets persisted through sessionStorage and can be changed in UI via checkbox
+ hidepassed: false,
+
+ // by default, run previously failed tests first
+ // very useful in combination with "Hide passed tests" checked
+ reorder: true,
+
+ // by default, modify document.title when suite is done
+ altertitle: true,
+
+ // when enabled, all tests must call expect()
+ requireExpects: false,
+
+ // add checkboxes that are persisted in the query-string
+ // when enabled, the id is set to `true` as a `QUnit.config` property
+ urlConfig: [
+ {
+ id: "noglobals",
+ label: "Check for Globals",
+ tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
+ },
+ {
+ id: "notrycatch",
+ label: "No try-catch",
+ tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
+ }
+ ],
+
+ // Set of all modules.
+ modules: {},
+
+ // logging callback queues
+ begin: [],
+ done: [],
+ log: [],
+ testStart: [],
+ testDone: [],
+ moduleStart: [],
+ moduleDone: []
+};
+
+// Export global variables, unless an 'exports' object exists,
+// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
+if ( typeof exports === "undefined" ) {
+ extend( window, QUnit );
+
+ // Expose QUnit object
+ window.QUnit = QUnit;
+}
+
+// Initialize more QUnit.config and QUnit.urlParams
+(function() {
+ var i,
+ location = window.location || { search: "", protocol: "file:" },
+ params = location.search.slice( 1 ).split( "&" ),
+ length = params.length,
+ urlParams = {},
+ current;
+
+ if ( params[ 0 ] ) {
+ for ( i = 0; i < length; i++ ) {
+ current = params[ i ].split( "=" );
+ current[ 0 ] = decodeURIComponent( current[ 0 ] );
+ // allow just a key to turn on a flag, e.g., test.html?noglobals
+ current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+ urlParams[ current[ 0 ] ] = current[ 1 ];
+ }
+ }
+
+ QUnit.urlParams = urlParams;
+
+ // String search anywhere in moduleName+testName
+ config.filter = urlParams.filter;
+
+ // Exact match of the module name
+ config.module = urlParams.module;
+
+ config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
+
+ // Figure out if we're running the tests from a server or not
+ QUnit.isLocal = location.protocol === "file:";
+}());
+
+// Extend QUnit object,
+// these after set here because they should not be exposed as global functions
+extend( QUnit, {
+ assert: assert,
+
+ config: config,
+
+ // Initialize the configuration options
+ init: function() {
+ extend( config, {
+ stats: { all: 0, bad: 0 },
+ moduleStats: { all: 0, bad: 0 },
+ started: +new Date(),
+ updateRate: 1000,
+ blocking: false,
+ autostart: true,
+ autorun: false,
+ filter: "",
+ queue: [],
+ semaphore: 1
+ });
+
+ var tests, banner, result,
+ qunit = id( "qunit" );
+
+ if ( qunit ) {
+ qunit.innerHTML =
+ "" +
+ " " +
+ "
" +
+ " " +
+ " ";
+ }
+
+ tests = id( "qunit-tests" );
+ banner = id( "qunit-banner" );
+ result = id( "qunit-testresult" );
+
+ if ( tests ) {
+ tests.innerHTML = "";
+ }
+
+ if ( banner ) {
+ banner.className = "";
+ }
+
+ if ( result ) {
+ result.parentNode.removeChild( result );
+ }
+
+ if ( tests ) {
+ result = document.createElement( "p" );
+ result.id = "qunit-testresult";
+ result.className = "result";
+ tests.parentNode.insertBefore( result, tests );
+ result.innerHTML = "Running... ";
+ }
+ },
+
+ // Resets the test setup. Useful for tests that modify the DOM.
+ reset: function() {
+ var fixture = id( "qunit-fixture" );
+ if ( fixture ) {
+ fixture.innerHTML = config.fixture;
+ }
+ },
+
+ // Trigger an event on an element.
+ // @example triggerEvent( document.body, "click" );
+ triggerEvent: function( elem, type, event ) {
+ if ( document.createEvent ) {
+ event = document.createEvent( "MouseEvents" );
+ event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+
+ elem.dispatchEvent( event );
+ } else if ( elem.fireEvent ) {
+ elem.fireEvent( "on" + type );
+ }
+ },
+
+ // Safe object type checking
+ is: function( type, obj ) {
+ return QUnit.objectType( obj ) === type;
+ },
+
+ objectType: function( obj ) {
+ if ( typeof obj === "undefined" ) {
+ return "undefined";
+ // consider: typeof null === object
+ }
+ if ( obj === null ) {
+ return "null";
+ }
+
+ var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
+ type = match && match[1] || "";
+
+ switch ( type ) {
+ case "Number":
+ if ( isNaN(obj) ) {
+ return "nan";
+ }
+ return "number";
+ case "String":
+ case "Boolean":
+ case "Array":
+ case "Date":
+ case "RegExp":
+ case "Function":
+ return type.toLowerCase();
+ }
+ if ( typeof obj === "object" ) {
+ return "object";
+ }
+ return undefined;
+ },
+
+ push: function( result, actual, expected, message ) {
+ if ( !config.current ) {
+ throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
+ }
+
+ var output, source,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: result,
+ message: message,
+ actual: actual,
+ expected: expected
+ };
+
+ message = escapeText( message ) || ( result ? "okay" : "failed" );
+ message = "" + message + " ";
+ output = message;
+
+ if ( !result ) {
+ expected = escapeText( QUnit.jsDump.parse(expected) );
+ actual = escapeText( QUnit.jsDump.parse(actual) );
+ output += "Expected: " + expected + " ";
+
+ if ( actual !== expected ) {
+ output += "Result: " + actual + " ";
+ output += "Diff: " + QUnit.diff( expected, actual ) + " ";
+ }
+
+ source = sourceFromStacktrace();
+
+ if ( source ) {
+ details.source = source;
+ output += "Source: " + escapeText( source ) + " ";
+ }
+
+ output += "
";
+ }
+
+ runLoggingCallbacks( "log", QUnit, details );
+
+ config.current.assertions.push({
+ result: !!result,
+ message: output
+ });
+ },
+
+ pushFailure: function( message, source, actual ) {
+ if ( !config.current ) {
+ throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
+ }
+
+ var output,
+ details = {
+ module: config.current.module,
+ name: config.current.testName,
+ result: false,
+ message: message
+ };
+
+ message = escapeText( message ) || "error";
+ message = "" + message + " ";
+ output = message;
+
+ output += "";
+
+ if ( actual ) {
+ output += "Result: " + escapeText( actual ) + " ";
+ }
+
+ if ( source ) {
+ details.source = source;
+ output += "Source: " + escapeText( source ) + " ";
+ }
+
+ output += "
";
+
+ runLoggingCallbacks( "log", QUnit, details );
+
+ config.current.assertions.push({
+ result: false,
+ message: output
+ });
+ },
+
+ url: function( params ) {
+ params = extend( extend( {}, QUnit.urlParams ), params );
+ var key,
+ querystring = "?";
+
+ for ( key in params ) {
+ if ( !hasOwn.call( params, key ) ) {
+ continue;
+ }
+ querystring += encodeURIComponent( key ) + "=" +
+ encodeURIComponent( params[ key ] ) + "&";
+ }
+ return window.location.protocol + "//" + window.location.host +
+ window.location.pathname + querystring.slice( 0, -1 );
+ },
+
+ extend: extend,
+ id: id,
+ addEvent: addEvent
+ // load, equiv, jsDump, diff: Attached later
+});
+
+/**
+ * @deprecated: Created for backwards compatibility with test runner that set the hook function
+ * into QUnit.{hook}, instead of invoking it and passing the hook function.
+ * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
+ * Doing this allows us to tell if the following methods have been overwritten on the actual
+ * QUnit object.
+ */
+extend( QUnit.constructor.prototype, {
+
+ // Logging callbacks; all receive a single argument with the listed properties
+ // run test/logs.html for any related changes
+ begin: registerLoggingCallback( "begin" ),
+
+ // done: { failed, passed, total, runtime }
+ done: registerLoggingCallback( "done" ),
+
+ // log: { result, actual, expected, message }
+ log: registerLoggingCallback( "log" ),
+
+ // testStart: { name }
+ testStart: registerLoggingCallback( "testStart" ),
+
+ // testDone: { name, failed, passed, total, duration }
+ testDone: registerLoggingCallback( "testDone" ),
+
+ // moduleStart: { name }
+ moduleStart: registerLoggingCallback( "moduleStart" ),
+
+ // moduleDone: { name, failed, passed, total }
+ moduleDone: registerLoggingCallback( "moduleDone" )
+});
+
+if ( typeof document === "undefined" || document.readyState === "complete" ) {
+ config.autorun = true;
+}
+
+QUnit.load = function() {
+ runLoggingCallbacks( "begin", QUnit, {} );
+
+ // Initialize the config, saving the execution queue
+ var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
+ urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
+ numModules = 0,
+ moduleFilterHtml = "",
+ urlConfigHtml = "",
+ oldconfig = extend( {}, config );
+
+ QUnit.init();
+ extend(config, oldconfig);
+
+ config.blocking = false;
+
+ len = config.urlConfig.length;
+
+ for ( i = 0; i < len; i++ ) {
+ val = config.urlConfig[i];
+ if ( typeof val === "string" ) {
+ val = {
+ id: val,
+ label: val,
+ tooltip: "[no tooltip available]"
+ };
+ }
+ config[ val.id ] = QUnit.urlParams[ val.id ];
+ urlConfigHtml += "" + val.label + " ";
+ }
+
+ moduleFilterHtml += "Module: < All Modules > ";
+
+ for ( i in config.modules ) {
+ if ( config.modules.hasOwnProperty( i ) ) {
+ numModules += 1;
+ moduleFilterHtml += "" + escapeText(i) + " ";
+ }
+ }
+ moduleFilterHtml += " ";
+
+ // `userAgent` initialized at top of scope
+ userAgent = id( "qunit-userAgent" );
+ if ( userAgent ) {
+ userAgent.innerHTML = navigator.userAgent;
+ }
+
+ // `banner` initialized at top of scope
+ banner = id( "qunit-header" );
+ if ( banner ) {
+ banner.innerHTML = "" + banner.innerHTML + " ";
+ }
+
+ // `toolbar` initialized at top of scope
+ toolbar = id( "qunit-testrunner-toolbar" );
+ if ( toolbar ) {
+ // `filter` initialized at top of scope
+ filter = document.createElement( "input" );
+ filter.type = "checkbox";
+ filter.id = "qunit-filter-pass";
+
+ addEvent( filter, "click", function() {
+ var tmp,
+ ol = document.getElementById( "qunit-tests" );
+
+ if ( filter.checked ) {
+ ol.className = ol.className + " hidepass";
+ } else {
+ tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
+ ol.className = tmp.replace( / hidepass /, " " );
+ }
+ if ( defined.sessionStorage ) {
+ if (filter.checked) {
+ sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
+ } else {
+ sessionStorage.removeItem( "qunit-filter-passed-tests" );
+ }
+ }
+ });
+
+ if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
+ filter.checked = true;
+ // `ol` initialized at top of scope
+ ol = document.getElementById( "qunit-tests" );
+ ol.className = ol.className + " hidepass";
+ }
+ toolbar.appendChild( filter );
+
+ // `label` initialized at top of scope
+ label = document.createElement( "label" );
+ label.setAttribute( "for", "qunit-filter-pass" );
+ label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
+ label.innerHTML = "Hide passed tests";
+ toolbar.appendChild( label );
+
+ urlConfigCheckboxesContainer = document.createElement("span");
+ urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
+ urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
+ // For oldIE support:
+ // * Add handlers to the individual elements instead of the container
+ // * Use "click" instead of "change"
+ // * Fallback from event.target to event.srcElement
+ addEvents( urlConfigCheckboxes, "click", function( event ) {
+ var params = {},
+ target = event.target || event.srcElement;
+ params[ target.name ] = target.checked ? true : undefined;
+ window.location = QUnit.url( params );
+ });
+ toolbar.appendChild( urlConfigCheckboxesContainer );
+
+ if (numModules > 1) {
+ moduleFilter = document.createElement( 'span' );
+ moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
+ moduleFilter.innerHTML = moduleFilterHtml;
+ addEvent( moduleFilter.lastChild, "change", function() {
+ var selectBox = moduleFilter.getElementsByTagName("select")[0],
+ selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
+
+ window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
+ });
+ toolbar.appendChild(moduleFilter);
+ }
+ }
+
+ // `main` initialized at top of scope
+ main = id( "qunit-fixture" );
+ if ( main ) {
+ config.fixture = main.innerHTML;
+ }
+
+ if ( config.autostart ) {
+ QUnit.start();
+ }
+};
+
+addEvent( window, "load", QUnit.load );
+
+// `onErrorFnPrev` initialized at top of scope
+// Preserve other handlers
+onErrorFnPrev = window.onerror;
+
+// Cover uncaught exceptions
+// Returning true will surpress the default browser handler,
+// returning false will let it run.
+window.onerror = function ( error, filePath, linerNr ) {
+ var ret = false;
+ if ( onErrorFnPrev ) {
+ ret = onErrorFnPrev( error, filePath, linerNr );
+ }
+
+ // Treat return value as window.onerror itself does,
+ // Only do our handling if not surpressed.
+ if ( ret !== true ) {
+ if ( QUnit.config.current ) {
+ if ( QUnit.config.current.ignoreGlobalErrors ) {
+ return true;
+ }
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
+ } else {
+ QUnit.test( "global failure", extend( function() {
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
+ }, { validTest: validTest } ) );
+ }
+ return false;
+ }
+
+ return ret;
+};
+
+function done() {
+ config.autorun = true;
+
+ // Log the last module results
+ if ( config.currentModule ) {
+ runLoggingCallbacks( "moduleDone", QUnit, {
+ name: config.currentModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ });
+ }
+
+ var i, key,
+ banner = id( "qunit-banner" ),
+ tests = id( "qunit-tests" ),
+ runtime = +new Date() - config.started,
+ passed = config.stats.all - config.stats.bad,
+ html = [
+ "Tests completed in ",
+ runtime,
+ " milliseconds. ",
+ "",
+ passed,
+ " assertions of ",
+ config.stats.all,
+ " passed, ",
+ config.stats.bad,
+ " failed."
+ ].join( "" );
+
+ if ( banner ) {
+ banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
+ }
+
+ if ( tests ) {
+ id( "qunit-testresult" ).innerHTML = html;
+ }
+
+ if ( config.altertitle && typeof document !== "undefined" && document.title ) {
+ // show ✖ for good, ✔ for bad suite result in title
+ // use escape sequences in case file gets loaded with non-utf-8-charset
+ document.title = [
+ ( config.stats.bad ? "\u2716" : "\u2714" ),
+ document.title.replace( /^[\u2714\u2716] /i, "" )
+ ].join( " " );
+ }
+
+ // clear own sessionStorage items if all tests passed
+ if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
+ // `key` & `i` initialized at top of scope
+ for ( i = 0; i < sessionStorage.length; i++ ) {
+ key = sessionStorage.key( i++ );
+ if ( key.indexOf( "qunit-test-" ) === 0 ) {
+ sessionStorage.removeItem( key );
+ }
+ }
+ }
+
+ // scroll back to top to show results
+ if ( window.scrollTo ) {
+ window.scrollTo(0, 0);
+ }
+
+ runLoggingCallbacks( "done", QUnit, {
+ failed: config.stats.bad,
+ passed: passed,
+ total: config.stats.all,
+ runtime: runtime
+ });
+}
+
+/** @return Boolean: true if this test should be ran */
+function validTest( test ) {
+ var include,
+ filter = config.filter && config.filter.toLowerCase(),
+ module = config.module && config.module.toLowerCase(),
+ fullName = (test.module + ": " + test.testName).toLowerCase();
+
+ // Internally-generated tests are always valid
+ if ( test.callback && test.callback.validTest === validTest ) {
+ delete test.callback.validTest;
+ return true;
+ }
+
+ if ( config.testNumber ) {
+ return test.testNumber === config.testNumber;
+ }
+
+ if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
+ return false;
+ }
+
+ if ( !filter ) {
+ return true;
+ }
+
+ include = filter.charAt( 0 ) !== "!";
+ if ( !include ) {
+ filter = filter.slice( 1 );
+ }
+
+ // If the filter matches, we need to honour include
+ if ( fullName.indexOf( filter ) !== -1 ) {
+ return include;
+ }
+
+ // Otherwise, do the opposite
+ return !include;
+}
+
+// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
+// Later Safari and IE10 are supposed to support error.stack as well
+// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+function extractStacktrace( e, offset ) {
+ offset = offset === undefined ? 3 : offset;
+
+ var stack, include, i;
+
+ if ( e.stacktrace ) {
+ // Opera
+ return e.stacktrace.split( "\n" )[ offset + 3 ];
+ } else if ( e.stack ) {
+ // Firefox, Chrome
+ stack = e.stack.split( "\n" );
+ if (/^error$/i.test( stack[0] ) ) {
+ stack.shift();
+ }
+ if ( fileName ) {
+ include = [];
+ for ( i = offset; i < stack.length; i++ ) {
+ if ( stack[ i ].indexOf( fileName ) !== -1 ) {
+ break;
+ }
+ include.push( stack[ i ] );
+ }
+ if ( include.length ) {
+ return include.join( "\n" );
+ }
+ }
+ return stack[ offset ];
+ } else if ( e.sourceURL ) {
+ // Safari, PhantomJS
+ // hopefully one day Safari provides actual stacktraces
+ // exclude useless self-reference for generated Error objects
+ if ( /qunit.js$/.test( e.sourceURL ) ) {
+ return;
+ }
+ // for actual exceptions, this is useful
+ return e.sourceURL + ":" + e.line;
+ }
+}
+function sourceFromStacktrace( offset ) {
+ try {
+ throw new Error();
+ } catch ( e ) {
+ return extractStacktrace( e, offset );
+ }
+}
+
+/**
+ * Escape text for attribute or text content.
+ */
+function escapeText( s ) {
+ if ( !s ) {
+ return "";
+ }
+ s = s + "";
+ // Both single quotes and double quotes (for attributes)
+ return s.replace( /['"<>&]/g, function( s ) {
+ switch( s ) {
+ case '\'':
+ return ''';
+ case '"':
+ return '"';
+ case '<':
+ return '<';
+ case '>':
+ return '>';
+ case '&':
+ return '&';
+ }
+ });
+}
+
+function synchronize( callback, last ) {
+ config.queue.push( callback );
+
+ if ( config.autorun && !config.blocking ) {
+ process( last );
+ }
+}
+
+function process( last ) {
+ function next() {
+ process( last );
+ }
+ var start = new Date().getTime();
+ config.depth = config.depth ? config.depth + 1 : 1;
+
+ while ( config.queue.length && !config.blocking ) {
+ if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
+ config.queue.shift()();
+ } else {
+ window.setTimeout( next, 13 );
+ break;
+ }
+ }
+ config.depth--;
+ if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
+ done();
+ }
+}
+
+function saveGlobal() {
+ config.pollution = [];
+
+ if ( config.noglobals ) {
+ for ( var key in window ) {
+ // in Opera sometimes DOM element ids show up here, ignore them
+ if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) {
+ continue;
+ }
+ config.pollution.push( key );
+ }
+ }
+}
+
+function checkPollution() {
+ var newGlobals,
+ deletedGlobals,
+ old = config.pollution;
+
+ saveGlobal();
+
+ newGlobals = diff( config.pollution, old );
+ if ( newGlobals.length > 0 ) {
+ QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
+ }
+
+ deletedGlobals = diff( old, config.pollution );
+ if ( deletedGlobals.length > 0 ) {
+ QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
+ }
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+ var i, j,
+ result = a.slice();
+
+ for ( i = 0; i < result.length; i++ ) {
+ for ( j = 0; j < b.length; j++ ) {
+ if ( result[i] === b[j] ) {
+ result.splice( i, 1 );
+ i--;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+function extend( a, b ) {
+ for ( var prop in b ) {
+ if ( b[ prop ] === undefined ) {
+ delete a[ prop ];
+
+ // Avoid "Member not found" error in IE8 caused by setting window.constructor
+ } else if ( prop !== "constructor" || a !== window ) {
+ a[ prop ] = b[ prop ];
+ }
+ }
+
+ return a;
+}
+
+/**
+ * @param {HTMLElement} elem
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvent( elem, type, fn ) {
+ // Standards-based browsers
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, fn, false );
+ // IE
+ } else {
+ elem.attachEvent( "on" + type, fn );
+ }
+}
+
+/**
+ * @param {Array|NodeList} elems
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvents( elems, type, fn ) {
+ var i = elems.length;
+ while ( i-- ) {
+ addEvent( elems[i], type, fn );
+ }
+}
+
+function hasClass( elem, name ) {
+ return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
+}
+
+function addClass( elem, name ) {
+ if ( !hasClass( elem, name ) ) {
+ elem.className += (elem.className ? " " : "") + name;
+ }
+}
+
+function removeClass( elem, name ) {
+ var set = " " + elem.className + " ";
+ // Class name may appear multiple times
+ while ( set.indexOf(" " + name + " ") > -1 ) {
+ set = set.replace(" " + name + " " , " ");
+ }
+ // If possible, trim it for prettiness, but not neccecarily
+ elem.className = window.jQuery ? jQuery.trim( set ) : ( set.trim ? set.trim() : set );
+}
+
+function id( name ) {
+ return !!( typeof document !== "undefined" && document && document.getElementById ) &&
+ document.getElementById( name );
+}
+
+function registerLoggingCallback( key ) {
+ return function( callback ) {
+ config[key].push( callback );
+ };
+}
+
+// Supports deprecated method of completely overwriting logging callbacks
+function runLoggingCallbacks( key, scope, args ) {
+ var i, callbacks;
+ if ( QUnit.hasOwnProperty( key ) ) {
+ QUnit[ key ].call(scope, args );
+ } else {
+ callbacks = config[ key ];
+ for ( i = 0; i < callbacks.length; i++ ) {
+ callbacks[ i ].call( scope, args );
+ }
+ }
+}
+
+// Test for equality any JavaScript type.
+// Author: Philippe Rathé
+QUnit.equiv = (function() {
+
+ // Call the o related callback with the given arguments.
+ function bindCallbacks( o, callbacks, args ) {
+ var prop = QUnit.objectType( o );
+ if ( prop ) {
+ if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
+ return callbacks[ prop ].apply( callbacks, args );
+ } else {
+ return callbacks[ prop ]; // or undefined
+ }
+ }
+ }
+
+ // the real equiv function
+ var innerEquiv,
+ // stack to decide between skip/abort functions
+ callers = [],
+ // stack to avoiding loops from circular referencing
+ parents = [],
+
+ getProto = Object.getPrototypeOf || function ( obj ) {
+ return obj.__proto__;
+ },
+ callbacks = (function () {
+
+ // for string, boolean, number and null
+ function useStrictEquality( b, a ) {
+ /*jshint eqeqeq:false */
+ if ( b instanceof a.constructor || a instanceof b.constructor ) {
+ // to catch short annotaion VS 'new' annotation of a
+ // declaration
+ // e.g. var i = 1;
+ // var j = new Number(1);
+ return a == b;
+ } else {
+ return a === b;
+ }
+ }
+
+ return {
+ "string": useStrictEquality,
+ "boolean": useStrictEquality,
+ "number": useStrictEquality,
+ "null": useStrictEquality,
+ "undefined": useStrictEquality,
+
+ "nan": function( b ) {
+ return isNaN( b );
+ },
+
+ "date": function( b, a ) {
+ return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
+ },
+
+ "regexp": function( b, a ) {
+ return QUnit.objectType( b ) === "regexp" &&
+ // the regex itself
+ a.source === b.source &&
+ // and its modifers
+ a.global === b.global &&
+ // (gmi) ...
+ a.ignoreCase === b.ignoreCase &&
+ a.multiline === b.multiline &&
+ a.sticky === b.sticky;
+ },
+
+ // - skip when the property is a method of an instance (OOP)
+ // - abort otherwise,
+ // initial === would have catch identical references anyway
+ "function": function() {
+ var caller = callers[callers.length - 1];
+ return caller !== Object && typeof caller !== "undefined";
+ },
+
+ "array": function( b, a ) {
+ var i, j, len, loop;
+
+ // b could be an object literal here
+ if ( QUnit.objectType( b ) !== "array" ) {
+ return false;
+ }
+
+ len = a.length;
+ if ( len !== b.length ) {
+ // safe and faster
+ return false;
+ }
+
+ // track reference to avoid circular references
+ parents.push( a );
+ for ( i = 0; i < len; i++ ) {
+ loop = false;
+ for ( j = 0; j < parents.length; j++ ) {
+ if ( parents[j] === a[i] ) {
+ loop = true;// dont rewalk array
+ }
+ }
+ if ( !loop && !innerEquiv(a[i], b[i]) ) {
+ parents.pop();
+ return false;
+ }
+ }
+ parents.pop();
+ return true;
+ },
+
+ "object": function( b, a ) {
+ var i, j, loop,
+ // Default to true
+ eq = true,
+ aProperties = [],
+ bProperties = [];
+
+ // comparing constructors is more strict than using
+ // instanceof
+ if ( a.constructor !== b.constructor ) {
+ // Allow objects with no prototype to be equivalent to
+ // objects with Object as their constructor.
+ if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
+ ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
+ return false;
+ }
+ }
+
+ // stack constructor before traversing properties
+ callers.push( a.constructor );
+ // track reference to avoid circular references
+ parents.push( a );
+
+ for ( i in a ) { // be strict: don't ensures hasOwnProperty
+ // and go deep
+ loop = false;
+ for ( j = 0; j < parents.length; j++ ) {
+ if ( parents[j] === a[i] ) {
+ // don't go down the same path twice
+ loop = true;
+ }
+ }
+ aProperties.push(i); // collect a's properties
+
+ if (!loop && !innerEquiv( a[i], b[i] ) ) {
+ eq = false;
+ break;
+ }
+ }
+
+ callers.pop(); // unstack, we are done
+ parents.pop();
+
+ for ( i in b ) {
+ bProperties.push( i ); // collect b's properties
+ }
+
+ // Ensures identical properties name
+ return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
+ }
+ };
+ }());
+
+ innerEquiv = function() { // can take multiple arguments
+ var args = [].slice.apply( arguments );
+ if ( args.length < 2 ) {
+ return true; // end transition
+ }
+
+ return (function( a, b ) {
+ if ( a === b ) {
+ return true; // catch the most you can
+ } else if ( a === null || b === null || typeof a === "undefined" ||
+ typeof b === "undefined" ||
+ QUnit.objectType(a) !== QUnit.objectType(b) ) {
+ return false; // don't lose time with error prone cases
+ } else {
+ return bindCallbacks(a, callbacks, [ b, a ]);
+ }
+
+ // apply transition with (1..n) arguments
+ }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) );
+ };
+
+ return innerEquiv;
+}());
+
+/**
+ * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
+ * http://flesler.blogspot.com Licensed under BSD
+ * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
+ *
+ * @projectDescription Advanced and extensible data dumping for Javascript.
+ * @version 1.0.0
+ * @author Ariel Flesler
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
+ */
+QUnit.jsDump = (function() {
+ function quote( str ) {
+ return '"' + str.toString().replace( /"/g, '\\"' ) + '"';
+ }
+ function literal( o ) {
+ return o + "";
+ }
+ function join( pre, arr, post ) {
+ var s = jsDump.separator(),
+ base = jsDump.indent(),
+ inner = jsDump.indent(1);
+ if ( arr.join ) {
+ arr = arr.join( "," + s + inner );
+ }
+ if ( !arr ) {
+ return pre + post;
+ }
+ return [ pre, inner + arr, base + post ].join(s);
+ }
+ function array( arr, stack ) {
+ var i = arr.length, ret = new Array(i);
+ this.up();
+ while ( i-- ) {
+ ret[i] = this.parse( arr[i] , undefined , stack);
+ }
+ this.down();
+ return join( "[", ret, "]" );
+ }
+
+ var reName = /^function (\w+)/,
+ jsDump = {
+ // type is used mostly internally, you can fix a (custom)type in advance
+ parse: function( obj, type, stack ) {
+ stack = stack || [ ];
+ var inStack, res,
+ parser = this.parsers[ type || this.typeOf(obj) ];
+
+ type = typeof parser;
+ inStack = inArray( obj, stack );
+
+ if ( inStack !== -1 ) {
+ return "recursion(" + (inStack - stack.length) + ")";
+ }
+ if ( type === "function" ) {
+ stack.push( obj );
+ res = parser.call( this, obj, stack );
+ stack.pop();
+ return res;
+ }
+ return ( type === "string" ) ? parser : this.parsers.error;
+ },
+ typeOf: function( obj ) {
+ var type;
+ if ( obj === null ) {
+ type = "null";
+ } else if ( typeof obj === "undefined" ) {
+ type = "undefined";
+ } else if ( QUnit.is( "regexp", obj) ) {
+ type = "regexp";
+ } else if ( QUnit.is( "date", obj) ) {
+ type = "date";
+ } else if ( QUnit.is( "function", obj) ) {
+ type = "function";
+ } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
+ type = "window";
+ } else if ( obj.nodeType === 9 ) {
+ type = "document";
+ } else if ( obj.nodeType ) {
+ type = "node";
+ } else if (
+ // native arrays
+ toString.call( obj ) === "[object Array]" ||
+ // NodeList objects
+ ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
+ ) {
+ type = "array";
+ } else if ( obj.constructor === Error.prototype.constructor ) {
+ type = "error";
+ } else {
+ type = typeof obj;
+ }
+ return type;
+ },
+ separator: function() {
+ return this.multiline ? this.HTML ? " " : "\n" : this.HTML ? " " : " ";
+ },
+ // extra can be a number, shortcut for increasing-calling-decreasing
+ indent: function( extra ) {
+ if ( !this.multiline ) {
+ return "";
+ }
+ var chr = this.indentChar;
+ if ( this.HTML ) {
+ chr = chr.replace( /\t/g, " " ).replace( / /g, " " );
+ }
+ return new Array( this._depth_ + (extra||0) ).join(chr);
+ },
+ up: function( a ) {
+ this._depth_ += a || 1;
+ },
+ down: function( a ) {
+ this._depth_ -= a || 1;
+ },
+ setParser: function( name, parser ) {
+ this.parsers[name] = parser;
+ },
+ // The next 3 are exposed so you can use them
+ quote: quote,
+ literal: literal,
+ join: join,
+ //
+ _depth_: 1,
+ // This is the list of parsers, to modify them, use jsDump.setParser
+ parsers: {
+ window: "[Window]",
+ document: "[Document]",
+ error: function(error) {
+ return "Error(\"" + error.message + "\")";
+ },
+ unknown: "[Unknown]",
+ "null": "null",
+ "undefined": "undefined",
+ "function": function( fn ) {
+ var ret = "function",
+ // functions never have name in IE
+ name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
+
+ if ( name ) {
+ ret += " " + name;
+ }
+ ret += "( ";
+
+ ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
+ return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
+ },
+ array: array,
+ nodelist: array,
+ "arguments": array,
+ object: function( map, stack ) {
+ var ret = [ ], keys, key, val, i;
+ QUnit.jsDump.up();
+ keys = [];
+ for ( key in map ) {
+ keys.push( key );
+ }
+ keys.sort();
+ for ( i = 0; i < keys.length; i++ ) {
+ key = keys[ i ];
+ val = map[ key ];
+ ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
+ }
+ QUnit.jsDump.down();
+ return join( "{", ret, "}" );
+ },
+ node: function( node ) {
+ var len, i, val,
+ open = QUnit.jsDump.HTML ? "<" : "<",
+ close = QUnit.jsDump.HTML ? ">" : ">",
+ tag = node.nodeName.toLowerCase(),
+ ret = open + tag,
+ attrs = node.attributes;
+
+ if ( attrs ) {
+ for ( i = 0, len = attrs.length; i < len; i++ ) {
+ val = attrs[i].nodeValue;
+ // IE6 includes all attributes in .attributes, even ones not explicitly set.
+ // Those have values like undefined, null, 0, false, "" or "inherit".
+ if ( val && val !== "inherit" ) {
+ ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" );
+ }
+ }
+ }
+ ret += close;
+
+ // Show content of TextNode or CDATASection
+ if ( node.nodeType === 3 || node.nodeType === 4 ) {
+ ret += node.nodeValue;
+ }
+
+ return ret + open + "/" + tag + close;
+ },
+ // function calls it internally, it's the arguments part of the function
+ functionArgs: function( fn ) {
+ var args,
+ l = fn.length;
+
+ if ( !l ) {
+ return "";
+ }
+
+ args = new Array(l);
+ while ( l-- ) {
+ // 97 is 'a'
+ args[l] = String.fromCharCode(97+l);
+ }
+ return " " + args.join( ", " ) + " ";
+ },
+ // object calls it internally, the key part of an item in a map
+ key: quote,
+ // function calls it internally, it's the content of the function
+ functionCode: "[code]",
+ // node calls it internally, it's an html attribute value
+ attribute: quote,
+ string: quote,
+ date: quote,
+ regexp: literal,
+ number: literal,
+ "boolean": literal
+ },
+ // if true, entities are escaped ( <, >, \t, space and \n )
+ HTML: false,
+ // indentation unit
+ indentChar: " ",
+ // if true, items in a collection, are separated by a \n, else just a space.
+ multiline: true
+ };
+
+ return jsDump;
+}());
+
+// from jquery.js
+function inArray( elem, array ) {
+ if ( array.indexOf ) {
+ return array.indexOf( elem );
+ }
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ if ( array[ i ] === elem ) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * Javascript Diff Algorithm
+ * By John Resig (http://ejohn.org/)
+ * Modified by Chu Alan "sprite"
+ *
+ * Released under the MIT license.
+ *
+ * More Info:
+ * http://ejohn.org/projects/javascript-diff-algorithm/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over"
+ */
+QUnit.diff = (function() {
+ /*jshint eqeqeq:false, eqnull:true */
+ function diff( o, n ) {
+ var i,
+ ns = {},
+ os = {};
+
+ for ( i = 0; i < n.length; i++ ) {
+ if ( !hasOwn.call( ns, n[i] ) ) {
+ ns[ n[i] ] = {
+ rows: [],
+ o: null
+ };
+ }
+ ns[ n[i] ].rows.push( i );
+ }
+
+ for ( i = 0; i < o.length; i++ ) {
+ if ( !hasOwn.call( os, o[i] ) ) {
+ os[ o[i] ] = {
+ rows: [],
+ n: null
+ };
+ }
+ os[ o[i] ].rows.push( i );
+ }
+
+ for ( i in ns ) {
+ if ( !hasOwn.call( ns, i ) ) {
+ continue;
+ }
+ if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) {
+ n[ ns[i].rows[0] ] = {
+ text: n[ ns[i].rows[0] ],
+ row: os[i].rows[0]
+ };
+ o[ os[i].rows[0] ] = {
+ text: o[ os[i].rows[0] ],
+ row: ns[i].rows[0]
+ };
+ }
+ }
+
+ for ( i = 0; i < n.length - 1; i++ ) {
+ if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
+ n[ i + 1 ] == o[ n[i].row + 1 ] ) {
+
+ n[ i + 1 ] = {
+ text: n[ i + 1 ],
+ row: n[i].row + 1
+ };
+ o[ n[i].row + 1 ] = {
+ text: o[ n[i].row + 1 ],
+ row: i + 1
+ };
+ }
+ }
+
+ for ( i = n.length - 1; i > 0; i-- ) {
+ if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
+ n[ i - 1 ] == o[ n[i].row - 1 ]) {
+
+ n[ i - 1 ] = {
+ text: n[ i - 1 ],
+ row: n[i].row - 1
+ };
+ o[ n[i].row - 1 ] = {
+ text: o[ n[i].row - 1 ],
+ row: i - 1
+ };
+ }
+ }
+
+ return {
+ o: o,
+ n: n
+ };
+ }
+
+ return function( o, n ) {
+ o = o.replace( /\s+$/, "" );
+ n = n.replace( /\s+$/, "" );
+
+ var i, pre,
+ str = "",
+ out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
+ oSpace = o.match(/\s+/g),
+ nSpace = n.match(/\s+/g);
+
+ if ( oSpace == null ) {
+ oSpace = [ " " ];
+ }
+ else {
+ oSpace.push( " " );
+ }
+
+ if ( nSpace == null ) {
+ nSpace = [ " " ];
+ }
+ else {
+ nSpace.push( " " );
+ }
+
+ if ( out.n.length === 0 ) {
+ for ( i = 0; i < out.o.length; i++ ) {
+ str += "" + out.o[i] + oSpace[i] + "";
+ }
+ }
+ else {
+ if ( out.n[0].text == null ) {
+ for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
+ str += "" + out.o[n] + oSpace[n] + "";
+ }
+ }
+
+ for ( i = 0; i < out.n.length; i++ ) {
+ if (out.n[i].text == null) {
+ str += "" + out.n[i] + nSpace[i] + " ";
+ }
+ else {
+ // `pre` initialized at top of scope
+ pre = "";
+
+ for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
+ pre += "" + out.o[n] + oSpace[n] + "";
+ }
+ str += " " + out.n[i].text + nSpace[i] + pre;
+ }
+ }
+ }
+
+ return str;
+ };
+}());
+
+// for CommonJS enviroments, export everything
+if ( typeof exports !== "undefined" ) {
+ extend( exports, QUnit );
+}
+
+// get at whatever the global object is, like window in browsers
+}( (function() {return this;}.call()) ));
diff --git a/license.txt b/license.txt
index 6c15aac..bee264a 100644
--- a/license.txt
+++ b/license.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2013 Bjorn Holine
+Copyright (c) 2016 Bjorn Holine
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
diff --git a/locations.json b/locations.json
deleted file mode 100644
index 9c099bd..0000000
--- a/locations.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"id":"1","name":"Chipotle Minneapolis","lat":"44.947464","lng":"-93.320826","address":"3040 Excelsior Blvd","address2":"","city":"Minneapolis","state":"MN","postal":"55416","phone":"612-922-6662","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"2","name":"Chipotle St. Louis Park","lat":"44.930810","lng":"-93.347877","address":"5480 Excelsior Blvd.","address2":"","city":"St. Louis Park","state":"MN","postal":"55416","phone":"952-922-1970","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"3","name":"Chipotle Minneapolis","lat":"44.991565","lng":"-93.216323","address":"2600 Hennepin Ave.","address2":"","city":"Minneapolis","state":"MN","postal":"55404","phone":"612-377-6035","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"4","name":"Chipotle Golden Valley","lat":"44.983935","lng":"-93.380542","address":"515 Winnetka Ave. N","address2":"","city":"Golden Valley","state":"MN","postal":"55427","phone":"763-544-2530","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"5","name":"Chipotle Hopkins","lat":"44.924363","lng":"-93.410158","address":"786 Mainstreet","address2":"","city":"Hopkins","state":"MN","postal":"55343","phone":"952-935-0044","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"6","name":"Chipotle Minneapolis","lat":"44.973557","lng":"-93.275111","address":"1040 Nicollet Ave","address2":"","city":"Minneapolis","state":"MN","postal":"55403","phone":"612-659-7955","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"7","name":"Chipotle Minneapolis","lat":"44.97774","lng":"-93.270909","address":"50 South 6th","address2":"","city":"Minneapolis","state":"MN","postal":"55402","phone":"612-333-0434","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"8","name":"Chipotle Edina","lat":"44.879826","lng":"-93.321280","address":"6801 York Avenue South","address2":"","city":"Edina","state":"MN","postal":"55435","phone":"952-926-6651","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"9","name":"Chipotle Minnetonka","lat":"44.970495","lng":"-93.437430","address":"12509 Wayzata Blvd","address2":"","city":"Minnetonka","state":"MN","postal":"55305","phone":"952-252-4900","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"10","name":"Chipotle Minneapolis","lat":"44.972808","lng":"-93.247153","address":"229 Cedar Ave S","address2":"","city":"Minneapolis","state":"MN","postal":"55454","phone":"612-659-7830","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"11","name":"Chipotle Minneapolis","lat":"44.987687","lng":"-93.257581","address":"225 Hennepin Ave E","address2":"","city":"Minneapolis","state":"MN","postal":"55414","phone":"612-331-6330","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"12","name":"Chipotle Minneapolis","lat":"44.973665","lng":"-93.227023","address":"800 Washington Ave SE","address2":"","city":"Minneapolis","state":"MN","postal":"55414","phone":"612-378-7078","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"13","name":"Chipotle Bloomington","lat":"44.8458631","lng":"-93.2860161","address":"322 South Ave","address2":"","city":"Bloomington","state":"MN","postal":"55425","phone":"952-252-3800","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"14","name":"Chipotle Wayzata","lat":"44.9716626","lng":"-93.4967757","address":"1313 Wayzata Blvd","address2":"","city":"Wayzata","state":"MN","postal":"55391","phone":"952-473-7100","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"15","name":"Chipotle Eden Prairie","lat":"44.859761","lng":"-93.436379","address":"13250 Technology Dr","address2":"","city":"Eden Prairie","state":"MN","postal":"55344","phone":"952-934-5955","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"16","name":"Chipotle Plymouth","lat":"45.019846","lng":"-93.481832","address":"3425 Vicksburg Lane N","address2":"","city":"Plymouth","state":"MN","postal":"55447","phone":"763-519-0063","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"17","name":"Chipotle Roseville","lat":"44.998965","lng":"-93.194622","address":"860 Rosedale Center Plaza","address2":"","city":"Roseville","state":"MN","postal":"55113","phone":"651-633-2300","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"18","name":"Chipotle St. Paul","lat":"44.939865","lng":"-93.136768","address":"867 Grand Ave","address2":"","city":"St. Paul","state":"MN","postal":"55105","phone":"651-602-0560","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"19","name":"Chipotle Chanhassen","lat":"44.858736","lng":"-93.533661","address":"560 W 79th","address2":"","city":"Chanhassen","state":"MN","postal":"55317","phone":"952-294-0301","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""},{"id":"20","name":"Chipotle St. Paul","lat":"44.945127","lng":"-93.095368","address":"29 5th St West","address2":"","city":"St. Paul","state":"MN","postal":"55102","phone":"651-291-5411","web":"http:\/\/www.chipotle.com","hours1":"Mon-Sun 11am-10pm","hours2":"","hours3":""}]
\ No newline at end of file
diff --git a/maxdistance-example.html b/maxdistance-example.html
deleted file mode 100644
index d834564..0000000
--- a/maxdistance-example.html
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
- Map Example - Maximum Distance
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/modal-example.html b/modal-example.html
deleted file mode 100644
index a837659..0000000
--- a/modal-example.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
- Map Example - Modal Window
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/noform-example.html b/noform-example.html
deleted file mode 100644
index 86122e5..0000000
--- a/noform-example.html
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
- Map Example - No Form for ASP.net
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/options.md b/options.md
new file mode 100644
index 0000000..32bfdd5
--- /dev/null
+++ b/options.md
@@ -0,0 +1,138 @@
+## Standard settings
+
+| Property | Default | Description |
+|------------------------------|---------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| ajaxData | null | Allows custom data to be sent with the AJAX request. Set the setting to an object with your properties and values. |
+| altDistanceNoResult | false | Display no results message vs. all locations when closest location is further than distanceAlert setting. |
+| apiKey | null | Set to your Google Maps API key when using Lazy Load setting. Value is unused if lazyLoadMap is false. |
+| autoComplete | false | Set to true to enable Google Places autocomplete. Note the slight markup differences in the example file. |
+| autoCompleteDisableListener | false | Disable the listener that immediately triggers a search when an auto complete location option is selected. |
+| autoCompleteOptions | {} | Google Places autocomplete [options object](https://developers.google.com/maps/documentation/javascript/places-autocomplete#add_autocomplete). |
+| autoGeocode | false | Set to true if you want to use the HTML5 geolocation API (good for mobile) to geocode the user's location. **SSL is required**. |
+| bounceMarker | true | Deprecated - no longer applies. |
+| catMarkers | null | Multiple replacement marker images based on categories object. Value should be array with image path followed by dimensions - ex value: catMarkers : {'Restaurant' : ['img/red-marker.svg', 32, 32]} |
+| dataLocation | 'data/locations.json' | The path to the location data. |
+| dataRaw | null | Accepts raw KML, XML, or JSON instead of using a remote file. |
+| dataType | 'json' | The format of the data source. Accepted values include kml, xml, json, and jsonp. |
+| debug | false | Set to true to enable console.log helper function that can be used for debugging. |
+| defaultLat | null | If using defaultLoc, set this to the default location latitude. |
+| defaultLng | null | If using defaultLoc, set this to the default location longitude. |
+| defaultLoc | false | If true, the map will load with a default location immediately. Set slideMap to false if you want to use this. |
+| disableAlphaMarkers | false | Disable displaying markers and location list indicators with alpha characters. |
+| distanceAlert | 60 | Displays alert if there are no locations with 60 m/km of the user's location. Set to -1 to disable. |
+| dragSearch | false | Set to true to perform a new search after the map is dragged. |
+| exclusiveFiltering | false | Set to true to enable exclusive taxonomy filtering rather than the default inclusive. |
+| exclusiveTax | null | Set to an array of taxonomies that should filter exclusively vs. inclusively. |
+| featuredDistance | null | Restrict the featured locations from displaying by a certain distance (use number value). |
+| featuredLocations | false | Set to true to enable featuring locations at the top of the location list (no matter the distance). Add featured=”true” to featured locations in your XML or JSON locations data. |
+| fullMapStart | false | Set to true if you want to immediately show a map of all locations. The map will center and zoom automatically. |
+| fullMapStartBlank | false | Set to a zoom integer if you want to immediately show a blank map without any locations. |
+| fullMapStartListLimit | false | Set to a number to limit the number of items displayed in the location list with full map start. |
+| infoBubble | null | InfoBubble settings object. [See example for available parameters](https://googlemaps.github.io/js-info-bubble/examples/example.html). Map and content parameters are set by default. |
+| inlineDirections | false | Set to true to enable displaying directions within the app instead of an off-site link. |
+| lazyLoadMap | false | Set to true to lazy load the Google Maps API script. Make sure to also set the apiKey setting and do not include the maps.google.com script manually. |
+| lengthUnit | 'm' | The unit of length. Default is m for miles, change to km for kilometers. |
+| listColor1 | '#ffffff' | Background color of the odd list elements. |
+| listColor2 | '#eeeeee' | Background color of the even list elements. |
+| loading | false | Set to true to display a loading animated gif next to the submit button. |
+| locationsPerPage | 10 | If using pagination, the number of locations to display per page. |
+| mapSettings | { zoom : 12, mapTypeId: google.maps.MapTypeId.ROADMAP } | Google maps settings object. Add all settings including zoom and map type if overriding. Set zoom to 0 to automatically center and zoom to show all display markers on the map |
+| mapSettingsID | '' | New Google Map ID used for map management and styling. Please refer to [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) and [Map ID with Styling](https://developers.google.com/maps/documentation/javascript/examples/map-id-style) in the Google API docs. |
+| markerCluster | null | Additional Clusterer settings object. Markers and map are set by default - use this to set other parameters such as algorithm, renderer, etc. [See docs](https://github.com/googlemaps/js-markerclusterer?tab=readme-ov-file#migration). |
+| markerImg | null | Replacement marker image used for all locations |
+| markerDim | null | Replacement marker dimensions object - ex value: { height: 20, width: 20 } |
+| maxDistance | false | Set to true if you want to give users an option to limit the distance from their location to the markers. |
+| modal | false | Shows the map container within a modal window. Set slideMap to false and this option to true to use. |
+| nameAttribute | 'name' | If using nameSearch, the data attribute used for the location name in the data file. Supports one or multiple values separated by commas. |
+| nameSearch | false | Set to true to allow searching for locations by name using separate searchID field. |
+| noForm | false | Set to true if you aren't able to use form tags (ASP.net WebForms). |
+| openNearest | false | Set to true to highlight the nearest location automatically after searching. |
+| originMarker | false | Display a marker at the origin. |
+| originMarkerDim | null | Replacement origin marker dimensions object - ex value: { height: 20, width: 20 } |
+| originMarkerImg | null | Replacement origin marker image. |
+| pagination | false | Set to true to enable displaying location results in multiple "pages." |
+| querystringParams | false | Set to true to enable query string support for passing input variables from page to page. |
+| selectedMarkerImg | null | Selected marker image. |
+| selectedMarkerImgDim | null | Selected marker image dimensions object - ex value: { height: 20, width: 20 } |
+| sessionStorage | false | Set to true to enable Window.sessionStorage for user's location when autoGeocode is enabled. |
+| slideMap | true | First hides the map container and then uses jQuery’s slideDown method to reveal the map. |
+| sortBy | null | Set to an object for custom sorting that accepts three properties: method ('alpha', 'date', or 'numeric'), order ('asc', or 'desc'), and prop (property in your data to sort by such as name, city, distance, etc.). |
+| storeLimit | 26 | The number of closest locations displayed at one time. Set to -1 for unlimited. |
+| taxonomyFilters | null | Filtering object that can be used to set up live filtering (see categories example). |
+| visibleMarkersList | false | Set to true to have the location list only show data from markers that are visible on the map. |
+| xmlElement | 'marker' | XML element used for locations (tag). |
+
+## HTML elements
+| Property | Default | Description |
+|--------------------------|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
+| addressID | 'bh-sl-address' | ID of the address input form field. |
+| closeIcon | 'bh-sl-close-icon' | Class of element that displays the close icon to close the modal window. |
+| formContainer | 'bh-sl-form-container' | Class of the container around the form. |
+| formID | 'bh-sl-user-location' | ID of the input form. |
+| geocodeID | null | Set to the ID of an element to connect the HTML5 geolocation API to a button instead of firing automatically. |
+| lengthSwapID | 'bh-sl-length-swap' | Set to the ID of a select element within the form container to allow users to swap between the distance length unit (miles/kilometers). |
+| loadingContainer | 'bh-sl-loading' | Class of element container that displays the loading animated gif. |
+| locationList | 'bh-sl-loc-list' | Class of the container around the location list. |
+| mapID | 'bh-sl-map' | ID of the div where the actual Google Map is displayed. |
+| maxDistanceID | 'bh-sl-maxdistance' | ID of the select element for the maximum distance options. |
+| modalContent | 'bh-sl-modal-content' | Class of element container around the content of the modal window. |
+| modalWindow | 'bh-sl-modal-window' | Class of element of the actual modal window |
+| orderID | 'bh-sl-order' | ID of the select form field for custom sort order handling of location results. |
+| overlay | 'bh-sl-overlay' | Class of element that fills 100% of the window and fills with a transparent background image. |
+| regionID | 'bh-sl-region' | ID of the region select form field for country region biasing. |
+| searchID | 'bh-sl-search' | ID of the search input form field for location name searching. |
+| sortID | 'bh-sl-sort' | ID of the select form field for custom sorting of location results. |
+| taxonomyFiltersContainer | 'bh-sl-filters-container' | Class of the container around the filters. |
+
+## Templates
+| Property | Default | Description |
+|---------------------------|-------------------------------------------------------------------------------|------------------------------------------------------------------------------------------|
+| infowindowTemplatePath | 'assets/js/plugins/storeLocator/templates/infowindow-description.html' | Path to the default infowindow template. |
+| listTemplatePath | 'assets/js/plugins/storeLocator/templates/location-list-description.html' | Path to the default list template. |
+| KMLinfowindowTemplatePath | 'assets/js/plugins/storeLocator/templates/kml-infowindow-description.html' | Path to the KML infowindow template – used if dataType is set to kml. |
+| KMLlistTemplatePath | 'assets/js/plugins/storeLocator/templates/kml-location-list-description.html' | Path to the KML list template – used if dataType is set to kml. |
+| listTemplateID | null | ID of list template if using inline Handlebar templates instead of separate files. |
+| infowindowTemplateID | null | ID of infowindow template if using inline Handlebar templates instead of separate files. |
+
+## Callbacks
+
+| Property | Default | Description |
+|---------------------------------------------------------------------------|---------|-------------------------------------------|
+| [callbackAutoGeoSuccess](callbacks/callback-autogeosuccess.md) | null | Geolocation API success callback |
+| [callbackBeforeSend](callbacks/callback-beforesend.md) | null | Before location data request callback |
+| [callbackCloseDirections](callbacks/callback-closedirections.md) | null | Close directions callback |
+| [callbackCreateMarker](callbacks/callback-createmarker.md) | null | Create marker override callback |
+| [callbackDirectionsRequest](callbacks/callback-directionsrequest.md) | null | Directions request callback |
+| [callbackFilters](callbacks/callback-filters.md) | null | Filters callback |
+| [callbackFormVals](callbacks/callback-formvals.md) | null | Form values callback |
+| [callbackGeocodeRestrictions](callbacks/callback-geocode-restrictions.md) | null | Geocoding component restrictions callback |
+| [callbackJsonp](callbacks/callback-jsonp.md) | null | JSONP callback |
+| [callbackListClick](callbacks/callback-listclick.md) | null | Location list click callback |
+| [callbackMapSet](callbacks/callback-mapset.md) | null | Map set callback |
+| [callbackMarkerClick](callbacks/callback-markerclick.md) | null | Marker click callback |
+| [callbackModalClose](callbacks/callback-modalclose.md) | null | Modal close callback |
+| [callbackModalOpen](callbacks/callback-modalopen.md) | null | Modal open callback |
+| [callbackModalReady](callbacks/callback-modalready.md) | null | Modal ready callback |
+| [callbackNearestLoc](callbacks/callback-nearestloc.md) | null | Nearest location callback |
+| [callbackNoResults](callbacks/callback-noresults.md) | null | No results callback |
+| [callbackNotify](callbacks/callback-notification.md) | null | Notification callback |
+| [callbackOrder](callbacks/callback-order.md) | null | Order callback |
+| [callbackPageChange](callbacks/callback-pagechange.md) | null | Page change callback |
+| [callbackRegion](callbacks/callback-region.md) | null | Region callback |
+| [callbackSorting](callbacks/callback-sorting.md) | null | Sorting callback |
+| [callbackSuccess](callbacks/callback-success.md) | null | Success callback |
+
+## Language options
+| Property | Default | Description |
+|-----------------------|--------------------------------------------------------------------------------------------|------------------|
+| addressErrorAlert | 'Unable to find address' | Language setting |
+| autoGeocodeErrorAlert | 'Automatic location detection failed. Please fill in your address or zip code.' | Language setting |
+| distanceErrorAlert | 'Unfortunately, our closest location is more than ' | Language setting |
+| kilometerLang | 'kilometer' | Language setting |
+| kilometersLang | 'kilometers' | Language setting |
+| mileLang | 'mile' | Language setting |
+| milesLang | 'miles' | Language setting |
+| noResultsTitle | 'No results' | Language setting |
+| noResultsDesc | 'No locations were found with the given criteria. Please modify your selections or input.' | Language setting |
+| nextPage | 'Next »' | Language setting |
+| prevPage | '« Prev' | Language setting |
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..eb1502a
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2872 @@
+{
+ "name": "jquery-storelocator-plugin",
+ "version": "3.4.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "jquery-storelocator-plugin",
+ "version": "3.4.1",
+ "license": "MIT",
+ "dependencies": {
+ "handlebars": ">=4.7.7",
+ "jquery": "^3.6.3"
+ },
+ "devDependencies": {
+ "grunt": "~1.6.1",
+ "grunt-banner": "~0.6.0",
+ "grunt-contrib-clean": "^2.0.1",
+ "grunt-contrib-concat": "~2.1.0",
+ "grunt-contrib-copy": "^1.0.0",
+ "grunt-contrib-cssmin": "~4.0.0",
+ "grunt-contrib-handlebars": "^3.0.0",
+ "grunt-contrib-jshint": "~3.2.0",
+ "grunt-contrib-qunit": "~7.0.0",
+ "grunt-contrib-sass": "^2.0.0",
+ "grunt-contrib-uglify": "~5.2.2",
+ "grunt-contrib-watch": "~1.1.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
+ "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.19.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
+ "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
+ "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/@babel/highlight/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "18.14.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.0.tgz",
+ "integrity": "sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+ "dev": true
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/argparse/node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true
+ },
+ "node_modules/array-each": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz",
+ "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/array-slice": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz",
+ "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/async": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
+ "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
+ "dev": true
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dev": true,
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
+ "node_modules/bl/node_modules/readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/bl/node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/body": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz",
+ "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=",
+ "dev": true,
+ "dependencies": {
+ "continuable-cache": "^0.3.1",
+ "error": "^7.0.0",
+ "raw-body": "~1.1.0",
+ "safe-json-parse": "~1.0.1"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz",
+ "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=",
+ "dev": true
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "dev": true
+ },
+ "node_modules/clean-css": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz",
+ "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==",
+ "dev": true,
+ "dependencies": {
+ "source-map": "~0.6.0"
+ },
+ "engines": {
+ "node": ">= 10.0"
+ }
+ },
+ "node_modules/cli": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz",
+ "integrity": "sha512-41U72MB56TfUMGndAKK8vJ78eooOD4Z5NOL4xEfjc0c23s+6EYKXlXsmACBVclLP1yOfWCgEganVzddVrSNoTg==",
+ "dev": true,
+ "dependencies": {
+ "exit": "0.1.2",
+ "glob": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=0.2.5"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/colors": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
+ "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.1.90"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "node_modules/console-browserify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+ "integrity": "sha512-duS7VP5pvfsNLDvL1O4VOEbw37AI3A4ZUQYemvDlnpGrNu9tprR7BYWpDYwC0Xia0Zxz5ZupdiIrUp0GH1aXfg==",
+ "dev": true,
+ "dependencies": {
+ "date-now": "^0.1.4"
+ }
+ },
+ "node_modules/continuable-cache": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz",
+ "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=",
+ "dev": true
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "dev": true
+ },
+ "node_modules/cosmiconfig": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.0.0.tgz",
+ "integrity": "sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==",
+ "dev": true,
+ "dependencies": {
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/cosmiconfig/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/cosmiconfig/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/cross-fetch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
+ "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
+ "dev": true,
+ "dependencies": {
+ "node-fetch": "2.6.7"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "dependencies": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ },
+ "engines": {
+ "node": ">=4.8"
+ }
+ },
+ "node_modules/dargs": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/dargs/-/dargs-6.1.0.tgz",
+ "integrity": "sha512-5dVBvpBLBnPwSsYXqfybFyehMmC/EenKEcf23AhCTgTf48JFBbmJKqoZBsERDnjL0FyiVTYWdFsRfTLHxLyKdQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/date-now": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+ "integrity": "sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw==",
+ "dev": true
+ },
+ "node_modules/dateformat": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
+ "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/detect-file": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz",
+ "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/devtools-protocol": {
+ "version": "0.0.1094867",
+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1094867.tgz",
+ "integrity": "sha512-pmMDBKiRVjh0uKK6CT1WqZmM3hBVSgD+N2MrgyV1uNizAZMw4tx6i/RTc+/uCsKSCmg0xXx7arCP/OFcIwTsiQ==",
+ "dev": true
+ },
+ "node_modules/dom-serializer": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
+ "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
+ "dev": true,
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "entities": "^2.0.0"
+ }
+ },
+ "node_modules/dom-serializer/node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ]
+ },
+ "node_modules/dom-serializer/node_modules/entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+ "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
+ "dev": true
+ },
+ "node_modules/domhandler": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
+ "integrity": "sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ==",
+ "dev": true,
+ "dependencies": {
+ "domelementtype": "1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+ "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==",
+ "dev": true,
+ "dependencies": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
+ "node_modules/duplexer": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
+ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
+ "dev": true
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
+ "integrity": "sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ==",
+ "dev": true
+ },
+ "node_modules/error": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz",
+ "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==",
+ "dev": true,
+ "dependencies": {
+ "string-template": "~0.2.1"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eventemitter2": {
+ "version": "0.4.14",
+ "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz",
+ "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==",
+ "dev": true
+ },
+ "node_modules/exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expand-tilde": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
+ "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==",
+ "dev": true,
+ "dependencies": {
+ "homedir-polyfill": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "dev": true
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/faye-websocket": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
+ "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
+ "dev": true,
+ "dependencies": {
+ "websocket-driver": ">=0.5.1"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "dev": true,
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/figures": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
+ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/file-sync-cmp": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz",
+ "integrity": "sha512-0k45oWBokCqh2MOexeYKpyqmGKG+8mQ2Wd8iawx+uWd/weWJQAZ6SoPybagdCI4xFisag8iAR77WPm4h3pTfxA==",
+ "dev": true
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/findup-sync": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz",
+ "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==",
+ "dev": true,
+ "dependencies": {
+ "detect-file": "^1.0.0",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.4",
+ "resolve-dir": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/fined": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz",
+ "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==",
+ "dev": true,
+ "dependencies": {
+ "expand-tilde": "^2.0.2",
+ "is-plain-object": "^2.0.3",
+ "object.defaults": "^1.1.0",
+ "object.pick": "^1.2.0",
+ "parse-filepath": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/flagged-respawn": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz",
+ "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/for-own": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz",
+ "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==",
+ "dev": true,
+ "dependencies": {
+ "for-in": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "dev": true
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/gaze": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
+ "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
+ "dev": true,
+ "dependencies": {
+ "globule": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/getobject": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz",
+ "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/global-modules": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
+ "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==",
+ "dev": true,
+ "dependencies": {
+ "global-prefix": "^1.0.1",
+ "is-windows": "^1.0.1",
+ "resolve-dir": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/global-prefix": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz",
+ "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==",
+ "dev": true,
+ "dependencies": {
+ "expand-tilde": "^2.0.2",
+ "homedir-polyfill": "^1.0.1",
+ "ini": "^1.3.4",
+ "is-windows": "^1.0.1",
+ "which": "^1.2.14"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/globule": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz",
+ "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "~7.1.1",
+ "lodash": "~4.17.10",
+ "minimatch": "~3.0.2"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/grunt": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.1.tgz",
+ "integrity": "sha512-/ABUy3gYWu5iBmrUSRBP97JLpQUm0GgVveDCp6t3yRNIoltIYw7rEj3g5y1o2PGPR2vfTRGa7WC/LZHLTXnEzA==",
+ "dev": true,
+ "dependencies": {
+ "dateformat": "~4.6.2",
+ "eventemitter2": "~0.4.13",
+ "exit": "~0.1.2",
+ "findup-sync": "~5.0.0",
+ "glob": "~7.1.6",
+ "grunt-cli": "~1.4.3",
+ "grunt-known-options": "~2.0.0",
+ "grunt-legacy-log": "~3.0.0",
+ "grunt-legacy-util": "~2.0.1",
+ "iconv-lite": "~0.6.3",
+ "js-yaml": "~3.14.0",
+ "minimatch": "~3.0.4",
+ "nopt": "~3.0.6"
+ },
+ "bin": {
+ "grunt": "bin/grunt"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/grunt-banner": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/grunt-banner/-/grunt-banner-0.6.0.tgz",
+ "integrity": "sha512-50H/Wxydlf+ifve5Jzcz9oB4jr6oCGEPyfhEDUsl2NEMX80cWUJqVMXSHBr2n9Rb3nd+rRSKeQzqNxWrqoyQ1A==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "peerDependencies": {
+ "grunt": ">=0.4.0"
+ }
+ },
+ "node_modules/grunt-banner/node_modules/ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/grunt-banner/node_modules/chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/grunt-banner/node_modules/supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/grunt-contrib-clean": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.1.tgz",
+ "integrity": "sha512-uRvnXfhiZt8akb/ZRDHJpQQtkkVkqc/opWO4Po/9ehC2hPxgptB9S6JHDC/Nxswo4CJSM0iFPT/Iym3cEMWzKA==",
+ "dev": true,
+ "dependencies": {
+ "async": "^3.2.3",
+ "rimraf": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "grunt": ">=0.4.5"
+ }
+ },
+ "node_modules/grunt-contrib-concat": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-concat/-/grunt-contrib-concat-2.1.0.tgz",
+ "integrity": "sha512-Vnl95JIOxfhEN7bnYIlCgQz41kkbi7tsZ/9a4usZmxNxi1S2YAIOy8ysFmO8u4MN26Apal1O106BwARdaNxXQw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "source-map": "^0.5.3"
+ },
+ "engines": {
+ "node": ">=0.12.0"
+ },
+ "peerDependencies": {
+ "grunt": ">=1.4.1"
+ }
+ },
+ "node_modules/grunt-contrib-concat/node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/grunt-contrib-copy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz",
+ "integrity": "sha512-gFRFUB0ZbLcjKb67Magz1yOHGBkyU6uL29hiEW1tdQ9gQt72NuMKIy/kS6dsCbV0cZ0maNCb0s6y+uT1FKU7jA==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^1.1.1",
+ "file-sync-cmp": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/grunt-contrib-copy/node_modules/ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/grunt-contrib-copy/node_modules/chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/grunt-contrib-copy/node_modules/supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/grunt-contrib-cssmin": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-4.0.0.tgz",
+ "integrity": "sha512-jXU+Zlk8Q8XztOGNGpjYlD/BDQ0n95IHKrQKtFR7Gd8hZrzgqiG1Ra7cGYc8h2DD9vkSFGNlweb9Q00rBxOK2w==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "clean-css": "^5.0.1",
+ "maxmin": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ }
+ },
+ "node_modules/grunt-contrib-handlebars": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-handlebars/-/grunt-contrib-handlebars-3.0.0.tgz",
+ "integrity": "sha512-Zh5fSnyhfOGIDieFNy1eVEqmdB0y2cGkFaceKkfJM4v5OEZP880+SGjbmmuriaVZsG7bOnj0Fg7wP4722GUBSw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.1",
+ "handlebars": "^4.7.7",
+ "nsdeclare": "0.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/grunt-contrib-jshint": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-3.2.0.tgz",
+ "integrity": "sha512-pcXWCSZWfoMSvcV4BwH21TUtLtcX0Ms8IGuOPIcLeXK3fud9KclY7iqMKY94jFx8TxZzh028YYtpR+io8DiEaQ==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "~4.1.2",
+ "hooker": "^0.2.3",
+ "jshint": "~2.13.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/grunt-contrib-qunit": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-qunit/-/grunt-contrib-qunit-7.0.0.tgz",
+ "integrity": "sha512-phSuAixAzyvizgUV9Nw7ip0/G5lVeWA/4DUifcdKPOgjTAY7QIWsTugxiXMGeVwMKnDOS3vTMpL/VyEw8z7yYw==",
+ "dev": true,
+ "dependencies": {
+ "eventemitter2": "^6.4.9",
+ "p-each-series": "^2.2.0",
+ "puppeteer": "^19.7.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/grunt-contrib-qunit/node_modules/eventemitter2": {
+ "version": "6.4.9",
+ "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz",
+ "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==",
+ "dev": true
+ },
+ "node_modules/grunt-contrib-sass": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-sass/-/grunt-contrib-sass-2.0.0.tgz",
+ "integrity": "sha512-RxZ3dlZZTX4YBPu2zMu84NPYgJ2AYAlIdEqlBaixNVyLNbgvJBGUr5Gi0ec6IiOQbt/I/z7uZVN9HsRxgznIRw==",
+ "dev": true,
+ "dependencies": {
+ "async": "^2.6.1",
+ "chalk": "^2.4.1",
+ "cross-spawn": "^6.0.5",
+ "dargs": "^6.0.0",
+ "which": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/grunt-contrib-sass/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/grunt-contrib-sass/node_modules/async": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+ "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.17.14"
+ }
+ },
+ "node_modules/grunt-contrib-sass/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/grunt-contrib-sass/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/grunt-contrib-sass/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "node_modules/grunt-contrib-sass/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/grunt-contrib-sass/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/grunt-contrib-uglify": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-5.2.2.tgz",
+ "integrity": "sha512-ITxiWxrjjP+RZu/aJ5GLvdele+sxlznh+6fK9Qckio5ma8f7Iv8woZjRkGfafvpuygxNefOJNc+hfjjBayRn2Q==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "maxmin": "^3.0.0",
+ "uglify-js": "^3.16.1",
+ "uri-path": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/grunt-contrib-uglify/node_modules/uglify-js": {
+ "version": "3.17.4",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
+ "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
+ "dev": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/grunt-contrib-watch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz",
+ "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==",
+ "dev": true,
+ "dependencies": {
+ "async": "^2.6.0",
+ "gaze": "^1.1.0",
+ "lodash": "^4.17.10",
+ "tiny-lr": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/grunt-contrib-watch/node_modules/async": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+ "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+ "dev": true,
+ "dependencies": {
+ "lodash": "^4.17.14"
+ }
+ },
+ "node_modules/grunt-known-options": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz",
+ "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/grunt-legacy-log": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz",
+ "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==",
+ "dev": true,
+ "dependencies": {
+ "colors": "~1.1.2",
+ "grunt-legacy-log-utils": "~2.1.0",
+ "hooker": "~0.2.3",
+ "lodash": "~4.17.19"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/grunt-legacy-log-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz",
+ "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "~4.1.0",
+ "lodash": "~4.17.19"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/grunt-legacy-util": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz",
+ "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==",
+ "dev": true,
+ "dependencies": {
+ "async": "~3.2.0",
+ "exit": "~0.1.2",
+ "getobject": "~1.0.0",
+ "hooker": "~0.2.3",
+ "lodash": "~4.17.21",
+ "underscore.string": "~3.3.5",
+ "which": "~2.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/grunt-legacy-util/node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "node_modules/grunt-legacy-util/node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/grunt/node_modules/grunt-cli": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz",
+ "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==",
+ "dev": true,
+ "dependencies": {
+ "grunt-known-options": "~2.0.0",
+ "interpret": "~1.1.0",
+ "liftup": "~3.0.1",
+ "nopt": "~4.0.1",
+ "v8flags": "~3.2.0"
+ },
+ "bin": {
+ "grunt": "bin/grunt"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/grunt/node_modules/grunt-cli/node_modules/nopt": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
+ "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
+ "dev": true,
+ "dependencies": {
+ "abbrev": "1",
+ "osenv": "^0.1.4"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ }
+ },
+ "node_modules/gzip-size": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
+ "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==",
+ "dev": true,
+ "dependencies": {
+ "duplexer": "^0.1.1",
+ "pify": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/handlebars": {
+ "version": "4.7.7",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
+ "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
+ "dependencies": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.0",
+ "source-map": "^0.6.1",
+ "wordwrap": "^1.0.0"
+ },
+ "bin": {
+ "handlebars": "bin/handlebars"
+ },
+ "engines": {
+ "node": ">=0.4.7"
+ },
+ "optionalDependencies": {
+ "uglify-js": "^3.1.4"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/homedir-polyfill": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
+ "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
+ "dev": true,
+ "dependencies": {
+ "parse-passwd": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/hooker": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz",
+ "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/htmlparser2": {
+ "version": "3.8.3",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
+ "integrity": "sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q==",
+ "dev": true,
+ "dependencies": {
+ "domelementtype": "1",
+ "domhandler": "2.3",
+ "domutils": "1.5",
+ "entities": "1.0",
+ "readable-stream": "1.1"
+ }
+ },
+ "node_modules/http-parser-js": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz",
+ "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==",
+ "dev": true
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true
+ },
+ "node_modules/interpret": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
+ "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==",
+ "dev": true
+ },
+ "node_modules/is-absolute": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
+ "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
+ "dev": true,
+ "dependencies": {
+ "is-relative": "^1.0.0",
+ "is-windows": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
+ "node_modules/is-core-module": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+ "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-relative": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
+ "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
+ "dev": true,
+ "dependencies": {
+ "is-unc-path": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-unc-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
+ "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
+ "dev": true,
+ "dependencies": {
+ "unc-path-regex": "^0.1.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "node_modules/isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/jquery": {
+ "version": "3.6.3",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz",
+ "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg=="
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jshint": {
+ "version": "2.13.6",
+ "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.6.tgz",
+ "integrity": "sha512-IVdB4G0NTTeQZrBoM8C5JFVLjV2KtZ9APgybDA1MK73xb09qFs0jCXyQLnCOp1cSZZZbvhq/6mfXHUTaDkffuQ==",
+ "dev": true,
+ "dependencies": {
+ "cli": "~1.0.0",
+ "console-browserify": "1.1.x",
+ "exit": "0.1.x",
+ "htmlparser2": "3.8.x",
+ "lodash": "~4.17.21",
+ "minimatch": "~3.0.2",
+ "strip-json-comments": "1.0.x"
+ },
+ "bin": {
+ "jshint": "bin/jshint"
+ }
+ },
+ "node_modules/jshint/node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/liftup": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz",
+ "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==",
+ "dev": true,
+ "dependencies": {
+ "extend": "^3.0.2",
+ "findup-sync": "^4.0.0",
+ "fined": "^1.2.0",
+ "flagged-respawn": "^1.0.1",
+ "is-plain-object": "^2.0.4",
+ "object.map": "^1.0.1",
+ "rechoir": "^0.7.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/liftup/node_modules/findup-sync": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz",
+ "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==",
+ "dev": true,
+ "dependencies": {
+ "detect-file": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "micromatch": "^4.0.2",
+ "resolve-dir": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "node_modules/livereload-js": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz",
+ "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==",
+ "dev": true
+ },
+ "node_modules/lodash": {
+ "version": "4.17.20",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
+ "dev": true
+ },
+ "node_modules/make-iterator": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz",
+ "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==",
+ "dev": true,
+ "dependencies": {
+ "kind-of": "^6.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/maxmin": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-3.0.0.tgz",
+ "integrity": "sha512-wcahMInmGtg/7c6a75fr21Ch/Ks1Tb+Jtoan5Ft4bAI0ZvJqyOw8kkM7e7p8hDSzY805vmxwHT50KcjGwKyJ0g==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "figures": "^3.2.0",
+ "gzip-size": "^5.1.1",
+ "pretty-bytes": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+ },
+ "node_modules/mkdirp-classic": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+ "dev": true
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
+ },
+ "node_modules/nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+ "dev": true
+ },
+ "node_modules/node-fetch": {
+ "version": "2.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
+ "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/nopt": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+ "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==",
+ "dev": true,
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ }
+ },
+ "node_modules/nsdeclare": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/nsdeclare/-/nsdeclare-0.1.0.tgz",
+ "integrity": "sha512-Wb+BpXFfacpp1cgrQoO5Q2wKHACuMlUE6uayGFFLF3yVuXejBp5Rflk991hWvTQbUuQslXTIvBNOHqYTvzBSjA==",
+ "dev": true
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object.defaults": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz",
+ "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==",
+ "dev": true,
+ "dependencies": {
+ "array-each": "^1.0.1",
+ "array-slice": "^1.0.0",
+ "for-own": "^1.0.0",
+ "isobject": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object.map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz",
+ "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==",
+ "dev": true,
+ "dependencies": {
+ "for-own": "^1.0.0",
+ "make-iterator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==",
+ "dev": true,
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/os-homedir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+ "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/osenv": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+ "deprecated": "This package is no longer supported.",
+ "dev": true,
+ "dependencies": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "node_modules/p-each-series": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz",
+ "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-filepath": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz",
+ "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==",
+ "dev": true,
+ "dependencies": {
+ "is-absolute": "^1.0.0",
+ "map-cache": "^0.2.0",
+ "path-root": "^0.1.1"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse-passwd": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
+ "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/path-root": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz",
+ "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==",
+ "dev": true,
+ "dependencies": {
+ "path-root-regex": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-root-regex": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz",
+ "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pretty-bytes": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
+ "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "dev": true
+ },
+ "node_modules/pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/puppeteer": {
+ "version": "19.7.1",
+ "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.7.1.tgz",
+ "integrity": "sha512-Hampj7jHlicySL1sSLHCwoFoRCi6RcEbnZmRE5brtbk0mp6Td33+9kWQD2eFs09772JIt00ybPKr50Gt7Y18Xg==",
+ "deprecated": "< 22.8.2 is no longer supported",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "cosmiconfig": "8.0.0",
+ "https-proxy-agent": "5.0.1",
+ "progress": "2.0.3",
+ "proxy-from-env": "1.1.0",
+ "puppeteer-core": "19.7.1"
+ },
+ "engines": {
+ "node": ">=14.1.0"
+ }
+ },
+ "node_modules/puppeteer-core": {
+ "version": "19.7.1",
+ "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.7.1.tgz",
+ "integrity": "sha512-4b5Go25IA+0xrUIw0Qtqi4nxc0qwdu/C7VT1+tFPl1W27207YT+7bxfANC3PjXMlS6bcbzinCf5YfGqMl8tfyQ==",
+ "dev": true,
+ "dependencies": {
+ "cross-fetch": "3.1.5",
+ "debug": "4.3.4",
+ "devtools-protocol": "0.0.1094867",
+ "extract-zip": "2.0.1",
+ "https-proxy-agent": "5.0.1",
+ "proxy-from-env": "1.1.0",
+ "rimraf": "3.0.2",
+ "tar-fs": "2.1.1",
+ "unbzip2-stream": "1.4.3",
+ "ws": "8.11.0"
+ },
+ "engines": {
+ "node": ">=14.1.0"
+ },
+ "peerDependencies": {
+ "chromium-bidi": "0.4.3",
+ "typescript": ">= 4.7.4"
+ },
+ "peerDependenciesMeta": {
+ "chromium-bidi": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/puppeteer-core/node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.9.4",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
+ "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz",
+ "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=",
+ "dev": true,
+ "dependencies": {
+ "bytes": "1",
+ "string_decoder": "0.10"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+ "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
+ "dev": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "node_modules/rechoir": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
+ "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
+ "dev": true,
+ "dependencies": {
+ "resolve": "^1.9.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-dir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz",
+ "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==",
+ "dev": true,
+ "dependencies": {
+ "expand-tilde": "^2.0.0",
+ "global-modules": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safe-json-parse": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz",
+ "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=",
+ "dev": true
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "node_modules/semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
+ "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
+ "dev": true
+ },
+ "node_modules/string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+ "dev": true
+ },
+ "node_modules/string-template": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz",
+ "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=",
+ "dev": true
+ },
+ "node_modules/strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
+ "integrity": "sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg==",
+ "dev": true,
+ "bin": {
+ "strip-json-comments": "cli.js"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tar-fs": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
+ "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
+ "dev": true,
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp-classic": "^0.5.2",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.1.4"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "dev": true,
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tar-stream/node_modules/readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/tar-stream/node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+ "dev": true
+ },
+ "node_modules/tiny-lr": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz",
+ "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==",
+ "dev": true,
+ "dependencies": {
+ "body": "^5.1.0",
+ "debug": "^3.1.0",
+ "faye-websocket": "~0.10.0",
+ "livereload-js": "^2.3.0",
+ "object-assign": "^4.1.0",
+ "qs": "^6.4.0"
+ }
+ },
+ "node_modules/tiny-lr/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/tiny-lr/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true
+ },
+ "node_modules/uglify-js": {
+ "version": "3.12.4",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.4.tgz",
+ "integrity": "sha512-L5i5jg/SHkEqzN18gQMTWsZk3KelRsfD1wUVNqtq0kzqWQqcJjyL8yc1o8hJgRrWqrAl2mUFbhfznEIoi7zi2A==",
+ "optional": true,
+ "bin": {
+ "uglifyjs": "bin/uglifyjs"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/unbzip2-stream": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
+ "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
+ "dev": true,
+ "dependencies": {
+ "buffer": "^5.2.1",
+ "through": "^2.3.8"
+ }
+ },
+ "node_modules/unc-path-regex": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
+ "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/underscore.string": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz",
+ "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==",
+ "dev": true,
+ "dependencies": {
+ "sprintf-js": "^1.1.1",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/uri-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz",
+ "integrity": "sha512-8pMuAn4KacYdGMkFaoQARicp4HSw24/DHOVKWqVRJ8LhhAwPPFpdGvdL9184JVmUwe7vz7Z9n6IqI6t5n2ELdg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/v8flags": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz",
+ "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==",
+ "dev": true,
+ "dependencies": {
+ "homedir-polyfill": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true
+ },
+ "node_modules/websocket-driver": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
+ "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
+ "dev": true,
+ "dependencies": {
+ "http-parser-js": ">=0.5.1",
+ "safe-buffer": ">=5.1.0",
+ "websocket-extensions": ">=0.1.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/websocket-extensions": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
+ "node_modules/wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "node_modules/ws": {
+ "version": "8.11.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
+ "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "dev": true,
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..93fcd3f
--- /dev/null
+++ b/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "jquery-storelocator-plugin",
+ "version": "3.4.1",
+ "description": "This jQuery plugin takes advantage of Google Maps API version 3 to create an easy to implement store locator. No back-end programming is required, you just need to feed it KML, XML, or JSON data with all the location information.",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/bjorn2404/jQuery-Store-Locator-Plugin.git"
+ },
+ "keywords": [
+ "jquery-plugin",
+ "ecosystem:jquery",
+ "locator",
+ "dealer",
+ "store",
+ "location",
+ "locations",
+ "maps",
+ "map",
+ "stores",
+ "find",
+ "finder"
+ ],
+ "author": {
+ "name": "Bjorn Holine",
+ "url": "https://www.bjornblog.com/"
+ },
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/bjorn2404/jQuery-Store-Locator-Plugin/issues"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "dependencies": {
+ "handlebars": ">=4.7.7",
+ "jquery": "^3.6.3"
+ },
+ "devDependencies": {
+ "grunt": "~1.6.1",
+ "grunt-banner": "~0.6.0",
+ "grunt-contrib-clean": "^2.0.1",
+ "grunt-contrib-concat": "~2.1.0",
+ "grunt-contrib-copy": "^1.0.0",
+ "grunt-contrib-cssmin": "~4.0.0",
+ "grunt-contrib-handlebars": "^3.0.0",
+ "grunt-contrib-jshint": "~3.2.0",
+ "grunt-contrib-qunit": "~7.0.0",
+ "grunt-contrib-sass": "^2.0.0",
+ "grunt-contrib-uglify": "~5.2.2",
+ "grunt-contrib-watch": "~1.1.0"
+ }
+}
diff --git a/readme.md b/readme.md
index 7219fd2..aa5d600 100644
--- a/readme.md
+++ b/readme.md
@@ -1,15 +1,457 @@
# [jQuery Google Maps Store Locator Plugin](http://www.bjornblog.com/web/jquery-store-locator-plugin)
+### The files you're looking for are in the dist/ directory
### [Please see my blog for more information and examples](http://www.bjornblog.com/web/jquery-store-locator-plugin).
-This jQuery plugin takes advantage of Google Maps API version 3 to create an easy to implement store locator. No back-end programming is required, you just need to feed it KML, XML, or JSON data with all the location information. How you create the data file is up to you. I originally created this for a company that didn’t have many locations, so I just used a static XML file. I also decided to geocode all the locations beforehand, to make sure it was quick and to avoid any potential geocoding errors. However, if you’re familiar with JavaScript you could easily make a modification to geocode everything on the fly (I may add this as an option at some point).
+This jQuery plugin takes advantage of Google Maps API version 3 to create an easy to implement store locator. No
+back-end programming is required, you just need to feed it KML, XML, or JSON data with all the location information.
+How you create the data file is up to you. I originally created this for a company that didn’t have many locations, so I
+just used a static XML file. You will need to geocode your locations beforehand or use a geocoding API service if
+you want to try to do it on the fly. The reason for this is that all free geocoding APIs have strict limits that would
+easily be exceeded. In the end, you're much better off storing the coordinates versus having to look them up for each
+location on each request.
-A note on the distance calculation: this plugin currently uses a distance function that was originally programmed by [Chris Pietschmann](http://pietschsoft.com/post/2008/02/01/Calculate-Distance-Between-Geocodes-in-C-and-JavaScript.aspx). Google Maps API version 3 does include a distance calculation service ([Google Distance Matrix API](http://code.google.com/apis/maps/documentation/distancematrix/)) but I decided not to use it because of the current request limits, which seem somewhat low. In addition, because the plugin currently calculates each location’s distance one by one, it appeared that I would have to re-structure some things to make all the distance calculations at once (or risk making many request for one location lookup). So, the distance calculation is “as the crow flies” instead of a road distance.
+A note on the distance calculation: this plugin currently uses a distance function that I found on the blog of
+[Chris Pietschmann](http://pietschsoft.com/post/2008/02/01/Calculate-Distance-Between-Geocodes-in-C-and-JavaScript.aspx).
+Google Maps API version 3 does include a distance calculation service
+([Google Distance Matrix API](http://code.google.com/apis/maps/documentation/distancematrix/)) but I decided not to use
+it because of the current request limits, which seem somewhat low. For v2 I also tried experimenting with the Directions API to request distances but also found the limits to be too restrictive. So, the distance calculation is “as the crow flies” instead of a
+road distance calculation. However, if you use the inline directions option that does provide the distance that's returned via the directions request.
+
+Last, it’s very important to note that the plugin requires the Handlebars template engine. This separates the markup of the
+infowindows and location list elements so that they can easily be restructured. Handlebars pretty slick, will read
+Mustache templates, and the built-in helpers really come in handy. Depending on what your data source is, 2 of the
+4 total templates will be used (KML vs XML or JSON) and there are options to set the paths of each template if you don’t
+want them in the default location. If you’re developing something for mobile devices the templates can be pre-compiled
+for even faster loading.
+
+### WordPress version
+
+[Cardinal Locator - WordPress store locator plugin](https://cardinalwp.com/) is now available, which uses this jQuery plugin
+as a base and all of the settings can be set via a settings page in the WP dashboard. It also integrates with core
+WordPress features such as custom post types for location data and custom taxonomies for location categorization and
+filtering.
-Handlebars is now required: It’s very important to note that the plugin now requires the Handlebars template engine. I made this change so that the data that’s displayed in the location list and the infowindows can be easily customized. I also wanted to separate the bulk of the layout additions from the main plugin file. Handlebars pretty slick, will read Mustache templates, and the built-in helpers can really come in handy. Depending on what your data source is, 2 of the 4 total templates will be used (KML vs XML or JSON) and there are options to set the paths of each template if you don’t want them in the default location. If you’re developing something for mobile devices the templates can be pre-compiled for even faster loading.
## Changelog
+### Version 3.4.1
+
+* Fixed default directions link (inline directions disabled). Props [@HarldVerbiesenVIPMarketingBV](https://github.com/HarldVerbiesenVIPMarketingBV) via [issue #304](https://github.com/bjorn2404/jQuery-Store-Locator-Plugin/issues/304)
+
+### Version 3.4.0
+
+* Added support for new Map ID with mapSettingsID setting. Please refer to [Use Map IDs](https://developers.google.com/maps/documentation/get-map-id) and [Map ID with Styling](https://developers.google.com/maps/documentation/javascript/examples/map-id-style) in the Google API docs.
+* Added support for new [Advanced Markers](https://developers.google.com/maps/documentation/javascript/advanced-markers/overview) while maintaining support for the deprecated (not yet discontinued) marker functionality. mapSettingsID setting needs to be set and marker library needs to be included - see previous item.
+* Fixed bugs with pagination.
+* Fixed new "Google Maps JavaScript API has been loaded directly without loading=async" notice.
+* Improved pagination accessibility and functionality.
+* Updated jQuery in example files to v3.7.1
+
+### Version 3.3.0
+
+* Updated max distance functionality to make distance changes apply dynamically vs. having to manually click button.
+
+### Version 3.2.1
+
+* Swapped old maps.google.com API domain to maps.googleapis.com in all example files.
+
+### Version 3.2.0
+
+* Added new Google Maps lazy load setting and example file - see new lazyLoadMap and apiKey settings.
+
+### Version 3.1.14
+
+* Added label tags to radio button markup in categories example file.
+* Fix - reverted removal of zoom reset to 0 after taxonomy filtering due to introduction of new issue.
+* Fixed comment typos.
+
+### Version 3.1.13
+
+* Fixed additional disable filtering functionality related to select options and radio buttons by globally tracking the disabled values.
+* Removed zoom reset to zero on taxonomy filtering to keep searched location in view.
+* Updated maybeDisableFilterOptions to run when full map start or default location settings are enabled.
+
+### Version 3.1.12
+
+* Added automatic reset functionality that fires when address input field value is removed (changed to blank).
+* Fixed additional issues with new disable filtering functionality with select fields, radio buttons, and updated address input value.
+* Fixed markerClusterer library usage of deprecated Google Maps addDomListener. Props [@marcohanke](https://github.com/marcohanke) via [#294](https://github.com/bjorn2404/jQuery-Store-Locator-Plugin/pull/294/)
+* Updated jQuery version in example files.
+* Updated query string functionality to fill in address and name search with query string values in search form.
+
+### Version 3.1.11
+
+* Fixed issue with new disable filtering functionality with radio buttons.
+* Fixed issue with reset button where all locations were duplicated - introduced in v3.0.1. Reported in https://github.com/bjorn2404/jQuery-Store-Locator-Plugin/issues/293.
+* Updated functionality to reset disabled form filters when Reset button is clicked.
+
+### Version 3.1.10
+
+* Added map marker accessibility.
+* Deprecated bounceMarker setting due to Google Charts API deprecation and poor animation results with Google marker labels.
+* Fixed issue with new disable filtering functionality when location objet property is missing.
+* Fixed marker labels not working with previous technique. Swapped to google.maps.Marker label parameter.
+* Updated package devDependencies.
+
+### Version 3.1.9
+
+* Added functionality to disable input and option fields that don't have matching values in the current location set after filtering when inclusive filtering is used (default).
+* Extended nameAttribute settings so multiple attributes can be searched. Separate attribute names with commas.
+* Fixed location set length scenario when fullMapStart is enabled and taxFilters is reset and name search and origin are empty.
+* Show empty result message if no locations with default sorting.
+* Temporary fix for missing Google Maps callback parameter console error - the parameter requirement was not previously enforced on the API side.
+* Updated deprecated google.maps.event.addDomListener usage with window.addEventListener.
+
+### Version 3.1.8
+
+* Added coordinate range check to exclude locations with invalid latitude and longitude values.
+
+### Version 3.1.7
+
+* Fixed empty name search value not clearing previous value.
+* Fixed openNearest setting not working in combination with querystringParams setting.
+* Fixed openNearest setting not opening correct location with custom sorting (sortBy) enabled.
+
+### Version 3.1.6
+
+* Added extra check to make sure mapping doesn't fire twice when defaultLoc is enabled and fullMapStart is also enabled.
+ fullMapStart is not needed when defaultLoc is enabled and this is just a preventative to avoid user errors.
+* Fixed centering issue on initial search with maxDistance enabled introduced with fitBounds updates in last update.
+
+### Version 3.1.5
+
+* Added Google Maps object as a parameter for [callbackMarkerClick](callbacks/callback-markerclick.md) callback.
+* Added a zoom listener after fitBounds is used to prevent high zoom levels after name search and taxonomy filtering.
+* Improved zooming when maxDistance setting is enabled taking advantage of the fitBounds method.
+
+### Version 3.1.4
+
+* Fixed name search filter value not clearing if form input is cleared.
+
+### Version 3.1.3
+
+* Fixed empty name search field value overriding filter results.
+* Fixed groups of filters not applying together.
+* Fixed potential error from occurring if fullMapStartListLimit is set, and the number of locations is less than the limit.
+* Updated bundled Handlebars to v4.7.7 that addresses a [critical vulnerability](https://github.com/advisories/GHSA-f2jv-r9rf-7988).
+* Updated jQuery version in example files to v3.6.0.
+
+### Version 3.1.2
+
+* Fixed name search issue introduced in v3.1.1. Reverted to previous matching pattern only for name searches and still using the new patter for taxonomy matching.
+
+### Version 3.1.1
+
+* Enhanced filtering regular expression to better account for exact matches vs. substrings.
+* Fixed multi-category selection filtering issue introduced in last version 3.1.0.
+* Updated bundled Handlebars to v4.7.6.
+* Updated node modules.
+
+### Version 3.1.0
+
+* Added featuredDistance setting to restrict featured locations by a specified distance (number value should be used).
+* Updated bundled version of Handlebars to v4.5.1 due to security issue.
+* Updated taxonomy filtering REGEX and string replacements for international character support.
+
+### Version 3.0.1
+
+* Added custom order handling to tie into previously added custom sorting. Set order to asc or desc.
+* Added functionality to fill in search input with determined address when using autoGeocode or geocodeID settings.
+* Added Google Map object as parameter to callbackBeforeSend, callbackListClick, callbackModalReady, and callbackFilters callbacks.
+* Changed parseJSON to native JSON.parse due to deprecation in rawData processing function.
+* Fixed issue with mapReload (triggered with optional reset button) where storeLimit wasn't reset.
+* Updated sort-example.html example with order select field.
+
+### Version 3.0.0
+
+Version 3 has a breaking change with the dataLocation and dataType settings switching the default from XML to JSON.
+
+* Added ajaxData to allow custom data to be sent with the AJAX request. The setting accepts an object.
+* Added altDistanceNoResult setting to display no results message vs. all locations when closest location is further than distanceAlert setting.
+* Added callbackAutoGeoSuccess callback that fires after the geolocation API returns a successful result.
+* Added callbackFormVals callback that fires after the form values have been processed from the form.
+* Added callbackGeocodeRestrictions callback that allows the componentRestrictions object to be overridden.
+* Added callbackNearestLoc callback that fires when the nearest location is triggered with the openNearest setting.
+* Added callbackSorting callback that fires when when a new sorting method is selected.
+* Added component filtering for geocoding to better restrict by area.
+* Added length unit (distance miles/kilometers) front-end swap functionality, setting and example file.
+* Added mappingObject, originPoint, data, and page parameters to callbackSuccess callback.
+* Added new front-end sorting functionality, settings, and example file.
+* Added location data object as parameter of callbackDirectionsRequest callback.
+* Added openNearest setting to open/select the nearest location after searching.
+* Fixed issue with featuredLocations setting where featured locations at far distances would trigger distance alert.
+* Fixed issue with filtering values containing ampersands, which would not display any results - updated filtering regex.
+* Fixed issue where HTML5 Geolocation was skipped when using the fullMapStartBlank setting.
+* Fixed issue with pagination page numbers displaying after no results via PR from [heldr88](https://github.com/heldr88)
+* Fixed issue with taxonomy filtering and autoGeocode setting where HTML Geocoding API would run on every filter change.
+* Removed Less from the project as it is no longer needed with the Bootstrap update.
+* Swapped the default location data type to be JSON instead of XML.
+* Updated Bootstrap example file to make use of Bootstrap 4.
+* Updated taxonomy filtering so pagination is reset to first page after selecting a filter.
+
+### Version 2.7.4
+
+* Fixed error when filtering with query strings where filter values with spaces wouldn't work.
+* Updated processForm method so submitting the map removes focus from any of the form input/select fields instead of just the address input.
+* Updated filterData string replace methods to match string replace method in filters setup.
+
+### Version 2.7.3
+
+* Added ability to indicate multiple query string parameter values (for checkboxes) with a comma separated list value.
+* Added autoCompleteDisableListener setting to disable the listener that immediately triggers a search when an auto complete location option is selected.
+* Added blur to primary location input field after form submission to hide mobile keyboards.
+* Added check to exclusive filtering to make sure filter values are not undefined before proceeding with the regular expression.
+* Added functionality to automatically select/check filters on load from query string parameter values.
+* Added location details object to callbackListClick and callbackMarkerClick objects.
+* Fixed broken dragSearch functionality that was introduced after map scope pull request was merged.
+* Fixed Handlebars targeting issue triggered by placing an unordered list within the location list template.
+* Fixed issue with fullMapStart where conditional was checking if isNaN was true when it should have been false on fullMapStartListLimit setting.
+* Updated callbackListClick documentation to include second market object parameter.
+* Updated zooming to prevent fitBounds from being used when query string parameters are in use and the location has been set with bh-sl-address.
+
+### Version 2.7.2
+
+* Added [callbackRegion](callbacks/callback-region.md) callback, which allows region to be set before being sent to the Google Maps Geocoding API.
+* Fixed incorrect origin marker parameter order after code restructure.
+* Fixed [issue](https://github.com/bjorn2404/jQuery-Store-Locator-Plugin/issues/160) where searching by name after searching by address, without a new address, didn't reset the origin.
+* Merged pull-request from [ollea](https://github.com/ollea) that adds "getMap" function that returns a google.maps.Map instance.
+
+### Version 2.7.1
+
+* Hotfix to prevent potential error with updated filterData method if the category of a location is undefined.
+
+### Version 2.7.0
+
+* Added [callback documentation](callbacks.md).
+* Added callbackCreateMarker for custom marker overrides.
+* Added [InfoBubble](https://github.com/googlemaps/js-info-bubble) support and example file.
+* Added location results total count if HTML element with "bh-sl-total-results" class exists.
+* Added checks to replace non-ASCII characters when filtering.
+* Added reset functionality that can be triggered via a button that has the CSS class "bh-sl-reset".
+* Added query string parameter filter check so that results can be filtered with URL query strings.
+* Fixed issue with maxDistance and querystringParams settings combination.
+* Moved some functionality from processData into new separate methods.
+* Removed non-standard $1 RegExp property in processData method.
+
+### Version 2.6.2
+
+* Added callbackMapSet callback that fires after the map has been set up.
+* Fixed issue where locations without attributes set could get the attribute values from prior locations.
+* Fixed issue where pagination total number of pages was based on the full location set total instead of the storeLimit setting.
+* Removed form markup from initial query string example index file as it's not needed until the submission page.
+
+### Version 2.6.1
+
+* Added additional error handling when the plugin checks the closest location.
+* Added listener for autoComplete change so that the search processes when a new place is selected.
+* Fixed [issue with new boundary search](https://github.com/bjorn2404/jQuery-Store-Locator-Plugin/issues/127) AJAX params after a full address search was made.
+* Merged in pull request from [noclat](https://github.com/noclat) that added autoCompleteOptions setting.
+
+### Version 2.6.0
+
+* Added bounds and formatted address info from geocoding API to AJAX data parameters.
+* Added Marker Clusterer library support, setting and example file.
+* Added disableAlphaMarkers setting to completely disable displaying markers and location list indicators with alpha characters.
+* Fixed issue with combination of autoGeocode and originMarker settings.
+* Merged in pull request from [drcomix](https://github.com/drcomix) that fixed zoom issue with dragSearch setting.
+* Switched included remote scripts in example files to https.
+* Updated jQuery references to the latest version.
+
+### Version 2.5.3
+
+* Removed check from createMarker method that removed spaces from categories - created issues with categories that have spaces.
+* Re-worked handling of no results.
+* Updated createMarker method to ensure custom category marker images are converted to integers if strings are passed for dimensions.
+* Updated autoGeocode and default location functionality so that max distance is applied on initial load.
+
+### Version 2.5.2
+
+* Fixed pagination bubbling issue.
+* Fixed pagination issues with autoGeocode and dragSearch combinations.
+
+### Version 2.5.1
+
+* Fixed issues with visibleMarkersList and location list background colors and selection.
+
+### Version 2.5.0
+
+* Added new dragSearch setting which performs a new search when the map is dragged when enabled.
+* Added new geocodeID setting so that the HTML geocoding API can be triggered by a button instead of firing automatically.
+* Fixed issues with no results where clicking the marker would display data from the previous result and clicking the location list item would throw an error.
+* Merged in pull request from [hawkmeister](https://github.com/hawkmeister) with update to bower.json file with main property.
+* Merged in pull request from [hyperTwitch](https://github.com/hyperTwitch) with fixes for using fullMapStartListLimit in combination with a different store limit.
+* Updated jQuery references to the latest version.
+
+### Version 2.4.2
+
+* Fixed issue with new full map start location list limit where clicking on a marker that didn't have a list item
+displayed caused an error.
+* Fixed issue with settings combination of inline directions and default location.
+* Reverted change to new list limit so that it's always applied with full map start enabled.
+
+### Version 2.4.1
+
+* Changed new full map start list limit so that it's only applied on the initial load.
+* Fixed issue with new autocomplete setting where search was firing twice.
+
+### Version 2.4.0
+
+* Added new selected marker image options to highlight clicked marker.
+* Added Google Places autocomplete option and example file.
+* Added full map start location list limit setting.
+
+### Version 2.3.3
+
+* Removed code that temporarily hid the map and results, when there are no results, in favor of just displaying the no
+results message and empty map.
+
+### Version 2.3.2
+
+* Tweaked list label width styling.
+
+### Version 2.3.1
+
+* Added preventative styling to inline directions panel table.
+* Switched to unitless line-heights.
+
+### Version 2.3.0
+
+* Added fullMapStartBlank option to show a blank map without any locations initially. Set this setting to an integer,
+which will be applied as the initial Google Maps zoom value and will then fall back to the mapSettings zoom level after
+a search is performed.
+* Added fullMapStartBlank example file.
+* Fixed filters select field styling inconsistency.
+* Moved pagination container within map container div in pagination example to avoid confusion when combined with modal option.
+* Reworked styling so that all HTML example files are responsive by default.
+* Updated map-container ID in all example files with bh-sl prefix.
+
+### Version 2.2.2
+
+* Added preventative styling to avoid table conflicts with directions panel.
+* Fixed clearMarkers issue with inline directions enabled.
+
+### Version 2.2.1
+
+* Updated preventative styling to be more specific to the map container and added max-height img rule.
+
+### Version 2.2.0
+
+* Added check for Google Maps API.
+* Added Grunt Handlebars task for compiling Handlebars templates from src directory - will add more compatibility in future release.
+* Added preventative styling to avoid conflicts with CSS frameworks and resets.
+* Default design refresh.
+* Fixed bug with inline directions panel that occurred after multiple address submissions.
+* Removed sensor parameter from Google Maps API URL as it's no longer needed.
+* Switched the default plugin styling from LESS to SASS.
+* Updated included Handlebars to v4.0.5.
+
+### Version 2.1.0
+
+Includes contributions from from [Giancarlo Gomez](https://github.com/GiancarloGomez).
+
+* Added ability to pass in array object as dataRaw.
+* Added writeDebug console.log helper function for debugging.
+* Added sessionStorage option to store user's location when autoGeocode in enabled to prevent multiple lookups per session.
+* Fixed bug with inline directions panel that occurred after multiple address submissions.
+* Updated processForm method form field variables with empty string fallback values.
+
+### Version 2.0.9
+
+* Fixed issue when using catMarkers setting and not setting a location's category resulted in an error.
+
+### Version 2.0.8
+
+* Changed infowindow and location list templates so that the comma is added if the city is available.
+* Fixed issue with inline directions where "null" was prepended to the destination address.
+* Fixed close directions bug where close icon couldn't be clicked more than two times.
+* Fixed bug where form wasn't overriding query string parameters.
+* Updated processForm method to accept max distance query string parameter.
+* Updated processForm method to use existing origin data if it's present and matches to avoid unnecessary geocode
+requests.
+* Updated max distance check to less than or equal to the selected distance vs. just less than.
+* Updated regionID description in options.md for clarity.
+* Updated formEventHandler method to prevent ASP.net form submission on keydown instead of keyup.
+* Updated mapSettings description in options.md to highlight that zoom can be set to 0 for automatic centering and zooming.
+
+### Version 2.0.7
+
+* Fixed bug where reverse geocoding wasn't passing the origin to the templates (autogeocode and default location),
+causing incorrect direction links.
+* Updated location list directions link to use https.
+
+### Version 2.0.6
+
+* Added the option to filter data exclusively rather than inclusively with the exclusiveFiltering setting.
+* Added callbackFilters that fires when a filter is changed and returns the filter values if needed.
+* Added dataRaw option to use raw KML, XML or JSON data instead of the AJAX call.
+* Added basic raw data example rawdata-example.php file.
+* Added visibleMarkersList option that updates the location list to only display data from the markers that are curently
+displayed on the map.
+* Changed the distance error functionality so that the map centers and zooms automatically and all locations are
+displayed on the map.
+* Fixed issue with fullMapStart and inlineDirections setting combination.
+* Fixed issue with global olat and olng variables not being set with autoGeocode setting enabled.
+* Fixed issue with maxDistance and autoGeocode setting combination.
+
+### Version 2.0.5
+
+* Fixed typo with originMarker setup.
+* Made the originMarkerDim setting optional when setting a custom origin marker image - defaults to 32px by 32px.
+* Removed geocodeErrorAlert language option and switched error alerts to custom exceptions so users aren't shown
+multiple alerts.
+* Fixed bug with inline directions where close icon wasn't being removed on page reload.
+* Added callbackListClick that fires when a list element is clicked.
+* Added callbackMarkerClick that fires when a map marker is clicked.
+
+### Version 2.0.4
+
+* Fixed bug with maxDistance and pagination setting combination. The last page of of the pagination results was set to
+use the locationsPerPage setting instead of the remaining number, which could have resulted in the plugin trying to
+load undefined locations.
+* Fixed bugs with googleGeocode and reverseGoogleGeocode methods in which references to "this" were undefined.
+
+### Version 2.0.3
+
+* Fixed bug with maxDistance setting - updated locationsSetup method so that the locationset array uses array.push
+instead of incrementing via a passed in parameter, which was causing undefined array elements and causing errors.
+* Removed testing line from maxdistance-example.html that was left in.
+
+### Version 2.0.2
+
+* Updated the Handlebars.compile calls when using the inline template options to include the ID hash so that it's
+consistent with the other ID settings.
+* Fixed incorrect callback call in the modalClose method - callbackModalOpen to callbackModalClose.
+* Added callbackModalReady that fires when the the content of the modal is generated.
+* Fixed markerImg setting - previously threw error if markerDim wasn't set.
+
+### Version 2.0.1
+
+* Quick fix to remove a dupicate copyright notice in dist/ file. Copyright was removed from src/jquery.storelocator.js
+file to prevent duplication with the Grunt Banner task.
+
+### Version 2.0.0
+
+Version 2 is a complete rewrite of the plugin based on the "basic" plugin pattern of the
+[jQuery Boilerplate](http://jqueryboilerplate.com/). The overall file structure has changed, several of the [plugin
+settings](options.md) have changed and all of the CSS is now prefixed to avoid potential collisions. In other words,
+you're not going to be able to simply replace the main plugin file to upgrade to the latest version. I've been working
+on this update off and on for the past six months, so a lot has changed. I've also added many new features based on the most common requests I've received. The following list doesn't cover everything that's new but all of the important items to note:
+
+* [Grunt](http://gruntjs.com/) is now utilized to minify and compile the plugin and CSS. You only need to worry about running this if you're doing extensive modifications or are interested in submitting a pull request.
+* Plugin file structure and programming pattern have been reworked to follow the [basic jQuery Boilerplate pattern](https://github.com/jquery-boilerplate/jquery-patterns/blob/master/patterns/jquery.basic.plugin-boilerplate.js)
+* Many of the methods are public and can be called as needed
+* Google Maps settings are now exposed as a setting. Instead of trying to modify the plugin to make Google Maps settings changes simply use the mapSettings option to override.
+* Added inline directions option
+* Added multi-group live filtering via regex for quick category, etc. filtering.
+* Added option to search locations by name
+* Added option to set custom markers by category
+* Added option for paginating results
+* Added responsive Bootstrap example
+* Added region biasing setting to handle region/country select field
+* Added coordinates and address (user input) to primary AJAX GET request for better back-end integration
+* Added option to check for query string parameters
+
### Version 1.4.9
More contributions from [Mathieu Boillat](https://github.com/ollea) and [Jimmy Rittenborg](https://github.com/JimmyRittenborg) in addition to a few style updates:
@@ -104,7 +546,7 @@ a duplicate locationset declaration on line 328.
This is a large update that has many updates and improvements. It’s very important to note that the plugin now requires the [Handlebars](http://handlebarsjs.com) template engine. I made this change so that the data that’s displayed in the location list and the infowindows can be easily customized. I also wanted to separate the bulk of the layout additions from the main plugin file. Handlebars pretty slick, will read Mustache templates, and the built-in helpers can really come in handy. Depending on what your data source is, 2 of the 4 total templates will be used (KML vs XML or JSON) and there are options to set the paths of each template if you don’t want them in the default location. If you’re developing something for mobile devices the templates can be pre-compiled for even faster loading. Additionally, I’d also like to note that it probably makes more sense to use KML now as the data source over the other options but I’m definitely leaving XML and JSON support in. XML is still the default datatype but I may switch it to KML in the future.
-####New features:####
+#### New features: ####
**Kilometers option**
This was a no-brainer. You could make the change without too much trouble before but I thought I’d make it easier for the rest of the world.
@@ -179,4 +621,4 @@ Left a couple of console.logs in my code, which was causing IE to hang.
### Version 1.0
-This is my first jQuery plugin and the first time I’ve published anything on Github. Let me know if I can improve something or if I’m making some kind of mistake.
\ No newline at end of file
+This is my first jQuery plugin and the first time I’ve published anything on Github. Let me know if I can improve something or if I’m making some kind of mistake.
diff --git a/src/.jshintrc b/src/.jshintrc
new file mode 100644
index 0000000..2b50bba
--- /dev/null
+++ b/src/.jshintrc
@@ -0,0 +1,27 @@
+{
+ "curly": true,
+ "eqeqeq": true,
+ "immed": true,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "sub": true,
+ "undef": true,
+ "unused": true,
+ "boss": true,
+ "eqnull": true,
+ "browser": true,
+ "devel": false,
+ "globals": {
+ "alert": true,
+ "console": true,
+ "define": true,
+ "document": true,
+ "google": true,
+ "Handlebars": true,
+ "InfoBubble": true,
+ "jQuery": true,
+ "MarkerClusterer": true,
+ "window": true
+ }
+}
diff --git a/src/css/bootstrap-example.scss b/src/css/bootstrap-example.scss
new file mode 100644
index 0000000..e6bb36f
--- /dev/null
+++ b/src/css/bootstrap-example.scss
@@ -0,0 +1,299 @@
+$white: #fff;
+$gray: #ccc;
+$darkgray: #8e8e8e;
+$titlegray: #797874;
+$shadow: #656565;
+$textgray: #555;
+$darkergray: #2c2c2c;
+$black: #000;
+$blue: #005293;
+$red: #ae2118;
+$arialFont: Arial, Helvetica, sans-serif;
+
+/* Infowindow Roboto font override */
+.gm-style div, .gm-style span, .gm-style label, .gm-style a {
+ font-family: $arialFont;
+}
+
+.GMAMP-maps-pin-view {
+ color: $black;
+ font: 400 15px/1 $arialFont;
+}
+
+.bh-sl-error {
+ clear: both;
+ float: left;
+ width: 100%;
+ padding: 10px 0;
+ color: $red;
+ font-weight: bold;
+}
+
+.bh-sl-container {
+ color: $textgray;
+
+ /* Avoid image issues with Google Maps and CSS resets */
+ img {
+ max-width: none !important;
+ border-radius: 0 !important;
+ box-shadow: none !important;
+ }
+
+ /* Avoid issues with Google Maps and CSS frameworks */
+ > * {
+ box-sizing: content-box !important;
+ }
+
+ .jumbotron{
+ margin-bottom: 0;
+ }
+
+ .form-input {
+ input,select,label {
+ margin-right: 10px;
+ }
+ }
+
+ .loc-alt-dist {
+ display: none;
+ }
+
+ .bh-sl-loading {
+ float: left;
+ margin: 4px 0 0 10px;
+ width: 16px;
+ height: 16px;
+ background: url(../img/ajax-loader.gif) no-repeat;
+ }
+
+ .bh-sl-filters-container {
+ clear: both;
+ width: 100%;
+ margin: 15px 0;
+
+ .bh-sl-filters {
+ list-style: none;
+ float: left;
+ padding: 0;
+ margin: 0 100px 0 0;
+
+ li {
+ display: block;
+ clear: left;
+ float: left;
+ width: 100%;
+ margin: 5px 0;
+
+ label {
+ display: inline;
+ }
+
+ input {
+ display: block;
+ float: left;
+ margin: 2px 8px 2px 0;
+ }
+ }
+ }
+ }
+
+ .bh-sl-map-container {
+ a {
+ color: $blue;
+ text-decoration: none;
+ &:hover, &:active {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .bh-sl-loc-list {
+ height: 530px;
+ overflow-x: auto;
+ font-size: 13px;
+
+ ul {
+ display: block;
+ clear: left;
+ float: left;
+ width: 100%;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ li {
+ display: block;
+ clear: left;
+ float: left;
+ margin: 3% 4%;
+ cursor: pointer;
+ width: 92%;
+ border: 1px solid $white; /* Adding this to prevent moving li elements when adding the list-focus class*/
+ }
+ }
+
+ .list-label {
+ float: left;
+ margin: 10px 0 0 6px;
+ padding: 4px;
+ width: 27px;
+ text-align: center;
+ background: darken( $blue, 20% );
+ color: $white;
+ font-weight: bold;
+ border-radius: 15px;
+ }
+
+ .list-details {
+ float: left;
+ margin-left: 6px;
+ width: 80%;
+
+ .list-content {
+ padding: 10px;
+ }
+
+ .loc-dist {
+ font-weight: bold;
+ font-style: italic;
+ color: $darkgray;
+ }
+ }
+
+ .list-focus {
+ border: 1px solid rgba(0, 82, 147, 0.4);
+ -moz-box-shadow: 0 0 8px rgba(0, 82, 147, 0.4);
+ -webkit-box-shadow: 0 0 8px rgba(0, 82, 147, 0.4);
+ box-shadow: 0 0 8px rgba(0, 100, 180, 0.4);
+ transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s;
+ }
+
+ .bh-sl-close-directions-container {
+ width: 100%;
+ height: 20px;
+ position: relative;
+
+ .bh-sl-close-icon {
+ top: 0;
+ right: 6px;
+ }
+ }
+
+ .bh-sl-noresults-title {
+ font-weight: bold;
+ }
+ }
+
+ .loc-name {
+ /* Picked up by both list and infowindows */
+ font-size: 15px;
+ font-weight: bold;
+ }
+
+ .bh-sl-map {
+ height: 530px;
+ }
+
+ .bh-sl-pagination-container {
+ clear: both;
+
+ ol {
+ list-style-type: none;
+ text-align: center;
+ margin: 0;
+ padding: 10px 0;
+
+ li {
+ display: inline-block;
+ padding: 10px;
+ cursor: pointer;
+ font: bold 14px $arialFont;
+ color: $blue;
+ }
+
+ .bh-sl-current {
+ color: $textgray;
+ cursor: auto;
+ text-decoration: none;
+ }
+ }
+ }
+}
+
+/* Modal window */
+.bh-sl-overlay {
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 10000;
+ background: url(../img/overlay-bg.png) repeat;
+
+ .bh-sl-modal-window {
+ position: absolute;
+ left: 50%;
+ margin-left: -460px; /* width divided by 2 */
+ margin-top: 60px;
+ width: 920px;
+ height: 620px;
+ z-index: 10010;
+ background: $white;
+ border-radius: 10px;
+ box-shadow: 0 0 10px #656565;
+
+ .bh-sl-map-container {
+ margin-top: 50px; /* increase map container margin */
+ }
+
+ .bh-sl-modal-content {
+ float: left;
+ padding: 0 22px; /* there's already a margin on the top of the map-container div */
+ }
+
+ .bh-sl-close-icon {
+ top: 13px;
+ right: 22px;
+ }
+ }
+}
+
+.bh-sl-close-icon {
+ position: absolute;
+ cursor: pointer;
+ height: 24px;
+ width: 24px;
+
+ &:after,
+ &:before {
+ position: absolute;
+ top: 3px;
+ right: 3px;
+ bottom: 0;
+ left: 50%;
+ background: $gray;
+ content: '';
+ display: block;
+ height: 24px;
+ margin: -3px 0 0 -1px;
+ width: 3px;
+ -webkit-transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ -o-transform: rotate(45deg);
+ transform: rotate(45deg);
+ }
+
+ &:hover:after,
+ &:hover:before {
+ background: darken($gray, 10%);
+ }
+
+ &:before {
+ -webkit-transform: rotate(-45deg);
+ -moz-transform: rotate(-45deg);
+ -ms-transform: rotate(-45deg);
+ -o-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ }
+}
diff --git a/src/css/storelocator.scss b/src/css/storelocator.scss
new file mode 100644
index 0000000..479f444
--- /dev/null
+++ b/src/css/storelocator.scss
@@ -0,0 +1,463 @@
+$white: #fff;
+$gray: #ccc;
+$darkgray: #6c6c6c;
+$titlegray: #797874;
+$shadow: #656565;
+$textgray: #555;
+$darkergray: #2c2c2c;
+$black: #000;
+$blue: #005293;
+$red: #ae2118;
+$arialFont: Arial, Helvetica, sans-serif;
+$tablet: '(min-width: 768px)';
+$tabletLarge: '(min-width: 1024px)';
+
+/* #page-header it just included for the examples */
+#page-header {
+ display: block;
+ float: left;
+ max-width: 800px;
+
+ .bh-sl-title {
+ color: $titlegray;
+ font: normal 20px/1.4 $arialFont;
+
+ @media #{$tabletLarge} {
+ font-size: 30px;
+ }
+ }
+}
+
+/* Infowindow Roboto font override */
+.gm-style {
+ div, span, label, a {
+ font-family: $arialFont;
+ }
+}
+
+.GMAMP-maps-pin-view {
+ color: $black;
+ font: 400 15px/1 $arialFont;
+}
+
+/* InfoBubble font size */
+.bh-sl-window {
+ font-size: 13px;
+}
+
+.bh-sl-error {
+ clear: both;
+ color: $red;
+ float: left;
+ font-weight: bold;
+ padding: 10px 0;
+ width: 100%;
+}
+
+/* Avoid image issues with Google Maps and CSS resets */
+.bh-sl-map-container img {
+ border-radius: 0 !important;
+ box-shadow: none !important;
+ max-height: none !important;
+ max-width: none !important;
+}
+
+.bh-sl-container {
+ box-sizing: border-box;
+ color: $textgray;
+ float: left;
+ font: normal 14px/1.4 $arialFont;
+ padding: 0 15px;
+ width: 100%;
+
+ /* Avoid issues with Google Maps and CSS frameworks */
+ > * {
+ box-sizing: content-box !important;
+ }
+
+ .bh-sl-form-container {
+ clear: left;
+ float: left;
+ margin-top: 15px;
+ width: 100%;
+ }
+
+ .form-input {
+ float: left;
+ margin-top: 3px;
+ width: 100%;
+
+ // Tablet up
+ @media #{$tablet} {
+ width: auto;
+ }
+
+ label {
+ display: block;
+ font-weight: bold;
+ width: 100%;
+
+ // Tablet up
+ @media #{$tablet} {
+ display: inline-block;
+ width: auto;
+ }
+ }
+
+ input, select {
+ box-sizing: border-box;
+ border: 1px solid $gray;
+ border-radius: 4px;
+ font: normal 14px/1.4 $arialFont;
+ margin: 15px 0;
+ padding: 6px 12px;
+ width: 100%;
+ -webkit-border-radius: 4px;
+
+ // Tablet up
+ @media #{$tablet} {
+ width: auto;
+ margin: 0 15px 0 10px;
+ }
+ }
+ }
+
+ .loc-alt-dist {
+ display: none;
+ }
+
+ button {
+ background: darken( $blue, 5% );
+ border: none;
+ border-radius: 4px;
+ color: $white;
+ cursor: pointer;
+ float: left;
+ font: bold 14px/1.4 $arialFont;
+ margin-top: 3px;
+ padding: 6px 12px;
+ white-space: nowrap;
+ -webkit-border-radius: 4px;
+ }
+
+ .bh-sl-loading {
+ background: url(../img/ajax-loader.gif) no-repeat;
+ float: left;
+ margin: 4px 0 0 10px;
+ height: 16px;
+ width: 16px;
+ }
+
+ .bh-sl-filters-container {
+ clear: both;
+ float: left;
+ margin: 15px 0;
+ width: 100%;
+
+ .bh-sl-filters {
+ float: left;
+ list-style: none;
+ margin: 0 100px 0 0;
+ padding: 0;
+
+ li {
+ clear: left;
+ display: block;
+ float: left;
+ margin: 5px 0;
+ width: 100%;
+
+ label {
+ display: inline;
+ vertical-align: text-bottom;
+ }
+
+ input {
+ display: block;
+ float: left;
+ margin-right: 8px;
+ }
+
+ select {
+ box-sizing: border-box;
+ border: 1px solid $gray;
+ border-radius: 4px;
+ font: normal 14px/1.4 $arialFont;
+ padding: 6px 12px;
+ -webkit-border-radius: 4px;
+ }
+ }
+ }
+ }
+
+ .bh-sl-map-container {
+ clear: left;
+ float: left;
+ margin-top: 27px;
+ width: 100%;
+
+ // Tablet large up
+ @media #{$tabletLarge} {
+ margin-bottom: 60px;
+ }
+
+ a {
+ color: $blue;
+ text-decoration: none;
+
+ &:active,
+ &:focus,
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ .bh-sl-loc-list {
+ font-size: 13px;
+ height: 530px;
+ overflow-x: auto;
+ width: 100%;
+
+ // Tablet large up
+ @media #{$tabletLarge} {
+ width: 30%;
+ }
+
+ ul {
+ display: block;
+ clear: left;
+ float: left;
+ width: 100%;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ li {
+ border: 1px solid $white; /* Adding this to prevent moving li elements when adding the list-focus class*/
+ box-sizing: border-box;
+ clear: left;
+ cursor: pointer;
+ display: block;
+ float: left;
+ width: 100%;
+ }
+ }
+
+ .list-label {
+ background: darken( $blue, 20% );
+ border-radius: 15px;
+ color: $white;
+ display: block;
+ float: left;
+ font-weight: bold;
+ margin: 10px 0 0 15px;
+ padding: 4px 7px;
+ text-align: center;
+ width: auto;
+ min-width: 13px;
+ }
+
+ .list-details {
+ float: left;
+ margin-left: 6px;
+ width: 80%;
+
+ .list-content {
+ padding: 10px;
+ }
+
+ .loc-dist {
+ color: $darkgray;
+ font-weight: bold;
+ font-style: italic;
+ }
+ }
+
+ .list-focus {
+ border: 1px solid rgba(0, 82, 147, 0.4);
+ transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s;
+ }
+
+ .bh-sl-close-directions-container {
+ height: 20px;
+ position: relative;
+ width: 100%;
+
+ .bh-sl-close-icon {
+ right: 6px;
+ top: 0;
+ }
+ }
+
+ .bh-sl-directions-panel {
+ margin: 0 2%;
+
+ /* Avoid issues with table-layout */
+ table {
+ table-layout: auto;
+ width: 100%;
+ }
+
+ table, td {
+ vertical-align: middle;
+ border-collapse: separate;
+ }
+
+ td {
+ padding: 1px;
+ }
+
+ .adp-placemark {
+ margin: 10px 0;
+ border: 1px solid #c0c0c0;
+ }
+
+ .adp-marker {
+ padding: 3px;
+ }
+ }
+
+ .bh-sl-noresults-title {
+ font-weight: bold;
+ margin: 15px;
+ }
+
+ .bh-sl-noresults-desc {
+ margin: 0 15px;
+ }
+ }
+
+ .loc-name {
+ /* Picked up by both list and infowindows */
+ font-size: 15px;
+ font-weight: bold;
+ }
+
+ .bh-sl-map {
+ float: left;
+ height: 530px;
+ width: 100%;
+
+ // Tablet large up
+ @media #{$tabletLarge} {
+ width: 70%;
+ }
+ }
+
+ .bh-sl-pagination-container {
+ clear: both;
+
+ ol {
+ list-style-type: none;
+ margin: 0;
+ padding: 10px 0;
+ text-align: center;
+
+ li {
+ display: inline-block;
+ margin: 0 4px;
+ }
+
+ a {
+ box-shadow: none;
+ color: $blue;
+ display: inline-block;
+ font: bold 14px $arialFont;
+ padding: 10px;
+ text-decoration: underline;
+ }
+
+ .bh-sl-current {
+ color: $textgray;
+ cursor: auto;
+ text-decoration: none;
+
+ a {
+ color: $textgray;
+ pointer-events: none;
+ text-decoration: none;
+ }
+ }
+ }
+ }
+}
+
+/* Modal window */
+.bh-sl-overlay {
+ background: url(../img/overlay-bg.png) repeat;
+ height: 100%;
+ left: 0;
+ position: fixed;
+ top: 0;
+ width: 100%;
+ z-index: 10000;
+
+ .bh-sl-modal-window {
+ background: $white;
+ border-radius: 10px;
+ box-shadow: 0 0 10px #656565;
+ position: absolute;
+ left: 50%;
+ margin-left: -460px; /* width divided by 2 */
+ margin-top: 60px;
+ height: 620px;
+ width: 920px;
+ z-index: 10010;
+
+ .bh-sl-map-container {
+ margin-top: 50px; /* increase map container margin */
+ }
+
+ .bh-sl-modal-content {
+ float: left;
+ padding: 0 1%; /* there's already a margin on the top of the map-container div */
+ width: 98%;
+ }
+
+ .bh-sl-close-icon {
+ right: 22px;
+ top: 13px;
+ }
+ }
+}
+
+.bh-sl-close-icon {
+ cursor: pointer;
+ height: 24px;
+ position: absolute;
+ width: 24px;
+
+ &:after,
+ &:before {
+ background: $gray;
+ content: '';
+ display: block;
+ height: 24px;
+ margin: -3px 0 0 -1px;
+ position: absolute;
+ bottom: 0;
+ left: 50%;
+ right: 3px;
+ top: 3px;
+ width: 3px;
+ -webkit-transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ -o-transform: rotate(45deg);
+ transform: rotate(45deg);
+ }
+
+ &:hover:after,
+ &:hover:before {
+ background: darken($gray, 10%);
+ }
+
+ &:before {
+ -webkit-transform: rotate(-45deg);
+ -moz-transform: rotate(-45deg);
+ -ms-transform: rotate(-45deg);
+ -o-transform: rotate(-45deg);
+ transform: rotate(-45deg);
+ }
+}
diff --git a/src/js/.jshintrc b/src/js/.jshintrc
new file mode 100644
index 0000000..7f876e4
--- /dev/null
+++ b/src/js/.jshintrc
@@ -0,0 +1,15 @@
+{
+ "curly": true,
+ "eqeqeq": true,
+ "immed": true,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "sub": true,
+ "undef": true,
+ "unused": true,
+ "boss": true,
+ "eqnull": true,
+ "browser": true,
+ "predef": ["jQuery"]
+}
diff --git a/js/geocode.js b/src/js/geocode.js
similarity index 76%
rename from js/geocode.js
rename to src/js/geocode.js
index 98d7707..21a0cb6 100644
--- a/js/geocode.js
+++ b/src/js/geocode.js
@@ -1,9 +1,9 @@
//Geocode function for the origin location
function GoogleGeocode() {
- geocoder = new google.maps.Geocoder();
+ var geocoder = new google.maps.Geocoder();
this.geocode = function(address, callbackFunction) {
geocoder.geocode( { 'address': address}, function(results, status) {
- if (status == google.maps.GeocoderStatus.OK) {
+ if (status === google.maps.GeocoderStatus.OK) {
var result = {};
result.latitude = results[0].geometry.location.lat();
result.longitude = results[0].geometry.location.lng();
@@ -18,13 +18,13 @@ function GoogleGeocode() {
//Process form input
$(function() {
- $('#user-location').on('submit', function(e){
+ $('#bh-sl-user-location').on('submit', function(e){
//Stop the form submission
e.preventDefault();
//Get the user input and use it
- var userinput = $('form #address').val();
+ var userinput = $('form #bh-sl-address').val();
- if (userinput == "")
+ if (userinput === "")
{
alert("The input box was blank.");
}
@@ -33,9 +33,9 @@ $(function() {
var address = userinput;
g.geocode(address, function(data) {
- if(data != null) {
- olat = data.latitude;
- olng = data.longitude;
+ if(data !== null) {
+ var olat = data.latitude;
+ var olng = data.longitude;
$('#geocode-result').append("Latitude: " + olat + " " + "Longitude: " + olng + " ");
diff --git a/src/js/jquery.storelocator.js b/src/js/jquery.storelocator.js
new file mode 100644
index 0000000..efdf78b
--- /dev/null
+++ b/src/js/jquery.storelocator.js
@@ -0,0 +1,4047 @@
+;(function ($, window, document, undefined) {
+ 'use strict';
+
+ var pluginName = 'storeLocator';
+ var googleMapsScriptIsInjected = false;
+ var googleMapsAPIPromise = null;
+
+ // Only allow for one instantiation of this script
+ if (typeof $.fn[pluginName] !== 'undefined') {
+ return;
+ }
+
+ // Variables used across multiple methods
+ var $this, map, listTemplate, infowindowTemplate, dataTypeRead, originalOrigin, originalData, originalZoom, dataRequest, searchInput, addressInput, olat, olng, storeNum, directionsDisplay, directionsService, prevSelectedMarkerBefore, prevSelectedMarkerAfter, firstRun, reload, nameAttrs, originalFilterVals, paginationPage, locationsTotal;
+ var featuredset = [], locationset = [], normalset = [], markers = [];
+ var filters = {}, locationData = {}, GeoCodeCalc = {}, mappingObj = {}, disabledFilterVals = {};
+
+ // Create the defaults once. DO NOT change these settings in this file - settings should be overridden in the plugin call
+ var defaults = {
+ 'ajaxData' : null,
+ 'altDistanceNoResult' : false,
+ 'apiKey' : null,
+ 'autoComplete' : false,
+ 'autoCompleteDisableListener': false,
+ 'autoCompleteOptions' : {},
+ 'autoGeocode' : false,
+ 'bounceMarker' : true, // Deprecated.
+ 'catMarkers' : null,
+ 'dataLocation' : 'data/locations.json',
+ 'dataRaw' : null,
+ 'dataType' : 'json',
+ 'debug' : false,
+ 'defaultLat' : null,
+ 'defaultLng' : null,
+ 'defaultLoc' : false,
+ 'disableAlphaMarkers' : false,
+ 'distanceAlert' : 60,
+ 'dragSearch' : false,
+ 'exclusiveFiltering' : false,
+ 'exclusiveTax' : null,
+ 'featuredDistance' : null,
+ 'featuredLocations' : false,
+ 'fullMapStart' : false,
+ 'fullMapStartBlank' : false,
+ 'fullMapStartListLimit' : false,
+ 'infoBubble' : null,
+ 'inlineDirections' : false,
+ 'lazyLoadMap' : false,
+ 'lengthUnit' : 'm',
+ 'listColor1' : '#ffffff',
+ 'listColor2' : '#eeeeee',
+ 'loading' : false,
+ 'locationsPerPage' : 10,
+ 'mapSettings' : {
+ mapTypeId: 'roadmap',
+ zoom : 12,
+ },
+ 'mapSettingsID' : '',
+ 'markerCluster' : null,
+ 'markerImg' : null,
+ 'markerDim' : null,
+ 'maxDistance' : false,
+ 'modal' : false,
+ 'nameAttribute' : 'name',
+ 'nameSearch' : false,
+ 'noForm' : false,
+ 'openNearest' : false,
+ 'originMarker' : false,
+ 'originMarkerDim' : null,
+ 'originMarkerImg' : null,
+ 'pagination' : false,
+ 'querystringParams' : false,
+ 'selectedMarkerImg' : null,
+ 'selectedMarkerImgDim' : null,
+ 'sessionStorage' : false,
+ 'slideMap' : true,
+ 'sortBy' : null,
+ 'storeLimit' : 26,
+ 'taxonomyFilters' : null,
+ 'visibleMarkersList' : false,
+ 'xmlElement' : 'marker',
+ // HTML elements
+ 'addressID' : 'bh-sl-address',
+ 'closeIcon' : 'bh-sl-close-icon',
+ 'formContainer' : 'bh-sl-form-container',
+ 'formID' : 'bh-sl-user-location',
+ 'geocodeID' : null,
+ 'lengthSwapID' : 'bh-sl-length-swap',
+ 'loadingContainer' : 'bh-sl-loading',
+ 'locationList' : 'bh-sl-loc-list',
+ 'mapID' : 'bh-sl-map',
+ 'maxDistanceID' : 'bh-sl-maxdistance',
+ 'modalContent' : 'bh-sl-modal-content',
+ 'modalWindow' : 'bh-sl-modal-window',
+ 'orderID' : 'bh-sl-order',
+ 'overlay' : 'bh-sl-overlay',
+ 'regionID' : 'bh-sl-region',
+ 'searchID' : 'bh-sl-search',
+ 'sortID' : 'bh-sl-sort',
+ 'taxonomyFiltersContainer': 'bh-sl-filters-container',
+ // Templates
+ 'infowindowTemplatePath' : 'assets/js/plugins/storeLocator/templates/infowindow-description.html',
+ 'listTemplatePath' : 'assets/js/plugins/storeLocator/templates/location-list-description.html',
+ 'KMLinfowindowTemplatePath': 'assets/js/plugins/storeLocator/templates/kml-infowindow-description.html',
+ 'KMLlistTemplatePath' : 'assets/js/plugins/storeLocator/templates/kml-location-list-description.html',
+ 'listTemplateID' : null,
+ 'infowindowTemplateID' : null,
+ // Callbacks
+ 'callbackAutoGeoSuccess' : null,
+ 'callbackBeforeMapInject' : null,
+ 'callbackBeforeSend' : null,
+ 'callbackCloseDirections' : null,
+ 'callbackCreateMarker' : null,
+ 'callbackDirectionsRequest' : null,
+ 'callbackFilters' : null,
+ 'callbackFormVals' : null,
+ 'callbackGeocodeRestrictions': null,
+ 'callbackJsonp' : null,
+ 'callbackListClick' : null,
+ 'callbackMapSet' : null,
+ 'callbackMarkerClick' : null,
+ 'callbackModalClose' : null,
+ 'callbackModalOpen' : null,
+ 'callbackModalReady' : null,
+ 'callbackNearestLoc' : null,
+ 'callbackNoResults' : null,
+ 'callbackNotify' : null,
+ 'callbackOrder' : null,
+ 'callbackPageChange' : null,
+ 'callbackRegion' : null,
+ 'callbackSorting' : null,
+ 'callbackSuccess' : null,
+ // Language options
+ 'addressErrorAlert' : 'Unable to find address',
+ 'autoGeocodeErrorAlert': 'Automatic location detection failed. Please fill in your address or zip code.',
+ 'distanceErrorAlert' : 'Unfortunately, our closest location is more than ',
+ 'kilometerLang' : 'kilometer',
+ 'kilometersLang' : 'kilometers',
+ 'mileLang' : 'mile',
+ 'milesLang' : 'miles',
+ 'noResultsTitle' : 'No results',
+ 'noResultsDesc' : 'No locations were found with the given criteria. Please modify your selections or input.',
+ 'nextPage' : 'Next »',
+ 'prevPage' : '« Prev'
+ };
+
+ // Plugin constructor
+ function Plugin(element, options) {
+ $this = $(element);
+ this.element = element;
+ this.settings = $.extend({}, defaults, options);
+ this._defaults = defaults;
+ this._name = pluginName;
+
+ // Add Map ID to map settings if set.
+ if (this.settings.mapSettingsID !== '') {
+ this.settings.mapSettings.mapId = this.settings.mapSettingsID;
+ }
+
+ // Load Google Maps API when lazy load is enabled.
+ if (this.settings.lazyLoadMap && this.settings.apiKey !== null && typeof google === 'undefined') {
+ var _this = this;
+ var optionsQuery = {};
+ var loadMap = false;
+
+ // Load new marker library.
+ optionsQuery.libraries = 'marker';
+
+ // Autocomplete.
+ if (this.settings.autoComplete === true) {
+ optionsQuery.libraries = 'places,marker';
+ }
+
+ // Allow callback to resolve map loading when set.
+ if (this.settings.callbackBeforeMapInject) {
+ new Promise(function (resolve, reject) {
+ _this.settings.callbackBeforeMapInject.call(this, options, resolve);
+ }).then(function () {
+ _this.triggerMapLoad(optionsQuery)
+ });
+ } else {
+ _this.triggerMapLoad(optionsQuery)
+ }
+ } else {
+ this.init();
+ }
+ }
+
+ // Avoid Plugin.prototype conflicts
+ $.extend(Plugin.prototype, {
+
+ /**
+ * Init function
+ */
+ init: function () {
+ var _this = this;
+ this.writeDebug('init');
+
+ // Calculate geocode distance functions
+ if (this.settings.lengthUnit === 'km') {
+ // Kilometers
+ GeoCodeCalc.EarthRadius = 6367.0;
+ }
+ else {
+ // Default is miles
+ GeoCodeCalc.EarthRadius = 3956.0;
+ }
+
+ // KML is read as XML
+ if (this.settings.dataType === 'kml') {
+ dataTypeRead = 'xml';
+ }
+ else {
+ dataTypeRead = this.settings.dataType;
+ }
+
+ // Add directions panel if enabled
+ if (this.settings.inlineDirections === true) {
+ $('.' + this.settings.locationList).prepend('
');
+ }
+
+ // Save the original zoom setting so it can be retrieved if taxonomy filtering resets it
+ originalZoom = this.settings.mapSettings.zoom;
+
+ // Add Handlebars helper for handling URL output
+ Handlebars.registerHelper('niceURL', function(url) {
+ if (url) {
+ return url.replace('https://', '').replace('http://', '');
+ }
+ });
+
+ // Handle distance changes on select
+ if (this.settings.maxDistance === true) {
+ this.distanceFiltering();
+ }
+
+ // Do taxonomy filtering if set
+ if (this.settings.taxonomyFilters !== null) {
+ this.taxonomyFiltering();
+ }
+
+ // Do sorting and ordering if set.
+ this.sorting();
+ this.order();
+
+ // Add modal window divs if set
+ if (this.settings.modal === true) {
+ // Clone the filters if there are any, so they can be used in the modal
+ if (this.settings.taxonomyFilters !== null) {
+ // Clone the filters
+ $('.' + this.settings.taxonomyFiltersContainer).clone(true, true).prependTo($this);
+ }
+
+ $this.wrap('');
+ $('.' + this.settings.modalWindow).prepend('
');
+ $('.' + this.settings.overlay).hide();
+ }
+
+ // Set up Google Places autocomplete if it's set to true
+ if (this.settings.autoComplete === true) {
+ var searchInput = document.getElementById(this.settings.addressID);
+ var autoPlaces = new google.maps.places.Autocomplete(searchInput, this.settings.autoCompleteOptions);
+
+ // Add listener when autoComplete selection changes.
+ if (this.settings.autoComplete === true && this.settings.autoCompleteDisableListener !== true) {
+ autoPlaces.addListener('place_changed', function(e) {
+ _this.processForm(e);
+ });
+ }
+ }
+
+ // Load the templates and continue from there
+ this._loadTemplates();
+ },
+
+ /**
+ * Trigger async map loading
+ */
+ triggerMapLoad: function(optionsQuery) {
+ this.writeDebug('triggerMapLoad');
+ var _this = this;
+
+ this.loadMapsAPI(this.settings.apiKey, optionsQuery)
+ .then(function (map) {
+ _this.map = map;
+ _this.init();
+ });
+ },
+
+ /**
+ * Inject Google Maps script
+ *
+ * @param {Object} options Options query object to pass as query string parameters to Google Maps.
+ */
+ injectGoogleMapsScript: function (options) {
+ this.writeDebug('injectGoogleMapsScript');
+ options = (typeof options !== 'undefined') ? options : {};
+
+ if (googleMapsScriptIsInjected) {
+ throw new Error('Google Maps API is already loaded.');
+ }
+
+ var optionsQuery = Object.keys(options)
+ .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(options[k]))
+ .join('&');
+ var apiURL = 'https://maps.googleapis.com/maps/api/js?' + optionsQuery;
+ var mapScript = document.createElement('script');
+ mapScript.setAttribute('src', apiURL);
+ mapScript.setAttribute('async', '');
+ mapScript.setAttribute('defer', '');
+
+ // Append the script to the document head.
+ document.head.appendChild(mapScript);
+ googleMapsScriptIsInjected = true;
+ },
+
+ /**
+ * Load Google Maps API
+ *
+ * @param {string} apiKey Google Maps JavaScript API key.
+ * @param {Object} options Options query object to pass as query string parameters to Google Maps.
+ *
+ * @returns {Promise
}
+ */
+ loadMapsAPI: function (apiKey, options) {
+ this.writeDebug('loadMapsAPI');
+ options = (typeof options !== 'undefined') ? options : {};
+ var _this = this;
+
+ if (!googleMapsAPIPromise) {
+ googleMapsAPIPromise = new Promise(function (resolve, reject) {
+ try {
+ window.onGoogleMapsAPILoaded = resolve;
+
+ _this.injectGoogleMapsScript({
+ key: apiKey,
+ loading: 'async',
+ callback: 'onGoogleMapsAPILoaded',
+ ...options,
+ });
+ } catch (error) {
+ reject(error);
+ }
+ }).then(function () { window.google.maps });
+ }
+
+ return googleMapsAPIPromise;
+ },
+
+ /**
+ * Destroy
+ * Note: The Google map is not destroyed here because Google recommends using a single instance and reusing it
+ * (it's not really supported)
+ */
+ destroy: function () {
+ this.writeDebug('destroy');
+ // Reset
+ this.reset();
+ var $mapDiv = $('#' + this.settings.mapID);
+
+ // Remove marker event listeners
+ if (markers.length) {
+ for(var i = 0; i <= markers.length; i++) {
+ google.maps.event.removeListener(markers[i]);
+ }
+ }
+
+ // Remove markup
+ $('.' + this.settings.locationList + ' ul').empty();
+ if ($mapDiv.hasClass('bh-sl-map-open')) {
+ $mapDiv.empty().removeClass('bh-sl-map-open');
+ }
+
+ // Remove modal markup
+ if (this.settings.modal === true) {
+ $('. ' + this.settings.overlay).remove();
+ }
+
+ // Remove map style from container
+ $mapDiv.attr('style', '');
+
+ // Hide map container
+ $this.hide();
+ // Remove data
+ $.removeData($this.get(0));
+ // Remove namespaced events
+ $(document).off(pluginName);
+ // Unbind plugin
+ $this.unbind();
+ },
+
+ /**
+ * Reset function
+ * This method clears out all the variables and removes events. It does not reload the map.
+ */
+ reset: function () {
+ this.writeDebug('reset');
+ locationset = [];
+ featuredset = [];
+ normalset = [];
+ markers = [];
+ firstRun = false;
+ $(document).off('click.'+pluginName, '.' + this.settings.locationList + ' li');
+
+ if ( $('.' + this.settings.locationList + ' .bh-sl-close-directions-container').length ) {
+ $('.bh-sl-close-directions-container').remove();
+ }
+
+ if (this.settings.inlineDirections === true) {
+ // Remove directions panel if it's there
+ var $adp = $('.' + this.settings.locationList + ' .adp');
+ if ( $adp.length > 0 ) {
+ $adp.remove();
+ $('.' + this.settings.locationList + ' ul').fadeIn();
+ }
+ $(document).off('click', '.' + this.settings.locationList + ' li .loc-directions a');
+ }
+
+ if (this.settings.pagination === true) {
+ $(document).off('click.'+pluginName, '.bh-sl-pagination li a');
+ }
+ },
+
+ /**
+ * Reset the form filters
+ */
+ formFiltersReset: function () {
+ this.writeDebug('formFiltersReset');
+ if (this.settings.taxonomyFilters === null) {
+ return;
+ }
+
+ var $inputs = $('.' + this.settings.taxonomyFiltersContainer + ' input'),
+ $selects = $('.' + this.settings.taxonomyFiltersContainer + ' select');
+
+ if ( typeof($inputs) !== 'object') {
+ return;
+ }
+
+ // Loop over the input fields
+ $inputs.each(function() {
+ if ($(this).is('input[type="checkbox"]') || $(this).is('input[type="radio"]')) {
+ $(this).prop('checked',false);
+ }
+ });
+
+ // Loop over select fields
+ $selects.each(function() {
+ $(this).prop('selectedIndex',0);
+ });
+ },
+
+ /**
+ * Reload everything
+ * This method does a reset of everything and reloads the map as it would first appear.
+ */
+ mapReload: function() {
+ this.writeDebug('mapReload');
+ this.reset();
+ reload = true;
+
+ if (this.settings.taxonomyFilters !== null) {
+ this.formFiltersReset();
+ this.resetDisabledFilterVals();
+ this.taxonomyFiltersInit();
+ }
+
+ if ((olat) && (olng)) {
+ this.settings.mapSettings.zoom = originalZoom;
+ this.processForm();
+ }
+ else {
+ this.mapping(mappingObj);
+ }
+ },
+
+ /**
+ * Notifications
+ * Some errors use alert by default. This is overridable with the callbackNotify option
+ *
+ * @param notifyText {string} the notification message
+ */
+ notify: function (notifyText) {
+ this.writeDebug('notify',notifyText);
+ if (this.settings.callbackNotify) {
+ this.settings.callbackNotify.call(this, notifyText);
+ }
+ else {
+ alert(notifyText);
+ }
+ },
+
+ /**
+ * Distance calculations
+ */
+ geoCodeCalcToRadian: function (v) {
+ this.writeDebug('geoCodeCalcToRadian',v);
+ return v * (Math.PI / 180);
+ },
+ geoCodeCalcDiffRadian: function (v1, v2) {
+ this.writeDebug('geoCodeCalcDiffRadian',arguments);
+ return this.geoCodeCalcToRadian(v2) - this.geoCodeCalcToRadian(v1);
+ },
+ geoCodeCalcCalcDistance: function (lat1, lng1, lat2, lng2, radius) {
+ this.writeDebug('geoCodeCalcCalcDistance',arguments);
+ 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) ))));
+ },
+
+ /**
+ * Range helper function for coordinate validation
+ *
+ * @param min {number} minimum number allowed
+ * @param num {number} number to check
+ * @param max {number} maximum number allowed
+ *
+ * @returns {boolean}
+ */
+ inRange: function(min, num, max){
+ this.writeDebug('inRange',arguments);
+ num = Math.abs(num);
+ return isFinite(num) && (num >= min) && (num <= max);
+ },
+
+ /**
+ * Coordinate validation
+ *
+ * @param lat {number} latitude
+ * @param lng {number} longitude
+ *
+ * @returns {boolean}
+ */
+ coordinatesInRange: function (lat, lng) {
+ this.writeDebug('coordinatesInRange',arguments);
+ return this.inRange(-90, lat, 90) && this.inRange(-180, lng, 180);
+ },
+
+ /**
+ * Check for query string
+ *
+ * @param param {string} query string parameter to test
+ *
+ * @returns {string} query string value
+ */
+ getQueryString: function(param) {
+ this.writeDebug('getQueryString',param);
+ if(param) {
+ param = param.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
+ var regex = new RegExp('[\\?&]' + param + '=([^]*)'),
+ results = regex.exec(location.search);
+ return (results === null) ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
+ }
+ },
+
+ /**
+ * Get google.maps.Map instance
+ *
+ * @returns {Object} google.maps.Map instance
+ */
+ getMap: function() {
+ return this.map;
+ },
+
+ /**
+ * Load templates via Handlebars templates in /templates or inline via IDs - private
+ */
+ _loadTemplates: function () {
+ this.writeDebug('_loadTemplates');
+ var source;
+ var _this = this;
+ var templateError = '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.
';
+ // Get the KML templates
+ if (this.settings.dataType === 'kml' && this.settings.listTemplateID === null && this.settings.infowindowTemplateID === null) {
+
+ // Try loading the external template files
+ $.when(
+ // KML infowindows
+ $.get(this.settings.KMLinfowindowTemplatePath, function (template) {
+ source = template;
+ infowindowTemplate = Handlebars.compile(source);
+ }),
+
+ // KML locations list
+ $.get(this.settings.KMLlistTemplatePath, function (template) {
+ source = template;
+ listTemplate = Handlebars.compile(source);
+ })
+ ).then(function () {
+ // Continue to the main script if templates are loaded successfully
+ _this.locator();
+
+ }, function () {
+ // KML templates not loaded
+ $('.' + _this.settings.formContainer).append(templateError);
+ throw new Error('Could not load storeLocator plugin templates');
+ });
+ }
+ // Handle script tag template method
+ else if (this.settings.listTemplateID !== null && this.settings.infowindowTemplateID !== null) {
+ // Infowindows
+ infowindowTemplate = Handlebars.compile($('#' + this.settings.infowindowTemplateID).html());
+
+ // Locations list
+ listTemplate = Handlebars.compile($('#' + this.settings.listTemplateID).html());
+
+ // Continue to the main script
+ _this.locator();
+ }
+ // Get the JSON/XML templates
+ else {
+ // Try loading the external template files
+ $.when(
+ // Infowindows
+ $.get(this.settings.infowindowTemplatePath, function (template) {
+ source = template;
+ infowindowTemplate = Handlebars.compile(source);
+ }),
+
+ // Locations list
+ $.get(this.settings.listTemplatePath, function (template) {
+ source = template;
+ listTemplate = Handlebars.compile(source);
+ })
+ ).then(function () {
+ // Continue to the main script if templates are loaded successfully
+ _this.locator();
+
+ }, function () {
+ // JSON/XML templates not loaded
+ $('.' + _this.settings.formContainer).append(templateError);
+ throw new Error('Could not load storeLocator plugin templates');
+ });
+ }
+ },
+
+ /**
+ * Primary locator function runs after the templates are loaded
+ */
+ locator: function () {
+ this.writeDebug('locator');
+ if (this.settings.slideMap === true) {
+ // Let's hide the map container to begin
+ $this.hide();
+ }
+
+ this._start();
+ this._formEventHandler();
+ },
+
+ /**
+ * Form event handler setup - private
+ */
+ _formEventHandler: function () {
+ this.writeDebug('_formEventHandler');
+ var _this = this;
+ // ASP.net or regular submission?
+ if (this.settings.noForm === true) {
+ $(document).on('click.'+pluginName, '.' + this.settings.formContainer + ' button', function (e) {
+ _this.processForm(e);
+ });
+ $(document).on('keydown.'+pluginName, function (e) {
+ if (e.keyCode === 13 && $('#' + _this.settings.addressID).is(':focus')) {
+ _this.processForm(e);
+ }
+ });
+ }
+ else {
+ $(document).on('submit.'+pluginName, '#' + this.settings.formID, function (e) {
+ _this.processForm(e);
+ });
+ }
+
+ // Reset button trigger.
+ if ($('.bh-sl-reset').length && $('#' + this.settings.mapID).length) {
+ $(document).on('click.' + pluginName, '.bh-sl-reset', function () {
+ _this.mapReload();
+ });
+ }
+
+ // Track changes to the address search field.
+ $('#' + this.settings.addressID).on('change.'+pluginName, function () {
+ originalFilterVals = undefined;
+ disabledFilterVals = {};
+
+ // Unset origin tracking if input field is removed.
+ if (
+ $.trim($('#' + _this.settings.addressID).val()) === '' &&
+ (typeof searchInput === 'undefined' || searchInput === '')
+ ) {
+
+ // Reset the origin, mapping object, and disabled filter values.
+ if (_this.settings.taxonomyFilters !== null && _this.settings.exclusiveFiltering === false) {
+ olat = undefined;
+ olng = undefined;
+ originalOrigin = undefined;
+ mappingObj = {};
+ _this.resetDisabledFilterVals();
+ _this.taxonomyFiltersInit();
+ _this.mapping(null);
+ }
+ }
+ });
+ },
+
+ /**
+ * AJAX data request - private
+ *
+ * @param lat {number} latitude
+ * @param lng {number} longitude
+ * @param address {string} street address
+ * @param geocodeData {object} full Google geocode results object
+ * @param map (object} Google Maps object.
+ *
+ * @returns {Object} deferred object
+ */
+ _getData: function (lat, lng, address, geocodeData, map) {
+ this.writeDebug('_getData',arguments);
+ var _this = this,
+ northEast = '',
+ southWest = '',
+ formattedAddress = '';
+
+ // Define extra geocode result info
+ if (typeof geocodeData !== 'undefined' && typeof geocodeData.geometry.bounds !== 'undefined') {
+ formattedAddress = geocodeData.formatted_address;
+ northEast = JSON.stringify( geocodeData.geometry.bounds.getNorthEast() );
+ southWest = JSON.stringify( geocodeData.geometry.bounds.getSouthWest() );
+ }
+
+ // Before send callback
+ if (this.settings.callbackBeforeSend) {
+ this.settings.callbackBeforeSend.call(this, lat, lng, address, formattedAddress, northEast, southWest, map);
+ }
+
+ // Raw data
+ if(_this.settings.dataRaw !== null) {
+ // XML
+ if( dataTypeRead === 'xml' ) {
+ return $.parseXML(_this.settings.dataRaw);
+ }
+
+ // JSON
+ else if (dataTypeRead === 'json') {
+ if (Array.isArray && Array.isArray(_this.settings.dataRaw)) {
+ return _this.settings.dataRaw;
+ }
+ else if (typeof _this.settings.dataRaw === 'string') {
+ return JSON.parse(_this.settings.dataRaw);
+ }
+ else {
+ return [];
+ }
+
+ }
+ }
+ // Remote data
+ else {
+ var d = $.Deferred();
+
+ // Loading
+ if (this.settings.loading === true) {
+ $('.' + this.settings.formContainer).append('
');
+ }
+
+ // Data to pass with the AJAX request
+ var ajaxData = {
+ 'origLat' : lat,
+ 'origLng' : lng,
+ 'origAddress': address,
+ 'formattedAddress': formattedAddress,
+ 'boundsNorthEast' : northEast,
+ 'boundsSouthWest' : southWest
+ };
+
+ // Set up extra object for custom extra data to be passed with the AJAX request
+ if (this.settings.ajaxData !== null && typeof this.settings.ajaxData === 'object') {
+ $.extend(ajaxData, this.settings.ajaxData);
+ }
+
+ // AJAX request
+ $.ajax({
+ type : 'GET',
+ url : this.settings.dataLocation + (this.settings.dataType === 'jsonp' ? (this.settings.dataLocation.match(/\?/) ? '&' : '?') + 'callback=?' : ''),
+ // Passing the lat, lng, address, formatted address and bounds with the AJAX request so they can optionally be used by back-end languages
+ data : ajaxData,
+ dataType : dataTypeRead,
+ jsonpCallback: (this.settings.dataType === 'jsonp' ? this.settings.callbackJsonp : null)
+ }).done(function(p) {
+ d.resolve(p);
+
+ // Loading remove
+ if (_this.settings.loading === true) {
+ $('.' + _this.settings.formContainer + ' .' + _this.settings.loadingContainer).remove();
+ }
+ }).fail(d.reject);
+ return d.promise();
+ }
+ },
+
+ /**
+ * Checks for default location, full map, and HTML5 geolocation settings - private
+ */
+ _start: function () {
+ this.writeDebug('_start');
+ var _this = this,
+ doAutoGeo = this.settings.autoGeocode,
+ latlng;
+
+ // Full map blank start
+ if (_this.settings.fullMapStartBlank !== false) {
+ var $mapDiv = $('#' + _this.settings.mapID);
+ $mapDiv.addClass('bh-sl-map-open');
+ var myOptions = _this.settings.mapSettings;
+ myOptions.zoom = _this.settings.fullMapStartBlank;
+
+ latlng = new google.maps.LatLng(this.settings.defaultLat, this.settings.defaultLng);
+ myOptions.center = latlng;
+
+ // Create the map
+ _this.map = new google.maps.Map(document.getElementById(_this.settings.mapID), myOptions);
+
+ // Re-center the map when the browser is re-sized
+ window.addEventListener('resize', function() {
+ var center = _this.map.getCenter();
+ google.maps.event.trigger(_this.map, 'resize');
+ _this.map.setCenter(center);
+ });
+
+ // Only do this once
+ _this.settings.fullMapStartBlank = false;
+ myOptions.zoom = originalZoom;
+ } else {
+ // If a default location is set
+ if (this.settings.defaultLoc === true) {
+ this.defaultLocation();
+ }
+
+ // If there is already have a value in the address bar
+ if ($.trim($('#' + this.settings.addressID).val()) !== ''){
+ _this.writeDebug('Using Address Field');
+ _this.processForm(null);
+ doAutoGeo = false; // No need for additional processing
+ }
+ // If show full map option is true
+ else if (this.settings.fullMapStart === true && this.settings.defaultLoc === false) {
+ 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))) {
+ _this.writeDebug('Using Query String');
+ this.processForm(null);
+ doAutoGeo = false; // No need for additional processing
+ }
+ else {
+ this.mapping(null);
+ }
+ }
+ }
+
+ // HTML5 auto geolocation API option
+ if (this.settings.autoGeocode === true && doAutoGeo === true) {
+ _this.writeDebug('Auto Geo');
+
+ _this.htmlGeocode();
+ }
+
+ // HTML5 geolocation API button option
+ if (this.settings.autoGeocode !== null) {
+ _this.writeDebug('Button Geo');
+
+ $(document).on('click.'+pluginName, '#' + this.settings.geocodeID, function () {
+ _this.htmlGeocode();
+ });
+ }
+ },
+
+ /**
+ * Geocode function used for auto geocode setting and geocodeID button
+ */
+ htmlGeocode: function() {
+ this.writeDebug('htmlGeocode',arguments);
+ var _this = this;
+
+ if (_this.settings.sessionStorage === true && window.sessionStorage && window.sessionStorage.getItem('myGeo')){
+ _this.writeDebug('Using Session Saved Values for GEO');
+ _this.autoGeocodeQuery(JSON.parse(window.sessionStorage.getItem('myGeo')));
+ return false;
+ }
+ else if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(function(position){
+ _this.writeDebug('Current Position Result');
+ // To not break autoGeocodeQuery then we create the obj to match the geolocation format
+ var pos = {
+ coords: {
+ latitude : position.coords.latitude,
+ longitude: position.coords.longitude,
+ accuracy : position.coords.accuracy
+ }
+ };
+
+ // Have to do this to get around scope issues
+ if (_this.settings.sessionStorage === true && window.sessionStorage) {
+ window.sessionStorage.setItem('myGeo',JSON.stringify(pos));
+ }
+
+ // Callback
+ if (_this.settings.callbackAutoGeoSuccess) {
+ _this.settings.callbackAutoGeoSuccess.call(this, pos);
+ }
+
+ _this.autoGeocodeQuery(pos);
+ }, function(error){
+ _this._autoGeocodeError(error);
+ });
+ }
+ },
+
+ /**
+ * Geocode function used to geocode the origin (entered location)
+ */
+ googleGeocode: function (thisObj) {
+ thisObj.writeDebug('googleGeocode',arguments);
+ var geocoder = new google.maps.Geocoder();
+ this.geocode = function (request, callbackFunction) {
+ geocoder.geocode(request, function (results, status) {
+ if (status === google.maps.GeocoderStatus.OK) {
+ var result = {};
+ result.latitude = results[0].geometry.location.lat();
+ result.longitude = results[0].geometry.location.lng();
+ result.geocodeResult = results[0];
+ callbackFunction(result);
+ } else {
+ callbackFunction(null);
+ throw new Error('Geocode was not successful for the following reason: ' + status);
+ }
+ });
+ };
+ },
+
+ /**
+ * Reverse geocode to get address for automatic options needed for directions link
+ */
+ reverseGoogleGeocode: function (thisObj) {
+ thisObj.writeDebug('reverseGoogleGeocode',arguments);
+ var geocoder = new google.maps.Geocoder();
+ this.geocode = function (request, callbackFunction) {
+ geocoder.geocode(request, function (results, status) {
+ if (status === google.maps.GeocoderStatus.OK) {
+ if (results[0]) {
+ var result = {};
+ result.address = results[0].formatted_address;
+ result.fullResult = results[0];
+ callbackFunction(result);
+ }
+ } else {
+ callbackFunction(null);
+ throw new Error('Reverse geocode was not successful for the following reason: ' + status);
+ }
+ });
+ };
+ },
+
+ /**
+ * Rounding function used for distances
+ *
+ * @param num {number} the full number
+ * @param dec {number} the number of digits to show after the decimal
+ *
+ * @returns {number}
+ */
+ roundNumber: function (num, dec) {
+ this.writeDebug('roundNumber',arguments);
+ return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
+ },
+
+ /**
+ * Checks to see if the object is empty. Using this instead of $.isEmptyObject for legacy browser support
+ *
+ * @param obj {Object} the object to check
+ *
+ * @returns {boolean}
+ */
+ isEmptyObject: function (obj) {
+ this.writeDebug('isEmptyObject',arguments);
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Checks to see if all the property values in the object are empty
+ *
+ * @param obj {Object} the object to check
+ *
+ * @returns {boolean}
+ */
+ hasEmptyObjectVals: function (obj) {
+ this.writeDebug('hasEmptyObjectVals',arguments);
+ var objTest = true;
+
+ for(var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ if (obj[key] !== '' && obj[key].length !== 0) {
+ objTest = false;
+ }
+ }
+ }
+
+ return objTest;
+ },
+
+ /**
+ * Checks to see if only a single taxonomy group has a selected value
+ *
+ * @param obj {Object} the object to check
+ * @param key {string} Key value of current filter group
+ *
+ * @returns {boolean}
+ */
+ hasSingleGroupFilterVal: function(obj, key) {
+ this.writeDebug('hasSingleGroupFilterVal',arguments);
+
+ // Copy the object so the original doesn't change.
+ var objCopy = Object.assign({}, obj);
+
+ return !this.hasEmptyObjectVals(objCopy[key]);
+ },
+
+ /**
+ * Modal window close function
+ */
+ modalClose: function () {
+ this.writeDebug('modalClose');
+ // Callback
+ if (this.settings.callbackModalClose) {
+ this.settings.callbackModalClose.call(this);
+ }
+
+ // Reset the filters
+ filters = {};
+
+ // Undo category selections
+ $('.' + this.settings.overlay + ' select').prop('selectedIndex', 0);
+ $('.' + this.settings.overlay + ' input').prop('checked', false);
+
+ // Hide the modal
+ $('.' + this.settings.overlay).hide();
+ },
+
+ /**
+ * Create the location variables - private
+ *
+ * @param loopcount {number} current marker id
+ */
+ _createLocationVariables: function (loopcount) {
+ this.writeDebug('_createLocationVariables',arguments);
+ var value;
+ locationData = {};
+
+ for (var key in locationset[loopcount]) {
+ if (locationset[loopcount].hasOwnProperty(key)) {
+ value = locationset[loopcount][key];
+
+ if (key === 'distance' || key === 'altdistance') {
+ value = this.roundNumber(value, 2);
+ }
+
+ locationData[key] = value;
+ }
+ }
+ },
+
+ /**
+ * Location alphabetical sorting function
+ *
+ * @param locationsarray {array} locationset array
+ */
+ sortAlpha: function(locationsarray) {
+ this.writeDebug('sortAlpha',arguments);
+ var property = (this.settings.sortBy.hasOwnProperty('prop') && typeof this.settings.sortBy.prop !== 'undefined') ? this.settings.sortBy.prop : 'name';
+
+ if (this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
+ locationsarray.sort(function (a, b) {
+ return b[property].toLowerCase().localeCompare(a[property].toLowerCase());
+ });
+ } else {
+ locationsarray.sort(function (a, b) {
+ return a[property].toLowerCase().localeCompare(b[property].toLowerCase());
+ });
+ }
+ },
+
+ /**
+ * Location date sorting function
+ *
+ * @param locationsarray {array} locationset array
+ */
+ sortDate: function(locationsarray) {
+ this.writeDebug('sortDate',arguments);
+ var property = (this.settings.sortBy.hasOwnProperty('prop') && typeof this.settings.sortBy.prop !== 'undefined') ? this.settings.sortBy.prop : 'date';
+
+ if (this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
+ locationsarray.sort(function (a, b) {
+ return new Date(b[property]).getTime() - new Date(a[property]).getTime();
+ });
+ } else {
+ locationsarray.sort(function (a, b) {
+ return new Date(a[property]).getTime() - new Date(b[property]).getTime();
+ });
+ }
+ },
+
+ /**
+ * Location distance sorting function
+ *
+ * @param locationsarray {array} locationset array
+ * @param distanceOverride {boolean} Force sort by distance
+ */
+ sortNumerically: function (locationsarray, distanceOverride) {
+ this.writeDebug('sortNumerically',arguments);
+ var property = (
+ this.settings.sortBy !== null &&
+ this.settings.sortBy.hasOwnProperty('prop') &&
+ typeof this.settings.sortBy.prop !== 'undefined'
+ ) ? this.settings.sortBy.prop : 'distance';
+
+ if (typeof distanceOverride !== 'undefined' && distanceOverride === true) {
+ property = 'distance';
+ }
+
+ if (this.settings.sortBy !== null && this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
+ locationsarray.sort(function (a, b) {
+ return ((b[property] < a[property]) ? -1 : ((b[property] > a[property]) ? 1 : 0));
+ });
+ } else {
+ locationsarray.sort(function (a, b) {
+ return ((a[property] < b[property]) ? -1 : ((a[property] > b[property]) ? 1 : 0));
+ });
+ }
+ },
+
+ /**
+ * Alternative sorting setup
+ *
+ * @param locationsarray {array} locationset array
+ */
+ sortCustom: function (locationsarray) {
+ this.writeDebug('sortCustom',arguments);
+
+ // Alphabetically, date, or numeric
+ if (this.settings.sortBy.hasOwnProperty('method') && this.settings.sortBy.method.toString() === 'alpha') {
+ this.sortAlpha(locationsarray);
+ } else if (this.settings.sortBy.hasOwnProperty('method') && this.settings.sortBy.method.toString() === 'date') {
+ this.sortDate(locationsarray);
+ } else {
+ this.sortNumerically(locationsarray);
+ }
+ },
+
+ /**
+ * Run the matching between regular expression filters and string value
+ *
+ * @param filter {array} One or multiple filters to apply
+ * @param val {string} Value to compare
+ * @param inclusive {boolean} Inclusive (default) or exclusive
+ *
+ * @returns {boolean}
+ */
+ filterMatching: function(filter, val, inclusive) {
+ this.writeDebug('inclusiveFilter',arguments);
+ inclusive = (typeof inclusive !== 'undefined') ? inclusive : true;
+ var applyFilters;
+
+ // Undefined check.
+ if (typeof val === 'undefined') {
+ return false;
+ }
+
+ // Modify the join depending on inclusive (AND) vs exclusive (OR).
+ if ( true === inclusive ) {
+ applyFilters = filter.join('');
+ } else {
+ applyFilters = filter.join('|');
+ }
+
+ if ((new RegExp(applyFilters, 'i').test(val.replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '')))) {
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Filter the data with Regex
+ *
+ * @param data {array} data array to check for filter values
+ * @param filters {Object} taxonomy filters object
+ *
+ * @returns {boolean}
+ */
+ filterData: function (data, filters) {
+ this.writeDebug('filterData', arguments);
+ var filterTest = true;
+
+ for (var k in filters) {
+ if (filters.hasOwnProperty(k)) {
+ var testResults = [];
+
+ for (var l = 0; l < filters[k].length; l++) {
+
+ // Exclusive filtering
+ if (this.settings.exclusiveFiltering === true || (this.settings.exclusiveTax !== null && Array.isArray(this.settings.exclusiveTax) && this.settings.exclusiveTax.indexOf(k) !== -1)) {
+ testResults[l] = this.filterMatching(filters[k], data[k], false);
+ }
+ // Inclusive filtering
+ else {
+ testResults[l] = this.filterMatching(filters[k], data[k]);
+ }
+ }
+
+ // First handle name search, then standard filtering.
+ if (typeof nameAttrs !== 'undefined' && nameAttrs.indexOf(k) !== -1 && testResults.indexOf(true) !== -1) {
+ return true;
+ } else {
+ if (testResults.indexOf(true) === -1) {
+ filterTest = false;
+ }
+ }
+ }
+ }
+
+ return filterTest;
+ },
+
+ /**
+ * Build pagination numbers and next/prev links - private
+ *
+ * @param currentPage {number}
+ * @param totalPages {number}
+ *
+ * @returns {string}
+ */
+ _paginationOutput: function(currentPage, totalPages) {
+ this.writeDebug('_paginationOutput',arguments);
+
+ currentPage = parseInt(currentPage);
+ totalPages = Math.ceil(totalPages);
+ var pagesStart, pagesEnd;
+ var output = '';
+ var nextPage = currentPage + 1;
+ var prevPage = currentPage - 1;
+ var pagesCutoff = 5;
+ var pagesCeiling = Math.ceil(pagesCutoff / 2);
+ var pagesFloor = Math.floor(pagesCutoff / 2);
+
+ // Determine page numbers to display.
+ if (totalPages < pagesCutoff) {
+ pagesStart = 0;
+ pagesEnd = totalPages;
+ } else if (currentPage >= 0 && currentPage <= pagesCeiling) {
+ pagesStart = 0;
+ pagesEnd = pagesCutoff;
+ } else if ((currentPage + pagesFloor) >= totalPages) {
+ pagesStart = (totalPages - pagesCutoff);
+ pagesEnd = totalPages;
+ } else {
+ pagesStart = (currentPage - pagesCeiling);
+ pagesEnd = (currentPage + pagesFloor);
+ }
+
+ // Previous page
+ if ( currentPage > 0 ) {
+ output += '';
+ output += '' + this.settings.prevPage + ' ';
+ output += ' ';
+ }
+
+ // Additional pages indicator.
+ if ((currentPage + 1) >= pagesCutoff && totalPages > pagesCutoff) {
+ // First page link.
+ output += '';
+ output += '' + 1 + ' ';
+ output += ' ';
+
+ output += '';
+ output += '… ';
+ output += ' ';
+ }
+
+ // Add the numbers
+ for (var p = pagesStart; p < pagesEnd; p++) {
+ var n = p + 1;
+
+ if (p === currentPage) {
+ output += '';
+ output += '' + n + ' ';
+ output += ' ';
+ } else {
+ output += '';
+ output += '' + n + ' ';
+ output += ' ';
+ }
+ }
+
+ // Additional pages indicator.
+ if ((currentPage + pagesCeiling) <= totalPages && totalPages > pagesCutoff) {
+ output += '';
+ output += '… ';
+ output += ' ';
+
+ // Last page link.
+ output += '';
+ output += '' + totalPages + ' ';
+ output += ' ';
+ }
+
+ // Next page
+ if ( nextPage < totalPages ) {
+ output += '';
+ output += '' + this.settings.nextPage + ' ';
+ output += ' ';
+ }
+
+ return output;
+ },
+
+ /**
+ * Reset pagination after the input has changed
+ */
+ paginationReset: function() {
+ this.writeDebug('paginationReset',arguments);
+
+ var currentUrl = window.location.href;
+ var url = new URL(currentUrl);
+
+ // Remove the old page in the URL.
+ url.searchParams.delete('bhsl-page');
+
+ // Update the query string param to match the new value.
+ if (history.pushState) {
+ window.history.pushState({path: url.href}, '', url.href);
+ }
+ },
+
+ /**
+ * Determine the total number of pages for pagination
+ */
+ totalPages: function() {
+ this.writeDebug('totalPages',arguments);
+
+ // Location limit.
+ if (
+ typeof originalOrigin !== 'undefined' &&
+ this.settings.storeLimit > 0 &&
+ locationset.length > this.settings.storeLimit
+ ) {
+ return this.settings.storeLimit / this.settings.locationsPerPage;
+ }
+
+ // WP API response after search.
+ if (locationsTotal > 0) {
+ return locationsTotal / this.settings.locationsPerPage;
+ }
+
+ // Unlimited or last page.
+ if (
+ this.settings.storeLimit === -1 ||
+ locationset.length < this.settings.storeLimit
+ ) {
+ return locationset.length / this.settings.locationsPerPage;
+ } else {
+ return this.settings.storeLimit / this.settings.locationsPerPage;
+ }
+ },
+
+ /**
+ * Set up the pagination pages
+ *
+ * @param currentPage {number} optional current page
+ */
+ paginationSetup: function (currentPage) {
+ this.writeDebug('paginationSetup',arguments);
+ var pagesOutput = '';
+ var $paginationList = $('.bh-sl-pagination-container .bh-sl-pagination');
+
+ // Total pages
+ var totalPages = this.totalPages();
+
+ // Current page check
+ if (typeof currentPage === 'undefined') {
+ currentPage = 0;
+ }
+
+ // Initial pagination setup
+ if ($paginationList.length === 0) {
+
+ pagesOutput = this._paginationOutput(currentPage, totalPages);
+ }
+ // Update pagination on page change
+ else {
+ // Remove the old pagination
+ $paginationList.empty();
+
+ // Add the numbers
+ pagesOutput = this._paginationOutput(currentPage, totalPages);
+ }
+
+ $paginationList.append(pagesOutput);
+ },
+
+ /**
+ * Determine if the legacy or Advanced markers should be used
+ *
+ * @returns {boolean}
+ */
+ useLegacyMarkers: function() {
+ this.writeDebug('useLegacyMarkers',arguments);
+
+ return !this.settings.mapSettings.hasOwnProperty('mapId') ||
+ this.settings.mapSettings.mapId === '';
+ },
+
+ /**
+ * Legacy marker image setup
+ *
+ * Original functionality supporting the now deprecated google.maps.Marker
+ * This will eventually be removed in favor of markerImage below.
+ *
+ * @param markerUrl {string} path to marker image
+ * @param markerWidth {number} width of marker
+ * @param markerHeight {number} height of marker
+ *
+ * @returns {Object} Google Maps icon object
+ */
+ legacyMarkerImage: function (markerUrl, markerWidth, markerHeight) {
+ this.writeDebug('legacyMarkerImage',arguments);
+ var markerImg;
+
+ // User defined marker dimensions
+ if (typeof markerWidth !== 'undefined' && typeof markerHeight !== 'undefined') {
+ markerImg = {
+ url: markerUrl,
+ size: new google.maps.Size(markerWidth, markerHeight),
+ scaledSize: new google.maps.Size(markerWidth, markerHeight)
+ };
+ }
+ // Default marker dimensions: 32px x 32px
+ else {
+ markerImg = {
+ url: markerUrl,
+ size: new google.maps.Size(32, 32),
+ scaledSize: new google.maps.Size(32, 32)
+ };
+ }
+
+ return markerImg;
+ },
+
+ /**
+ * Marker image setup
+ *
+ * @param markerUrl {string} path to marker image
+ * @param markerWidth {number} width of marker
+ * @param markerHeight {number} height of marker
+ *
+ * @returns {HTMLImageElement} Image element
+ */
+ markerImage: function (markerUrl, markerWidth, markerHeight) {
+ this.writeDebug('markerImage',arguments);
+
+ // Check if legacy marker image should be used
+ if (this.useLegacyMarkers()) {
+ return this.legacyMarkerImage(markerUrl, markerWidth, markerHeight);
+ }
+
+ var markerImg = document.createElement('img');
+ markerImg.src = markerUrl;
+
+ // User defined marker dimensions
+ if (typeof markerWidth !== 'undefined' && typeof markerHeight !== 'undefined') {
+ markerImg.height = markerHeight;
+ markerImg.width = markerWidth;
+ }
+ // Default marker dimensions: 32px x 32px
+ else {
+ markerImg.height = 32;
+ markerImg.width = 32;
+ }
+
+ return markerImg;
+ },
+
+ /**
+ * Map marker setup
+ *
+ * @param point {Object} LatLng of current location
+ * @param name {string} location name
+ * @param address {string} location address
+ * @param letter {string} optional letter used for front-end identification and correlation between list and
+ * points
+ * @param map {Object} the Google Map
+ * @param category {string} location category/categories
+ *
+ * @returns {Object} Google Maps marker
+ */
+ createMarker: function (point, name, address, letter, map, category) {
+ this.writeDebug('createMarker',arguments);
+ var marker, markerImg, letterMarkerImg;
+ var categories = [];
+
+ // Custom multi-marker image override (different markers for different categories
+ if (this.settings.catMarkers !== null) {
+ if (typeof category !== 'undefined') {
+ // Multiple categories
+ if (category.indexOf(',') !== -1) {
+ // Break the category variable into an array if there are multiple categories for the location
+ categories = category.split(',');
+ // With multiple categories the color will be determined by the last matched category in the data
+ for (var i = 0; i < categories.length; i++) {
+ if (categories[i] in this.settings.catMarkers) {
+ markerImg = this.markerImage(
+ this.settings.catMarkers[categories[i]][0],
+ Number(this.settings.catMarkers[categories[i]][1]),
+ Number(this.settings.catMarkers[categories[i]][2])
+ );
+ }
+ }
+ }
+ // Single category
+ else {
+ if (category in this.settings.catMarkers) {
+ markerImg = this.markerImage(
+ this.settings.catMarkers[category][0],
+ Number(this.settings.catMarkers[category][1]),
+ Number(this.settings.catMarkers[category][2])
+ );
+ }
+ }
+ }
+ }
+
+ // Custom single marker image override
+ if (this.settings.markerImg !== null) {
+ if (this.settings.markerDim === null) {
+ markerImg = this.markerImage(this.settings.markerImg);
+ } else {
+ markerImg = this.markerImage(
+ this.settings.markerImg,
+ this.settings.markerDim.width,
+ this.settings.markerDim.height
+ );
+ }
+ }
+
+ // Marker setup
+ if (this.settings.callbackCreateMarker) {
+ // Marker override callback
+ marker = this.settings.callbackCreateMarker.call(this, map, point, letter, category);
+ }
+ else {
+ // Create the default markers
+ 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))) {
+ if (this.useLegacyMarkers()) {
+ marker = new google.maps.Marker({
+ draggable: false,
+ icon : markerImg, // Reverts to default marker if markerImg not set.
+ map : map,
+ optimized: false,
+ position : point,
+ title : name,
+ });
+ } else {
+ marker = new google.maps.marker.AdvancedMarkerElement({
+ content : markerImg, // Reverts to default marker if markerImg not set.
+ draggable: false,
+ map : map,
+ position : point,
+ title : name,
+ });
+ }
+ }
+ else {
+ // Letter markers
+ if (this.useLegacyMarkers()) {
+ marker = new google.maps.Marker({
+ draggable: false,
+ label : letter,
+ map : map,
+ optimized: false,
+ position : point,
+ title : name,
+ });
+ } else {
+ var letterPin = new google.maps.marker.PinElement({glyph: letter});
+
+ marker = new google.maps.marker.AdvancedMarkerElement({
+ content : letterPin.element,
+ draggable: false,
+ map : map,
+ position : point,
+ title : name,
+ });
+ }
+ }
+ }
+
+ return marker;
+ },
+
+ /**
+ * Define the location data for the templates - private
+ *
+ * @param currentMarker {Object} Google Maps marker
+ * @param storeStart {number} optional first location on the current page
+ * @param page {number} optional current page
+ *
+ * @returns {Object} extended location data object
+ */
+ _defineLocationData: function (currentMarker, storeStart, page) {
+ this.writeDebug('_defineLocationData',arguments);
+ var indicator = '';
+
+ if (this.useLegacyMarkers()) {
+ this._createLocationVariables(currentMarker.get('id'));
+ } else {
+ this._createLocationVariables(currentMarker.bhslID);
+ }
+
+ var altDistLength,
+ distLength;
+
+ if (locationData.distance <= 1) {
+ if (this.settings.lengthUnit === 'km') {
+ distLength = this.settings.kilometerLang;
+ altDistLength = this.settings.mileLang;
+ }
+ else {
+ distLength = this.settings.mileLang;
+ altDistLength = this.settings.kilometerLang;
+ }
+ }
+ else {
+ if (this.settings.lengthUnit === 'km') {
+ distLength = this.settings.kilometersLang;
+ altDistLength = this.settings.milesLang;
+ }
+ else {
+ distLength = this.settings.milesLang;
+ altDistLength = this.settings.kilometersLang;
+ }
+ }
+
+ var markerId;
+
+ // Set up alpha character
+ if (this.useLegacyMarkers()) {
+ markerId = currentMarker.get('id');
+ } else {
+ markerId = currentMarker.bhslID;
+ }
+
+ // Use dot markers instead of alpha if there are more than 26 locations
+ 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))) {
+ if (page > 0) {
+ indicator = storeStart + markerId + 1;
+ } else {
+ indicator = markerId + 1;
+ }
+ } else {
+ if (page > 0) {
+ indicator = String.fromCharCode('A'.charCodeAt(0) + (storeStart + markerId));
+ }
+ else {
+ indicator = String.fromCharCode('A'.charCodeAt(0) + markerId);
+ }
+ }
+
+ // Define location data
+ return {
+ location: [$.extend(locationData, {
+ 'markerid' : markerId,
+ 'marker' : indicator,
+ 'altlength': altDistLength,
+ 'length' : distLength,
+ 'origin' : originalOrigin
+ })]
+ };
+ },
+
+ /**
+ * Set up the list templates
+ *
+ * @param marker {Object} Google Maps marker
+ * @param storeStart {number} optional first location on the current page
+ * @param page {number} optional current page
+ */
+ listSetup: function (marker, storeStart, page) {
+ this.writeDebug('listSetup',arguments);
+ // Define the location data
+ var locations = this._defineLocationData(marker, storeStart, page);
+
+ // Set up the list template with the location data
+ var listHtml = listTemplate(locations);
+ $('.' + this.settings.locationList + ' > ul').append(listHtml);
+ },
+
+ /**
+ * Change the selected marker image
+ *
+ * @param marker {Object} Google Maps marker object
+ */
+ changeSelectedMarker: function (marker) {
+ var markerImg;
+
+ // Reset the previously selected marker
+ if ( typeof prevSelectedMarkerAfter !== 'undefined' ) {
+ prevSelectedMarkerAfter.setIcon( prevSelectedMarkerBefore );
+ }
+
+ // Change the selected marker icon
+ if (this.settings.selectedMarkerImgDim === null) {
+ markerImg = this.markerImage(this.settings.selectedMarkerImg);
+ } else {
+ markerImg = this.markerImage(this.settings.selectedMarkerImg, this.settings.selectedMarkerImgDim.width, this.settings.selectedMarkerImgDim.height);
+ }
+
+ // Save the marker before switching it
+ prevSelectedMarkerBefore = marker.icon;
+
+ marker.setIcon( markerImg );
+
+ // Save the marker to a variable so it can be reverted when another marker is clicked
+ prevSelectedMarkerAfter = marker;
+ },
+
+ /**
+ * Create the infowindow
+ *
+ * @param marker {Object} Google Maps marker object
+ * @param location {string} indicates if the list or a map marker was clicked
+ * @param infowindow Google Maps InfoWindow constructor
+ * @param storeStart {number}
+ * @param page {number}
+ */
+ createInfowindow: function (marker, location, infowindow, storeStart, page) {
+ this.writeDebug('createInfowindow',arguments);
+ var _this = this;
+ // Define the location data
+ var locations = this._defineLocationData(marker, storeStart, page);
+
+ // Set up the infowindow template with the location data
+ var formattedAddress = infowindowTemplate(locations);
+
+ // Opens the infowindow when list item is clicked
+ if (location === 'left') {
+ infowindow.setContent(formattedAddress);
+
+ if (this.useLegacyMarkers()) {
+ infowindow.open(marker.get('map'), marker);
+ } else {
+ infowindow.open(marker.map, marker);
+ }
+ }
+ // Opens the infowindow when the marker is clicked
+ else {
+ if (this.useLegacyMarkers()) {
+ google.maps.event.addListener(marker, 'click', function () {
+ infowindow.setContent(formattedAddress);
+ infowindow.open(marker.get('map'), marker);
+ // Focus on the list
+ var markerId = marker.get('id');
+ var $selectedLocation = $('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']');
+
+ if ($selectedLocation.length > 0) {
+ // Marker click callback
+ if (_this.settings.callbackMarkerClick) {
+ _this.settings.callbackMarkerClick.call(this, marker, markerId, $selectedLocation, locationset[markerId], _this.map);
+ }
+
+ $('.' + _this.settings.locationList + ' li').removeClass('list-focus');
+ $selectedLocation.addClass('list-focus');
+
+ // Scroll list to selected marker
+ var $container = $('.' + _this.settings.locationList);
+ $container.animate({
+ scrollTop: $selectedLocation.offset().top - $container.offset().top + $container.scrollTop()
+ });
+ }
+
+ // Custom selected marker override
+ if (_this.settings.selectedMarkerImg !== null) {
+ _this.changeSelectedMarker(marker);
+ }
+ });
+ } else {
+ marker.addListener('click', function (domEvent, latLng) {
+ infowindow.setContent(formattedAddress);
+ infowindow.open(marker.map, marker);
+
+ // Focus on the list
+ var markerId = marker.bhslID;
+ var $selectedLocation = $('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']');
+
+ if ($selectedLocation.length > 0) {
+ // Marker click callback
+ if (_this.settings.callbackMarkerClick) {
+ _this.settings.callbackMarkerClick.call(this, marker, markerId, $selectedLocation, locationset[markerId], _this.map);
+ }
+
+ $('.' + _this.settings.locationList + ' li').removeClass('list-focus');
+ $selectedLocation.addClass('list-focus');
+
+ // Scroll list to selected marker
+ var $container = $('.' + _this.settings.locationList);
+ $container.animate({
+ scrollTop: $selectedLocation.offset().top - $container.offset().top + $container.scrollTop()
+ });
+ }
+
+ // Custom selected marker override
+ if (_this.settings.selectedMarkerImg !== null) {
+ _this.changeSelectedMarker(marker);
+ }
+ });
+ }
+ }
+ },
+
+ /**
+ * HTML5 geocoding function for automatic location detection
+ *
+ * @param position {Object} coordinates
+ */
+ autoGeocodeQuery: function (position) {
+ this.writeDebug('autoGeocodeQuery',arguments);
+ var _this = this,
+ distance = null,
+ $distanceInput = $('#' + this.settings.maxDistanceID),
+ originAddress;
+
+ // Query string parameters
+ if (this.settings.querystringParams === true) {
+ // Check for distance query string parameters
+ if (this.getQueryString(this.settings.maxDistanceID)){
+ distance = this.getQueryString(this.settings.maxDistanceID);
+
+ if ($distanceInput.val() !== '') {
+ distance = $distanceInput.val();
+ }
+ }
+ else{
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+ }
+ else {
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+
+ // The address needs to be determined for the directions link
+ var r = new this.reverseGoogleGeocode(this);
+ var latlng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
+ r.geocode({'latLng': latlng}, function (data) {
+ if (data !== null) {
+ originAddress = addressInput = data.address;
+ olat = mappingObj.lat = position.coords.latitude;
+ olng = mappingObj.lng = position.coords.longitude;
+ mappingObj.origin = originAddress;
+ mappingObj.distance = distance;
+ _this.mapping(mappingObj);
+
+ // Fill in the search box.
+ if (typeof originAddress !== 'undefined') {
+ $('#' + _this.settings.addressID).val(originAddress);
+ }
+ } else {
+ // Unable to geocode
+ _this.notify(_this.settings.addressErrorAlert);
+ }
+ });
+ },
+
+ /**
+ * Handle autoGeocode failure - private
+ *
+ */
+ _autoGeocodeError: function () {
+ this.writeDebug('_autoGeocodeError');
+ // If automatic detection doesn't work show an error
+ this.notify(this.settings.autoGeocodeErrorAlert);
+ },
+
+ /**
+ * Default location method
+ */
+ defaultLocation: function() {
+ this.writeDebug('defaultLocation');
+ var _this = this,
+ distance = null,
+ $distanceInput = $('#' + this.settings.maxDistanceID),
+ originAddress;
+
+ // Query string parameters
+ if (this.settings.querystringParams === true) {
+ // Check for distance query string parameters
+ if (this.getQueryString(this.settings.maxDistanceID)){
+ distance = this.getQueryString(this.settings.maxDistanceID);
+
+ if ($distanceInput.val() !== '') {
+ distance = $distanceInput.val();
+ }
+ }
+ else {
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+ }
+ else {
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+
+ // The address needs to be determined for the directions link
+ var r = new this.reverseGoogleGeocode(this);
+ var latlng = new google.maps.LatLng(this.settings.defaultLat, this.settings.defaultLng);
+ r.geocode({'latLng': latlng}, function (data) {
+ if (data !== null) {
+ originAddress = addressInput = data.address;
+ olat = mappingObj.lat = _this.settings.defaultLat;
+ olng = mappingObj.lng = _this.settings.defaultLng;
+ mappingObj.distance = distance;
+ mappingObj.origin = originAddress;
+ _this.mapping(mappingObj);
+ } else {
+ // Unable to geocode
+ _this.notify(_this.settings.addressErrorAlert);
+ }
+ });
+ },
+
+ /**
+ * Change the page
+ *
+ * @param newPage {number} page to change to
+ */
+ paginationChange: function (newPage) {
+ this.writeDebug('paginationChange',arguments);
+
+ var currentUrl = window.location.href;
+ var url = new URL(currentUrl);
+
+ // Update the page in the URL.
+ url.searchParams.set('bhsl-page', parseInt(newPage) + 1);
+
+ // Update the query string param to match the new value.
+ if (history.pushState) {
+ window.history.pushState({path: url.href}, '', url.href);
+ }
+
+ // Page change callback
+ if (this.settings.callbackPageChange) {
+ this.settings.callbackPageChange.call(this, newPage);
+ }
+
+ mappingObj.page = newPage;
+ this.mapping(mappingObj);
+ },
+
+ /**
+ * Get the address by marker ID
+ *
+ * @param markerID {number} location ID
+ *
+ * @returns {string} formatted address
+ */
+ getAddressByMarker: function(markerID) {
+ this.writeDebug('getAddressByMarker',arguments);
+ var formattedAddress = "";
+ // Set up formatted address
+ if(locationset[markerID].address){ formattedAddress += locationset[markerID].address + ' '; }
+ if(locationset[markerID].address2){ formattedAddress += locationset[markerID].address2 + ' '; }
+ if(locationset[markerID].city){ formattedAddress += locationset[markerID].city + ', '; }
+ if(locationset[markerID].state){ formattedAddress += locationset[markerID].state + ' '; }
+ if(locationset[markerID].postal){ formattedAddress += locationset[markerID].postal + ' '; }
+ if(locationset[markerID].country){ formattedAddress += locationset[markerID].country + ' '; }
+
+ return formattedAddress;
+ },
+
+ /**
+ * Clear the markers from the map
+ */
+ clearMarkers: function() {
+ this.writeDebug('clearMarkers');
+ var locationsLimit = null;
+
+ if (locationset.length < this.settings.storeLimit) {
+ locationsLimit = locationset.length;
+ }
+ else {
+ locationsLimit = this.settings.storeLimit;
+ }
+
+ for (var i = 0; i < locationsLimit; i++) {
+ markers[i].setMap(null);
+ }
+ },
+
+ /**
+ * Handle inline direction requests
+ *
+ * @param origin {string} origin address
+ * @param locID {number} location ID
+ * @param map {Object} Google Map
+ */
+ directionsRequest: function(origin, locID, map) {
+ this.writeDebug('directionsRequest',arguments);
+
+ // Directions request callback
+ if (this.settings.callbackDirectionsRequest) {
+ this.settings.callbackDirectionsRequest.call(this, origin, locID, map, locationset[locID]);
+ }
+
+ var destination = this.getAddressByMarker(locID);
+
+ if (destination) {
+ // Hide the location list
+ $('.' + this.settings.locationList + ' ul').hide();
+ // Remove the markers
+ this.clearMarkers();
+
+ // Clear the previous directions request
+ if (directionsDisplay !== null && typeof directionsDisplay !== 'undefined') {
+ directionsDisplay.setMap(null);
+ directionsDisplay = null;
+ }
+
+ directionsDisplay = new google.maps.DirectionsRenderer();
+ directionsService = new google.maps.DirectionsService();
+
+ // Directions request
+ directionsDisplay.setMap(map);
+ directionsDisplay.setPanel($('.bh-sl-directions-panel').get(0));
+
+ var request = {
+ origin: origin,
+ destination: destination,
+ travelMode: google.maps.TravelMode.DRIVING
+ };
+ directionsService.route(request, function(response, status) {
+ if (status === google.maps.DirectionsStatus.OK) {
+ directionsDisplay.setDirections(response);
+ }
+ });
+
+ $('.' + this.settings.locationList).prepend('');
+ }
+
+ $(document).off('click', '.' + this.settings.locationList + ' li .loc-directions a');
+ },
+
+ /**
+ * Close the directions panel and reset the map with the original locationset and zoom
+ */
+ closeDirections: function() {
+ this.writeDebug('closeDirections');
+
+ // Close directions callback
+ if (this.settings.callbackCloseDirections) {
+ this.settings.callbackCloseDirections.call(this);
+ }
+
+ // Remove the close icon, remove the directions, add the list back
+ this.reset();
+
+ if ((olat) && (olng)) {
+ if (this.countFilters() === 0) {
+ this.settings.mapSettings.zoom = originalZoom;
+ }
+ else {
+ this.settings.mapSettings.zoom = 0;
+ }
+ this.processForm(null);
+ }
+
+ $(document).off('click.'+pluginName, '.' + this.settings.locationList + ' .bh-sl-close-icon');
+ },
+
+ /**
+ * Handle length unit swap
+ *
+ * @param $lengthSwap
+ */
+ lengthUnitSwap: function($lengthSwap) {
+ this.writeDebug('lengthUnitSwap',arguments);
+
+ if ($lengthSwap.val() === 'alt-distance') {
+ $('.' + this.settings.locationList + ' .loc-alt-dist').show();
+ $('.' + this.settings.locationList + ' .loc-default-dist').hide();
+ } else if ($lengthSwap.val() === 'default-distance') {
+ $('.' + this.settings.locationList + ' .loc-default-dist').show();
+ $('.' + this.settings.locationList + ' .loc-alt-dist').hide();
+ }
+ },
+
+ /**
+ * Process the form values and/or query string
+ *
+ * @param e {Object} event
+ */
+ processForm: function (e) {
+ this.writeDebug('processForm',arguments);
+ var _this = this,
+ distance = null,
+ geocodeRestrictions = {},
+ $addressInput = $('#' + this.settings.addressID),
+ $searchInput = $('#' + this.settings.searchID),
+ $distanceInput = $('#' + this.settings.maxDistanceID),
+ region = '';
+
+ // Stop the form submission.
+ if (typeof e !== 'undefined' && e !== null) {
+ e.preventDefault();
+ }
+
+ // Blur any form field to hide mobile keyboards.
+ $('.' + _this.settings.formContainer +' input, .' + _this.settings.formContainer + ' select').blur();
+
+ // Query string parameters
+ if (this.settings.querystringParams === true) {
+ // Check for query string parameters
+ if (this.getQueryString(this.settings.addressID) || this.getQueryString(this.settings.searchID) || this.getQueryString(this.settings.maxDistanceID)) {
+ addressInput = this.getQueryString(this.settings.addressID);
+ searchInput = this.getQueryString(this.settings.searchID);
+ distance = this.getQueryString(this.settings.maxDistanceID);
+
+ // Max distance field.
+ if (distance && $('#' + this.settings.maxDistanceID + ' option[value=' + distance + ']').length) {
+ $distanceInput.val(distance);
+ }
+
+ // Update zoom if origin coordinates are available and a distance query string value is set.
+ if (addressInput && distance) {
+ _this.settings.mapSettings.zoom = 0;
+ }
+
+ // The form should override the query string parameters.
+ if ($addressInput.val() !== '') {
+ addressInput = $addressInput.val();
+ }
+ if ($searchInput.val() !== '') {
+ searchInput = $searchInput.val();
+ }
+ if ($distanceInput.val() !== '') {
+ distance = $distanceInput.val();
+ }
+ }
+ else {
+ // Get the user input and use it
+ addressInput = $addressInput.val() || '';
+ searchInput = $searchInput.val() || '';
+
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+ }
+ else {
+ // Get the user input and use it
+ addressInput = $addressInput.val() || '';
+ searchInput = $searchInput.val() || '';
+ // Get the distance if set
+ if (this.settings.maxDistance === true) {
+ distance = $distanceInput.val() || '';
+ }
+ }
+
+ // Region
+ if (this.settings.callbackRegion) {
+ // Region override callback
+ region = this.settings.callbackRegion.call(this, addressInput, searchInput, distance);
+ } else {
+ // Region setting
+ region = $('#' + this.settings.regionID).val();
+ }
+
+ // Form values callback
+ if (this.settings.callbackFormVals) {
+ this.settings.callbackFormVals.call(this, addressInput, searchInput, distance, region);
+ }
+
+ // Add component restriction if the region has been set.
+ if (typeof region !== 'undefined') {
+ geocodeRestrictions = {
+ country: region
+ };
+ }
+
+ // Component restriction value via callback.
+ if (typeof this.settings.callbackGeocodeRestrictions === 'function') {
+ // Component restriction override callback
+ geocodeRestrictions = this.settings.callbackGeocodeRestrictions.call(this, addressInput, searchInput, distance);
+ }
+
+ if (addressInput === '' && searchInput === '' && this.settings.autoGeocode !== true) {
+ this._start();
+ }
+ else if (addressInput !== '') {
+ // Check for existing name search and remove if address input is blank.
+ if (searchInput === '' && filters.hasOwnProperty('name')) {
+ delete filters.name;
+ }
+
+ // Geocode the origin if needed
+ if (typeof originalOrigin !== 'undefined' && typeof olat !== 'undefined' && typeof olng !== 'undefined' && (addressInput === originalOrigin)) {
+ // Run the mapping function
+ mappingObj.lat = olat;
+ mappingObj.lng = olng;
+ mappingObj.origin = addressInput;
+ mappingObj.name = searchInput;
+ mappingObj.distance = distance;
+ _this.mapping(mappingObj);
+ }
+ else {
+ var g = new this.googleGeocode(this);
+ g.geocode({
+ address: addressInput,
+ componentRestrictions: geocodeRestrictions,
+ region: region
+ }, function (data) {
+ if (data !== null) {
+ olat = data.latitude;
+ olng = data.longitude;
+
+ // Run the mapping function
+ mappingObj.lat = olat;
+ mappingObj.lng = olng;
+ mappingObj.origin = addressInput;
+ mappingObj.name = searchInput;
+ mappingObj.distance = distance;
+ mappingObj.geocodeResult = data.geocodeResult;
+ _this.mapping(mappingObj);
+ } else {
+ // Unable to geocode
+ _this.notify(_this.settings.addressErrorAlert);
+ }
+ });
+ }
+ }
+ else if (searchInput !== '') {
+ // Check for existing origin and remove if address input is blank.
+ if ( addressInput === '' ) {
+ delete mappingObj.origin;
+ }
+
+ mappingObj.name = searchInput;
+ _this.mapping(mappingObj);
+ }
+ else if (this.settings.autoGeocode === true) {
+ // Run the mapping function
+ mappingObj.lat = olat;
+ mappingObj.lng = olng;
+ mappingObj.origin = addressInput;
+ mappingObj.name = searchInput;
+ mappingObj.distance = distance;
+ _this.mapping(mappingObj);
+ }
+
+ // Reset pagination if the input has changed.
+ if (typeof originalOrigin !== 'undefined' && addressInput !== originalOrigin) {
+ this.paginationReset();
+ }
+ },
+
+ /**
+ * Checks distance of each location and sets up the locationset array
+ *
+ * @param data {Object} location data object
+ * @param lat {number} origin latitude
+ * @param lng {number} origin longitude
+ * @param origin {string} origin address
+ * @param maxDistance {number} maximum distance if set
+ */
+ locationsSetup: function (data, lat, lng, origin, maxDistance) {
+ this.writeDebug('locationsSetup',arguments);
+ if (typeof origin !== 'undefined') {
+ if (!data.distance) {
+ data.distance = this.geoCodeCalcCalcDistance(lat, lng, data.lat, data.lng, GeoCodeCalc.EarthRadius);
+
+ // Alternative distance length unit
+ if (this.settings.lengthUnit === 'm') {
+ // Miles to kilometers
+ data.altdistance = parseFloat(data.distance)*1.609344;
+ } else if (this.settings.lengthUnit === 'km') {
+ // Kilometers to miles
+ data.altdistance = parseFloat(data.distance)/1.609344;
+ }
+ }
+ }
+
+ // Make sure the location coordinates are valid.
+ if (!this.coordinatesInRange(data.lat, data.lng)) {
+ this.writeDebug('locationsSetup', "location ignored because coordinates out of range: " + maxDistance, data);
+ return;
+ }
+
+ // Create the array
+ if (this.settings.maxDistance === true && typeof maxDistance !== 'undefined' && maxDistance !== null) {
+ if (data.distance <= maxDistance) {
+ locationset.push( data );
+ } else {
+ this.writeDebug('locationsSetup', "location ignored because it is out of maxDistance: " + maxDistance, data);
+ return;
+ }
+ } else if (this.settings.maxDistance === true && this.settings.querystringParams === true && typeof maxDistance !== 'undefined' && maxDistance !== null) {
+ if (data.distance <= maxDistance) {
+ locationset.push( data );
+ } else {
+ this.writeDebug('locationsSetup', "location ignored because it is out of maxDistance: " + maxDistance, data);
+ return;
+ }
+ } else {
+ locationset.push( data );
+ }
+ },
+
+ /**
+ * Set up front-end sorting functionality
+ */
+ sorting: function() {
+ this.writeDebug('sorting',arguments);
+ var _this = this,
+ $mapDiv = $('#' + _this.settings.mapID),
+ $sortSelect = $('#' + _this.settings.sortID);
+
+ if ($sortSelect.length === 0) {
+ return;
+ }
+
+ $sortSelect.on('change.'+pluginName, function (e) {
+ e.stopPropagation();
+
+ // Reset pagination.
+ if (_this.settings.pagination === true) {
+ _this.paginationChange(0);
+ }
+
+ var sortMethod,
+ sortVal;
+
+ sortMethod = (typeof $(this).find(':selected').attr('data-method') !== 'undefined') ? $(this).find(':selected').attr('data-method') : 'distance';
+ sortVal = $(this).val();
+
+ _this.settings.sortBy.method = sortMethod;
+ _this.settings.sortBy.prop = sortVal;
+
+ // Callback
+ if (_this.settings.callbackSorting) {
+ _this.settings.callbackSorting.call(this, _this.settings.sortBy);
+ }
+
+ if ($mapDiv.hasClass('bh-sl-map-open')) {
+ _this.mapping(mappingObj);
+ }
+ });
+ },
+
+ /**
+ * Set up front-end ordering functionality - this ties in to sorting and that has to be enabled for this to
+ * work.
+ */
+ order: function() {
+ this.writeDebug('order',arguments);
+ var _this = this,
+ $mapDiv = $('#' + _this.settings.mapID),
+ $orderSelect = $('#' + _this.settings.orderID);
+
+ if ($orderSelect.length === 0) {
+ return;
+ }
+
+ $orderSelect.on('change.'+pluginName, function (e) {
+ e.stopPropagation();
+
+ // Reset pagination.
+ if (_this.settings.pagination === true) {
+ _this.paginationChange(0);
+ }
+
+ _this.settings.sortBy.order = $(this).val();
+
+ // Callback
+ if (_this.settings.callbackOrder) {
+ _this.settings.callbackOrder.call(this, _this.settings.order);
+ }
+
+ if ($mapDiv.hasClass('bh-sl-map-open')) {
+ _this.mapping(mappingObj);
+ }
+ });
+ },
+
+ /**
+ * Distance filtering
+ */
+ distanceFiltering: function () {
+ this.writeDebug('distanceFiltering');
+ var _this = this;
+ var $distanceInput = $('#' + this.settings.maxDistanceID);
+
+ // Add event listener
+ $distanceInput.on('change.'+pluginName, function (e) {
+ e.stopPropagation();
+
+ // Query string parameter value updates on change.
+ if (_this.settings.querystringParams === true) {
+ var currentUrl = window.location.href;
+ var url = new URL(currentUrl);
+
+ // Update the distance in the URL.
+ url.searchParams.set(_this.settings.maxDistanceID, this.value);
+
+ // Update the query string param to match the new value.
+ if (history.pushState) {
+ window.history.pushState({path: url.href}, '', url.href);
+ } else {
+ window.location.replace(url.href);
+ }
+ }
+
+ if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
+ if ((olat) && (olng)) {
+ _this.settings.mapSettings.zoom = 0;
+ _this.processForm();
+ } else {
+ _this.mapping(mappingObj);
+ }
+ }
+ });
+ },
+
+ /**
+ * Count the selected filters
+ *
+ * @returns {number}
+ */
+ countFilters: function () {
+ this.writeDebug('countFilters');
+ var filterCount = 0;
+
+ if (!this.isEmptyObject(filters)) {
+ for (var key in filters) {
+ if (filters.hasOwnProperty(key)) {
+ filterCount += filters[key].length;
+ }
+ }
+ }
+
+ return filterCount;
+ },
+
+ /**
+ * Find the existing checked boxes for each checkbox filter - private
+ *
+ * @param key {string} object key
+ */
+ _existingCheckedFilters: function(key) {
+ this.writeDebug('_existingCheckedFilters',arguments);
+ $('#' + this.settings.taxonomyFilters[key] + ' input[type=checkbox]').each(function () {
+ if ($(this).prop('checked')) {
+ var filterVal = $(this).val();
+
+ // Only add the taxonomy id if it doesn't already exist
+ if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
+ filters[key].push(filterVal);
+ }
+ }
+ });
+ },
+
+ /**
+ * Find the existing selected value for each select filter - private
+ *
+ * @param key {string} object key
+ */
+ _existingSelectedFilters: function(key) {
+ this.writeDebug('_existingSelectedFilters',arguments);
+ $('#' + this.settings.taxonomyFilters[key] + ' select').each(function () {
+ var filterVal = $(this).val();
+
+ // Only add the taxonomy id if it doesn't already exist
+ if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
+ filters[key] = [filterVal];
+ }
+ });
+ },
+
+ /**
+ * Find the existing selected value for each radio button filter - private
+ *
+ * @param key {string} object key
+ */
+ _existingRadioFilters: function(key) {
+ this.writeDebug('_existingRadioFilters',arguments);
+ $('#' + this.settings.taxonomyFilters[key] + ' input[type=radio]').each(function () {
+ if ($(this).prop('checked')) {
+ var filterVal = $(this).val();
+
+ // Only add the taxonomy id if it doesn't already exist
+ if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
+ filters[key] = [filterVal];
+ }
+ }
+ });
+ },
+
+ /**
+ * Check for existing filter selections
+ */
+ checkFilters: function () {
+ this.writeDebug('checkFilters');
+ for(var key in this.settings.taxonomyFilters) {
+
+ if (this.settings.taxonomyFilters.hasOwnProperty(key)) {
+ // Find the existing checked boxes for each checkbox filter
+ this._existingCheckedFilters(key);
+
+ // Find the existing selected value for each select filter
+ this._existingSelectedFilters(key);
+
+ // Find the existing value for each radio button filter
+ this._existingRadioFilters(key);
+ }
+ }
+ },
+
+ /**
+ * Select the indicated values from query string parameters.
+ *
+ * @param taxonomy {string} Current taxonomy.
+ * @param value {array} Query string array values.
+ */
+ selectQueryStringFilters: function( taxonomy, value ) {
+ this.writeDebug('selectQueryStringFilters', arguments);
+
+ var $taxGroupContainer = $('#' + this.settings.taxonomyFilters[taxonomy]);
+
+ // Handle checkboxes.
+ if ( $taxGroupContainer.find('input[type="checkbox"]').length ) {
+
+ for ( var i = 0; i < value.length; i++ ) {
+ $taxGroupContainer.find('input:checkbox[value="' + value[i] + '"]').prop('checked', true);
+ }
+ }
+
+ // Handle select fields.
+ if ( $taxGroupContainer.find('select').length ) {
+ // Only expecting one value for select fields.
+ $taxGroupContainer.find('option[value="' + value[0] + '"]').prop('selected', true);
+ }
+
+ // Handle radio buttons.
+ if ( $taxGroupContainer.find('input[type="radio"]').length ) {
+ // Only one value for radio button.
+ $taxGroupContainer.find('input:radio[value="' + value[0] + '"]').prop('checked', true);
+ }
+ },
+
+ /**
+ * Check query string parameters for filter values.
+ */
+ checkQueryStringFilters: function () {
+ this.writeDebug('checkQueryStringFilters',arguments);
+
+ // Loop through the filters.
+ for(var key in filters) {
+ if (filters.hasOwnProperty(key)) {
+ var filterVal = this.getQueryString(key);
+
+ // Check for multiple values separated by comma.
+ if ( filterVal.indexOf( ',' ) !== -1 ) {
+ filterVal = filterVal.split( ',' );
+ }
+
+ // Only add the taxonomy id if it doesn't already exist
+ if (typeof filterVal !== 'undefined' && filterVal !== '' && filters[key].indexOf(filterVal) === -1) {
+ if ( Array.isArray( filterVal ) ) {
+ filters[key] = filterVal;
+ } else {
+ filters[key] = [filterVal];
+ }
+ }
+
+ // Select the filters indicated in the query string.
+ if ( filters[key].length ) {
+ this.selectQueryStringFilters( key, filters[key] );
+ }
+ }
+ }
+ },
+
+ /**
+ * Get the filter key from the taxonomyFilter setting
+ *
+ * @param filterContainer {string} ID of the changed filter's container
+ */
+ getFilterKey: function (filterContainer) {
+ this.writeDebug('getFilterKey',arguments);
+ for (var key in this.settings.taxonomyFilters) {
+ if (this.settings.taxonomyFilters.hasOwnProperty(key)) {
+ for (var i = 0; i < this.settings.taxonomyFilters[key].length; i++) {
+ if (this.settings.taxonomyFilters[key] === filterContainer) {
+ return key;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Initialize or reset the filters object to its original state
+ */
+ taxonomyFiltersInit: function () {
+ this.writeDebug('taxonomyFiltersInit');
+
+ // Set up the filters
+ for(var key in this.settings.taxonomyFilters) {
+ if (this.settings.taxonomyFilters.hasOwnProperty(key)) {
+ filters[key] = [];
+ }
+ }
+ },
+
+ /**
+ * Taxonomy filtering
+ */
+ taxonomyFiltering: function() {
+ this.writeDebug('taxonomyFiltering');
+ var _this = this;
+
+ // Set up the filters
+ _this.taxonomyFiltersInit();
+
+ // Check query string for taxonomy parameter keys.
+ _this.checkQueryStringFilters();
+
+ // Handle filter updates
+ $('.' + this.settings.taxonomyFiltersContainer).on('change.'+pluginName, 'input, select', function (e) {
+ e.stopPropagation();
+
+ var filterVal, filterContainer, filterKey;
+
+ // Reset pagination.
+ if (_this.settings.pagination === true) {
+ _this.paginationReset();
+ }
+
+ // Handle checkbox filters
+ if ($(this).is('input[type="checkbox"]')) {
+ // First check for existing selections
+ _this.checkFilters();
+
+ filterVal = $(this).val();
+ filterContainer = $(this).closest('.bh-sl-filters').attr('id');
+ filterKey = _this.getFilterKey(filterContainer);
+
+ if (filterKey) {
+ // Add or remove filters based on checkbox values
+ if ($(this).prop('checked')) {
+ // Add ids to the filter arrays as they are checked
+ if (filters[filterKey].indexOf(filterVal) === -1) {
+ filters[filterKey].push(filterVal);
+ }
+
+ if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
+ if ((olat) && (olng)) {
+ _this.settings.mapSettings.zoom = 0;
+ _this.processForm();
+ }
+ else {
+ _this.mapping(mappingObj);
+ }
+ }
+ }
+ else {
+ // Remove ids from the filter arrays as they are unchecked
+ var filterIndex = filters[filterKey].indexOf(filterVal);
+ if (filterIndex > -1) {
+ filters[filterKey].splice(filterIndex, 1);
+ if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
+ if ((olat) && (olng)) {
+ if (_this.countFilters() === 0) {
+ _this.settings.mapSettings.zoom = originalZoom;
+ } else {
+ _this.settings.mapSettings.zoom = 0;
+ }
+
+ _this.processForm();
+ }
+ else {
+ _this.mapping(mappingObj);
+ }
+ }
+ }
+ }
+ }
+ }
+ // Handle select or radio filters
+ else if ($(this).is('select') || $(this).is('input[type="radio"]')) {
+ // First check for existing selections
+ _this.checkFilters();
+
+ filterVal = $(this).val();
+ filterContainer = $(this).closest('.bh-sl-filters').attr('id');
+ filterKey = _this.getFilterKey(filterContainer);
+
+ // Check for blank filter on select since default val could be empty
+ if (filterVal) {
+ if (filterKey) {
+ filters[filterKey] = [filterVal];
+ if ($('#' + _this.settings.mapID).hasClass('bh-sl-map-open') === true) {
+ if ((olat) && (olng)) {
+ _this.settings.mapSettings.zoom = 0;
+ _this.processForm();
+ } else {
+ _this.mapping(mappingObj);
+ }
+ }
+ }
+ }
+ // Reset if the default option is selected
+ else {
+ if (filterKey) {
+ filters[filterKey] = [];
+ }
+ _this.reset();
+ if ((olat) && (olng)) {
+ _this.settings.mapSettings.zoom = originalZoom;
+ _this.processForm();
+ }
+ else {
+ _this.mapping(mappingObj);
+ }
+ }
+ }
+ });
+ },
+
+ /**
+ * Updates the location list to reflect the markers that are displayed on the map
+ *
+ * @param markers {Object} Map markers
+ * @param map {Object} Google map
+ */
+ checkVisibleMarkers: function(markers, map) {
+ this.writeDebug('checkVisibleMarkers',arguments);
+ var _this = this;
+ var locations, listHtml;
+
+ // Empty the location list
+ $('.' + this.settings.locationList + ' ul').empty();
+
+ // Set up the new list
+ $(markers).each(function(x, marker){
+ if (_this.useLegacyMarkers()) {
+ if (map.getBounds().contains(marker.getPosition())) {
+ // Define the location data
+ _this.listSetup(marker, 0, 0);
+
+ // Set up the list template with the location data
+ listHtml = listTemplate(locations);
+ $('.' + _this.settings.locationList + ' > ul').append(listHtml);
+ }
+ } else {
+ if (map.getBounds().contains(marker.position)) {
+ // Define the location data
+ _this.listSetup(marker, 0, 0);
+
+ // Set up the list template with the location data
+ listHtml = listTemplate(locations);
+ $('.' + _this.settings.locationList + ' > ul').append(listHtml);
+ }
+ }
+ });
+
+ // Re-add the list background colors
+ $('.' + this.settings.locationList + ' ul li:even').css('background', this.settings.listColor1);
+ $('.' + this.settings.locationList + ' ul li:odd').css('background', this.settings.listColor2);
+ },
+
+ /**
+ * Performs a new search when the map is dragged to a new position
+ *
+ * @param map {Object} Google map
+ */
+ dragSearch: function(map) {
+ this.writeDebug('dragSearch',arguments);
+ var newCenter = map.getCenter(),
+ newCenterCoords,
+ _this = this;
+
+ // Save the new zoom setting
+ this.settings.mapSettings.zoom = map.getZoom();
+
+ olat = mappingObj.lat = newCenter.lat();
+ olng = mappingObj.lng = newCenter.lng();
+
+ // Determine the new origin address
+ var newAddress = new this.reverseGoogleGeocode(this);
+ newCenterCoords = new google.maps.LatLng(mappingObj.lat, mappingObj.lng);
+ newAddress.geocode({'latLng': newCenterCoords}, function (data) {
+ if (data !== null) {
+ mappingObj.origin = addressInput = data.address;
+ _this.mapping(mappingObj);
+ } else {
+ // Unable to geocode
+ _this.notify(_this.settings.addressErrorAlert);
+ }
+ });
+ },
+
+ /**
+ * Handle no results
+ */
+ emptyResult: function() {
+ this.writeDebug('emptyResult',arguments);
+ var center,
+ locList = $('.' + this.settings.locationList + ' ul'),
+ myOptions = this.settings.mapSettings,
+ noResults;
+
+ // Create the map
+ this.map = new google.maps.Map(document.getElementById(this.settings.mapID), myOptions);
+
+ // Callback
+ if (this.settings.callbackNoResults) {
+ this.settings.callbackNoResults.call(this, this.map, myOptions);
+ }
+
+ // Empty the location list
+ locList.empty();
+
+ // Append the no results message
+ noResults = $('' + this.settings.noResultsTitle + '
' + this.settings.noResultsDesc + '').hide().fadeIn();
+ locList.append(noResults);
+
+ // Center on the original origin or 0,0 if not available
+ if ((olat) && (olng)) {
+ center = new google.maps.LatLng(olat, olng);
+ } else {
+ center = new google.maps.LatLng(0, 0);
+ }
+
+ this.map.setCenter(center);
+
+ if (originalZoom) {
+ this.map.setZoom(originalZoom);
+ }
+ },
+
+
+ /**
+ * Origin marker setup
+ *
+ * @param map {Object} Google map
+ * @param origin {string} Origin address
+ * @param originPoint {Object} LatLng of origin point
+ */
+ originMarker: function(map, origin, originPoint) {
+ this.writeDebug('originMarker',arguments);
+
+ if (this.settings.originMarker !== true) {
+ return;
+ }
+
+ var marker,
+ originImg;
+
+ if (typeof origin !== 'undefined') {
+ if (this.useLegacyMarkers()) {
+ if (this.settings.originMarkerImg !== null) {
+ if (this.settings.originMarkerDim === null) {
+ originImg = this.markerImage(this.settings.originMarkerImg);
+ }
+ else {
+ originImg = this.markerImage(this.settings.originMarkerImg, this.settings.originMarkerDim.width, this.settings.originMarkerDim.height);
+ }
+ }
+ else {
+ originImg = {
+ url: 'https://mt.googleapis.com/vt/icon/name=icons/spotlight/spotlight-waypoint-a.png'
+ };
+ }
+
+ marker = new google.maps.Marker({
+ position : originPoint,
+ map : map,
+ icon : originImg,
+ draggable: false
+ });
+ } else {
+ // Default green origin pin.
+ var defaultOriginPin = new google.maps.marker.PinElement({
+ background : '#39b25e',
+ borderColor: '#177d3d',
+ glyphColor : '#177d3c'
+ });
+
+ marker = new google.maps.marker.AdvancedMarkerElement({
+ content : defaultOriginPin.element,
+ draggable: false,
+ map : map,
+ position : originPoint,
+ title : name,
+ });
+
+ // Origin image.
+ if (this.settings.originMarkerImg !== null) {
+ originImg = document.createElement('img');
+
+ if (this.settings.originMarkerDim === null) {
+ originImg.src = this.settings.originMarkerImg;
+ } else {
+ originImg = this.markerImage(
+ this.settings.originMarkerImg,
+ this.settings.originMarkerDim.width,
+ this.settings.originMarkerDim.height,
+ );
+ }
+
+ marker.content = originImg;
+ }
+ }
+ }
+ },
+
+ /**
+ * Modal window setup
+ */
+ modalWindow: function() {
+ this.writeDebug('modalWindow');
+
+ if (this.settings.modal !== true) {
+ return;
+ }
+
+ var _this = this;
+
+ // Callback
+ if (_this.settings.callbackModalOpen) {
+ _this.settings.callbackModalOpen.call(this);
+ }
+
+ // Pop up the modal window
+ $('.' + _this.settings.overlay).fadeIn();
+ // Close modal when close icon is clicked and when background overlay is clicked
+ $(document).on('click.'+pluginName, '.' + _this.settings.closeIcon + ', .' + _this.settings.overlay, function () {
+ _this.modalClose();
+ });
+ // Prevent clicks within the modal window from closing the entire thing
+ $(document).on('click.'+pluginName, '.' + _this.settings.modalWindow, function (e) {
+ e.stopPropagation();
+ });
+ // Close modal when escape key is pressed
+ $(document).on('keyup.'+pluginName, function (e) {
+ if (e.keyCode === 27) {
+ _this.modalClose();
+ }
+ });
+ },
+
+ /**
+ * Open and select the location closest to the origin
+ *
+ * @param nearestLoc {Object} Details for the nearest location
+ * @param infowindow {Object} Info window object
+ * @param storeStart {number} Starting point of current page when pagination is enabled
+ * @param page {number} Current page number when pagination is enabled
+ */
+ openNearestLocation: function(nearestLoc, infowindow, storeStart, page) {
+ this.writeDebug('openNearestLocation',arguments);
+
+ if (
+ this.settings.openNearest !== true ||
+ typeof nearestLoc === 'undefined' ||
+ typeof originalOrigin === 'undefined' ||
+ (this.settings.fullMapStart === true && firstRun === true && this.settings.querystringParams === false) ||
+ (this.settings.defaultLoc === true && firstRun === true && this.settings.querystringParams === false)
+ ) {
+ return;
+ }
+
+ var _this = this;
+
+ // Callback
+ if (_this.settings.callbackNearestLoc) {
+ _this.settings.callbackNearestLoc.call(this, _this.map, nearestLoc, infowindow, storeStart, page);
+ }
+
+ var markerId = (nearestLoc.hasOwnProperty('markerid')) ? nearestLoc.markerid : 0;
+ var selectedMarker = markers[markerId];
+
+ _this.createInfowindow(selectedMarker, 'left', infowindow, storeStart, page);
+
+ // Scroll list to selected marker
+ var $container = $('.' + _this.settings.locationList);
+ var $selectedLocation = $('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']');
+
+ // Focus on the list
+ $('.' + _this.settings.locationList + ' li').removeClass('list-focus');
+ $selectedLocation.addClass('list-focus');
+
+ $container.animate({
+ scrollTop: $selectedLocation.offset().top - $container.offset().top + $container.scrollTop()
+ });
+ },
+
+ /**
+ * Handle clicks from the location list
+ *
+ * @param map {Object} Google map
+ * @param infowindow {Object} Info window object
+ * @param storeStart {number} Starting point of current page when pagination is enabled
+ * @param page {number} Current page number when pagination is enabled
+ */
+ listClick: function(map, infowindow, storeStart, page) {
+ this.writeDebug('listClick',arguments);
+ var _this = this;
+
+ $(document).on('click.' + pluginName, '.' + _this.settings.locationList + ' li', function () {
+ var markerId = $(this).data('markerid');
+ var selectedMarker = markers[markerId];
+
+ // List click callback
+ if (_this.settings.callbackListClick) {
+ _this.settings.callbackListClick.call(this, markerId, selectedMarker, locationset[markerId], map);
+ }
+
+ if (_this.useLegacyMarkers()) {
+ map.panTo(selectedMarker.getPosition());
+ } else {
+ map.panTo(selectedMarker.position);
+ }
+
+ var listLoc = 'left';
+ _this.createInfowindow(selectedMarker, listLoc, infowindow, storeStart, page);
+
+ // Custom selected marker override
+ if (_this.settings.selectedMarkerImg !== null) {
+ _this.changeSelectedMarker(selectedMarker);
+ }
+
+ // Focus on the list
+ $('.' + _this.settings.locationList + ' li').removeClass('list-focus');
+ $('.' + _this.settings.locationList + ' li[data-markerid=' + markerId + ']').addClass('list-focus');
+ });
+
+ // Prevent bubbling from list content links
+ $(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' li a', function(e) {
+ e.stopPropagation();
+ });
+ },
+
+ /**
+ * Output total results count if HTML element with .bh-sl-total-results class exists
+ *
+ * @param locCount
+ */
+ resultsTotalCount: function(locCount) {
+ this.writeDebug('resultsTotalCount',arguments);
+
+ var $resultsContainer = $('.bh-sl-total-results');
+
+ if (typeof locCount === 'undefined' || locCount <= 0 || $resultsContainer.length === 0) {
+ return;
+ }
+
+ $resultsContainer.text(locCount);
+ },
+
+ /**
+ * Inline directions setup
+ *
+ * @param map {Object} Google map
+ * @param origin {string} Origin address
+ */
+ inlineDirections: function(map, origin) {
+ this.writeDebug('inlineDirections',arguments);
+
+ if (this.settings.inlineDirections !== true || typeof origin === 'undefined') {
+ return;
+ }
+
+ var _this = this;
+
+ // Open directions
+ $(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' li .loc-directions a', function (e) {
+ e.preventDefault();
+ var locID = $(this).closest('li').attr('data-markerid');
+ _this.directionsRequest(origin, parseInt(locID), map);
+
+ // Close directions
+ $(document).on('click.'+pluginName, '.' + _this.settings.locationList + ' .bh-sl-close-icon', function () {
+ _this.closeDirections();
+ });
+ });
+ },
+
+ /**
+ * Visible markers list setup
+ *
+ * @param map {Object} Google map
+ * @param markers {Object} Map markers
+ */
+ visibleMarkersList: function(map, markers) {
+ this.writeDebug('visibleMarkersList',arguments);
+
+ if (this.settings.visibleMarkersList !== true) {
+ return;
+ }
+
+ var _this = this;
+
+ // Add event listener to filter the list when the map is fully loaded
+ google.maps.event.addListenerOnce(map, 'idle', function(){
+ _this.checkVisibleMarkers(markers, map);
+ });
+
+ // Add event listener for center change
+ google.maps.event.addListener(map, 'center_changed', function() {
+ _this.checkVisibleMarkers(markers, map);
+ });
+
+ // Add event listener for zoom change
+ google.maps.event.addListener(map, 'zoom_changed', function() {
+ _this.checkVisibleMarkers(markers, map);
+ });
+ },
+
+ /**
+ * Restrict featured locations from displaying in results by a specific distance
+ *
+ * @returns {Array}
+ */
+ featuredDistanceRestriction: function() {
+ this.writeDebug('featuredDistanceRestriction',arguments);
+ var _this = this;
+
+ featuredset = $.grep(featuredset, function (val) {
+
+ if (val.hasOwnProperty('distance')) {
+ return parseFloat(val.distance) <= parseFloat(_this.settings.featuredDistance);
+ }
+ });
+
+ return featuredset;
+ },
+
+ /**
+ * Restrict featured locations by distance.
+ *
+ * @returns {Array}
+ */
+ featuredRestrictions: function(mappingObject) {
+ this.writeDebug('featuredRestrictions',arguments);
+
+ if (this.settings.featuredDistance === null) {
+ return featuredset;
+ }
+
+ // Featured locations radius restriction.
+ if (this.settings.featuredDistance !== null) {
+ featuredset = this.featuredDistanceRestriction(mappingObject);
+ }
+
+ return featuredset;
+ },
+
+ /**
+ * The primary mapping function that runs everything
+ *
+ * @param mappingObject {Object} all the potential mapping properties - latitude, longitude, origin, name, max
+ * distance, page
+ */
+ mapping: function (mappingObject) {
+ this.writeDebug('mapping',arguments);
+ var _this = this;
+ var orig_lat, orig_lng, geocodeData, origin, originPoint, page;
+ if (!this.isEmptyObject(mappingObject)) {
+ orig_lat = mappingObject.lat;
+ orig_lng = mappingObject.lng;
+ geocodeData = mappingObject.geocodeResult;
+ origin = mappingObject.origin;
+ page = mappingObject.page;
+ }
+
+ // Set the initial page to zero if not set
+ if ( _this.settings.pagination === true ) {
+ if (typeof page === 'undefined' || originalOrigin !== addressInput ) {
+ page = 0;
+ }
+
+ paginationPage = page;
+ }
+
+ // Override page if the query string was set.
+ var queryStringPage = _this.getQueryString('bhsl-page');
+ if (queryStringPage !== '') {
+ page = paginationPage = parseInt(queryStringPage) - 1;
+ }
+
+ // Data request
+ if (typeof origin === 'undefined' && this.settings.nameSearch === true) {
+ dataRequest = _this._getData();
+ }
+ else {
+ // Set up the origin point
+ originPoint = new google.maps.LatLng(orig_lat, orig_lng);
+
+ // If the origin hasn't changed use the existing data, so we aren't making unneeded AJAX requests
+ if ((typeof originalOrigin !== 'undefined') && (origin === originalOrigin) && (typeof originalData !== 'undefined') && this.settings.pagination !== true) {
+ origin = originalOrigin;
+ dataRequest = originalData;
+ }
+ else {
+ // Do the data request - doing this in mapping so the lat/lng and address can be passed over and used if needed
+ dataRequest = _this._getData(olat, olng, origin, geocodeData, mappingObject);
+ }
+ }
+
+ // Check filters here to handle selected filtering after page reload
+ if (_this.settings.taxonomyFilters !== null && _this.hasEmptyObjectVals(filters)) {
+ _this.checkFilters();
+ }
+ /**
+ * Process the location data
+ */
+ // Raw data
+ if ( _this.settings.dataRaw !== null ) {
+ _this.processData(mappingObject, originPoint, dataRequest, page);
+ }
+ // Remote data
+ else {
+ dataRequest.done(function (data) {
+ _this.processData(mappingObject, originPoint, data, page);
+ });
+ }
+ },
+
+ /**
+ * Reset disabled form fields
+ */
+ resetDisabledFilterVals: function() {
+ this.writeDebug('resetDisabledFilterVals');
+
+ for (var taxKey in this.settings.taxonomyFilters) {
+ if (this.settings.taxonomyFilters.hasOwnProperty(taxKey)) {
+ for (var x = 0; x < this.settings.taxonomyFilters[taxKey].length; x++) {
+ $('#' + this.settings.taxonomyFilters[taxKey] + ' input,option').each(function () {
+ var disabled = $(this).attr('disabled');
+
+ if (typeof disabled !== 'undefined') {
+ $(this).removeAttr('disabled');
+ }
+ });
+ }
+ }
+ }
+ },
+
+ /**
+ * Get available filter values
+ *
+ * @param callback
+ */
+ getAvailableFilters: function(callback) {
+ this.writeDebug('getAvailableFilters');
+ var availableValues = [];
+
+ for (var location in locationset) {
+ if (locationset.hasOwnProperty(location)) {
+ // Loop through the location values.
+ for (var locationKey in locationset[location]) {
+ if (filters.hasOwnProperty(locationKey) && locationset[location][locationKey] !== '') {
+ if (availableValues.hasOwnProperty(locationKey)) {
+ var availableVal = availableValues[locationKey].concat(',', locationset[location][locationKey].replace(', ', ',').trim());
+ availableValues[locationKey] = Array.from(new Set(availableVal.split(','))).toString();
+ } else {
+ availableValues[locationKey] = locationset[location][locationKey].replace(', ', ',').trim();
+ }
+ }
+ }
+ }
+ }
+
+ // Account for missing filter properties in location set.
+ for (var keyName in filters) {
+ if (!availableValues.hasOwnProperty(keyName)) {
+ availableValues[keyName] = '';
+ }
+ }
+
+ callback(availableValues);
+ },
+
+ /**
+ * Disable input fields that aren't available within the current location set
+ */
+ maybeDisableFilterOptions: function() {
+ this.writeDebug('maybeDisableFilterOptions');
+ var availableValues = [];
+ var _this = this;
+
+ // Initially reset any input/option fields that were previously disabled.
+ this.resetDisabledFilterVals();
+
+ // Loop through current location set to determine what filter values are still available.
+ this.getAvailableFilters( function(values) {
+ availableValues = values;
+
+ // Save the original filter values for reference.
+ if (typeof originalFilterVals === 'undefined') {
+ originalFilterVals = availableValues;
+ }
+
+ // Update input and option fields to disabled if they're not available.
+ for (var key in _this.settings.taxonomyFilters) {
+ if (_this.settings.taxonomyFilters.hasOwnProperty(key)) {
+
+ // Loop through the taxonomy filter group items.
+ for (var i = 0; i < _this.settings.taxonomyFilters[key].length; i++) {
+ if (_this.settings.taxonomyFilters.hasOwnProperty(key)) {
+ $('#' + _this.settings.taxonomyFilters[key] + ' input, #' + _this.settings.taxonomyFilters[key] + ' option').each(function () {
+
+ // Initial determination of values that should be disabled.
+ if ($(this).val() !== '' && ! Array.from(new Set(availableValues[key].split(','))).includes($(this).val())) {
+ if (! disabledFilterVals.hasOwnProperty(key)) {
+ disabledFilterVals[key] = [];
+ }
+
+ // Handle select options and radio button values when there is no address input.
+ if (
+ (typeof addressInput === 'undefined' || addressInput === '') &&
+ ($(this).prop('tagName') === 'OPTION' || $(this).prop('type') === 'radio') &&
+ _this.hasSingleGroupFilterVal(filters, key) &&
+ Array.from(new Set(originalFilterVals[key].split(','))).includes($(this).val())
+ ) {
+ return;
+ }
+
+ // Handle select options and radio button values when there is address input.
+ if (
+ (typeof addressInput !== 'undefined' || addressInput !== '') &&
+ ($(this).prop('tagName') === 'OPTION' || $(this).prop('type') === 'radio') &&
+ _this.hasSingleGroupFilterVal(filters, key) &&
+ Array.from(new Set(originalFilterVals[key].split(','))).includes($(this).val()) &&
+ _this.countFilters() === 1
+ ) {
+ return;
+ }
+
+ // Keep select options and radio button available values after one filter has been selected.
+ if (
+ ($(this).prop('tagName') === 'OPTION' || $(this).prop('type') === 'radio') &&
+ _this.hasSingleGroupFilterVal(filters, key) &&
+ _this.countFilters() > 1 &&
+ Array.from(new Set(originalFilterVals[key].split(','))).includes($(this).val()) &&
+ ! disabledFilterVals[key].includes($(this).val())
+ ) {
+ return;
+ }
+
+ // Track disabled values.
+ if (
+ disabledFilterVals.hasOwnProperty(key) &&
+ Array.isArray(disabledFilterVals[key]) &&
+ ! disabledFilterVals[key].includes($(this).val())
+ ) {
+ disabledFilterVals[key].push($(this).val());
+ }
+
+ $(this).attr('disabled', true);
+ }
+ });
+ }
+ }
+ }
+ }
+ });
+ },
+
+ /**
+ * Processes the location data
+ *
+ * @param mappingObject {Object} all the potential mapping properties - latitude, longitude, origin, name, max
+ * distance, page
+ * @param originPoint {Object} LatLng of origin point
+ * @param data {Object} location data
+ * @param page {number} current page number
+ */
+ processData: function (mappingObject, originPoint, data, page) {
+ this.writeDebug('processData',arguments);
+ var _this = this;
+ var i = 0;
+ var orig_lat, orig_lng, origin, name, maxDistance, marker, bounds, storeStart, storeNumToShow, myOptions, distError, openMap, infowindow, nearestLoc;
+ var taxFilters = {};
+ var $lengthSwap = $('#' + _this.settings.lengthSwapID);
+
+ if (!this.isEmptyObject(mappingObject)) {
+ orig_lat = mappingObject.lat;
+ orig_lng = mappingObject.lng;
+ origin = mappingObject.origin;
+ name = mappingObject.name;
+ maxDistance = mappingObject.distance;
+ }
+
+ var $mapDiv = $('#' + _this.settings.mapID);
+ // Get the length unit
+ var distUnit = (_this.settings.lengthUnit === 'km') ? _this.settings.kilometersLang : _this.settings.milesLang;
+
+ // Save data and origin separately so we can potentially avoid multiple AJAX requests
+ originalData = dataRequest;
+ if ( typeof origin !== 'undefined' ) {
+ originalOrigin = origin;
+ }
+
+ // Callback
+ if (_this.settings.callbackSuccess) {
+ _this.settings.callbackSuccess.call(this, mappingObject, originPoint, data, page);
+ }
+
+ openMap = $mapDiv.hasClass('bh-sl-map-open');
+
+ // Set a variable for fullMapStart, so we can detect the first run
+ if (
+ (_this.settings.fullMapStart === true && openMap === false) ||
+ (_this.settings.autoGeocode === true && openMap === false) ||
+ (_this.settings.defaultLoc === true && openMap === false)
+ ) {
+ firstRun = true;
+ } else if (
+ (_this.settings.fullMapStart === true && reload === true) ||
+ (_this.settings.autoGeocode === true && reload === true) ||
+ (_this.settings.defaultLoc === true && reload === true)
+ ) {
+ _this.reset();
+ } else {
+ _this.reset();
+ }
+
+ $mapDiv.addClass('bh-sl-map-open');
+
+ // Process the location data depending on the data format type
+ if (_this.settings.dataType === 'json' || _this.settings.dataType === 'jsonp') {
+
+ // Process JSON
+ for(var x = 0; i < data.length; x++){
+ var obj = data[x];
+ var locationData = {};
+
+ // Parse each data variable
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ locationData[key] = obj[key];
+ }
+ }
+
+ _this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
+
+ i++;
+ }
+ }
+ else if (_this.settings.dataType === 'kml') {
+ // Process KML
+ $(data).find('Placemark').each(function () {
+ var locationData = {
+ 'name' : $(this).find('name').text(),
+ 'lat' : $(this).find('coordinates').text().split(',')[1],
+ 'lng' : $(this).find('coordinates').text().split(',')[0],
+ 'description': $(this).find('description').text()
+ };
+
+ _this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
+
+ i++;
+ });
+ }
+ else {
+ // Process XML
+ $(data).find(_this.settings.xmlElement).each(function () {
+ var locationData = {};
+
+ for (var key in this.attributes) {
+ if (this.attributes.hasOwnProperty(key)) {
+ locationData[this.attributes[key].name] = this.attributes[key].value;
+ }
+ }
+
+ _this.locationsSetup(locationData, orig_lat, orig_lng, origin, maxDistance);
+
+ i++;
+ });
+ }
+
+ // Name search - using taxonomy filter to handle
+ if (_this.settings.nameSearch === true) {
+ if (typeof searchInput !== 'undefined' && '' !== searchInput) {
+
+ if (_this.settings.nameAttribute.indexOf(',')) {
+ nameAttrs = _this.settings.nameAttribute.split(',');
+
+ // Multiple name attributes should swap to exclusive filtering.
+ if (_this.settings.exclusiveTax !== null) {
+ _this.settings.exclusiveTax.concat(nameAttrs);
+ } else {
+ _this.settings.exclusiveTax = nameAttrs;
+ }
+
+ for (var a = 0; a < nameAttrs.length; a++) {
+ filters[nameAttrs[a].trim()] = [searchInput];
+ }
+ } else {
+ filters[_this.settings.nameAttribute] = [searchInput];
+ }
+ }
+
+ // Check for a previous value.
+ if (
+ typeof searchInput !== 'undefined' &&
+ '' === searchInput
+ ) {
+ if (typeof nameAttrs !== 'undefined') {
+ for (var pa = 0; pa < nameAttrs.length; pa++) {
+ if (nameAttrs[pa] in filters) {
+ delete filters[nameAttrs[pa]];
+ }
+ }
+ } else {
+ delete filters[_this.settings.nameAttribute];
+ }
+ }
+ }
+
+ // Taxonomy filtering setup
+ if (_this.settings.taxonomyFilters !== null || _this.settings.nameSearch === true) {
+
+ for(var k in filters) {
+ if (filters.hasOwnProperty(k) && filters[k].length > 0) {
+ // Let's use regex
+ for (var z = 0; z < filters[k].length; z++) {
+ // Creating a new object so we don't mess up the original filters
+ if (!taxFilters[k]) {
+ taxFilters[k] = [];
+ }
+
+ // Swap pattern matching depending on name search vs. taxonomy filtering.
+ if (typeof nameAttrs !== 'undefined') {
+ if (nameAttrs.indexOf(k) !== -1) {
+ taxFilters[k][z] = '(?:^|\\s)' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '');
+ } else {
+ taxFilters[k][z] = '(?=.*' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '') + '(?!\\s))';
+ }
+ } else {
+ if (k === _this.settings.nameAttribute) {
+ taxFilters[k][z] = '(?:^|\\s)' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '');
+ } else {
+ taxFilters[k][z] = '(?=.*' + filters[k][z].replace(/([.*+?^=!:${}()|\[\]\/\\]|&\s+)/g, '') + '(?!\\s))';
+ }
+ }
+ }
+ }
+ }
+
+ // Filter the data
+ if (!_this.isEmptyObject(taxFilters)) {
+ locationset = $.grep(locationset, function (val) {
+ return _this.filterData(val, taxFilters);
+ });
+ }
+ }
+
+ // Sorting
+ if (_this.settings.sortBy !== null && typeof _this.settings.sortBy === 'object') {
+
+ // Sort the multi-dimensional array by distance to get the nearest location first when enabled
+ if (_this.settings.openNearest === true && typeof originalOrigin !== 'undefined') {
+ this.sortNumerically(locationset, true);
+
+ // Save the closest location to a variable for openNearest setting
+ if (typeof locationset[0] !== 'undefined') {
+
+ if (this.settings.sortBy.hasOwnProperty('order') && this.settings.sortBy.order.toString() === 'desc') {
+ nearestLoc = locationset[locationset.length - 1];
+ } else {
+ nearestLoc = locationset[0];
+ }
+ }
+ }
+
+ // Custom sorting
+ _this.sortCustom(locationset);
+ } else {
+ // Sort the multi-dimensional array by distance
+ if (typeof origin !== 'undefined') {
+ _this.sortNumerically(locationset);
+ }
+
+ // Check the closest marker
+ if (_this.isEmptyObject(taxFilters)) {
+ if (_this.settings.maxDistance === true && maxDistance) {
+ if (typeof locationset[0] === 'undefined' || locationset[0].distance > maxDistance) {
+ _this.notify(_this.settings.distanceErrorAlert + maxDistance + ' ' + distUnit);
+ }
+ }
+ else {
+ if (typeof locationset[0] !== 'undefined') {
+ if (
+ _this.settings.distanceAlert !== -1 &&
+ locationset[0].distance > _this.settings.distanceAlert &&
+ (typeof paginationPage === 'undefined' || parseInt(paginationPage) === 1)
+ ) {
+ _this.notify(_this.settings.distanceErrorAlert + _this.settings.distanceAlert + ' ' + distUnit);
+ distError = true;
+ }
+ }
+ else {
+ _this.emptyResult();
+ throw new Error('No locations found. Please check the dataLocation setting and path.');
+ return;
+ }
+ }
+ }
+
+ // Save the closest location to a variable for openNearest setting
+ if (typeof locationset[0] !== 'undefined') {
+ nearestLoc = locationset[0];
+ }
+ }
+
+ // Featured locations filtering
+ if (_this.settings.featuredLocations === true) {
+
+ // Create array for featured locations
+ featuredset = $.grep(locationset, function (val) {
+ if (val.hasOwnProperty('featured')) {
+ return val.featured === 'true';
+ }
+ });
+
+ // Featured location restrictions.
+ featuredset = _this.featuredRestrictions(mappingObject);
+
+ // Create array for normal locations
+ normalset = $.grep(locationset, function (val) {
+ if (val.hasOwnProperty('featured')) {
+ return val.featured !== 'true';
+ }
+ });
+
+ // Combine the arrays
+ locationset = [];
+ locationset = featuredset.concat(normalset);
+ }
+
+ // Disable filter inputs if there are no locations with the values left.
+ if (
+ (firstRun !== true && _this.settings.exclusiveFiltering === false) ||
+ (_this.settings.fullMapStart === true && _this.settings.exclusiveFiltering === false) ||
+ (_this.settings.defaultLoc === true && _this.settings.exclusiveFiltering === false)
+ ) {
+ _this.maybeDisableFilterOptions();
+ }
+
+ // Slide in the map container
+ if (_this.settings.slideMap === true) {
+ $this.slideDown();
+ }
+
+ // Output page numbers if pagination setting is true
+ if (_this.settings.pagination === true) {
+ _this.paginationSetup(page);
+ }
+
+ // Alternative method to display no results if locations are too far away instead of all locations.
+ if (_this.settings.altDistanceNoResult === true && nearestLoc.distance > _this.settings.distanceAlert) {
+ _this.emptyResult();
+ return;
+ }
+
+ // Handle no results
+ if (_this.isEmptyObject(locationset) || locationset[0].result === 'none') {
+ _this.emptyResult();
+ return;
+ }
+
+ // Set up the modal window
+ _this.modalWindow();
+
+ // Avoid error if number of locations is less than the default of 26
+ 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))) {
+ storeNum = locationset.length;
+ }
+ else {
+ storeNum = _this.settings.storeLimit;
+ }
+
+ // If fullMapStart is enabled and taxFilters is reset and name search and origin are empty, swap back to the original length.
+ if (
+ _this.settings.fullMapStart === true &&
+ _this.isEmptyObject(taxFilters) &&
+ (searchInput === '' || typeof searchInput === 'undefined') &&
+ (addressInput === '' || typeof addressInput === 'undefined')
+ ) {
+ storeNum = locationset.length;
+ }
+
+ // If pagination is on, change the store limit to the setting and slice the locationset array
+ if (_this.settings.pagination === true) {
+ storeNumToShow = _this.settings.locationsPerPage;
+ storeStart = page * _this.settings.locationsPerPage;
+
+ if ( (storeStart + storeNumToShow) > locationset.length ) {
+ storeNumToShow = _this.settings.locationsPerPage - ((storeStart + storeNumToShow) - locationset.length);
+ }
+
+ locationset = locationset.slice(storeStart, storeStart + storeNumToShow);
+ storeNum = locationset.length;
+ }
+ else {
+ storeNumToShow = storeNum;
+ storeStart = 0;
+ }
+
+ // Output location results count
+ _this.resultsTotalCount(locationset.length);
+
+ // Google maps settings
+ if (
+ (_this.settings.fullMapStart === true && firstRun === true && _this.settings.querystringParams !== true) ||
+ (_this.settings.mapSettings.zoom === 0) ||
+ (typeof origin === 'undefined') ||
+ (distError === true) ||
+ ((_this.settings.maxDistance === true && firstRun === false) && this.countFilters() > 0)
+ ) {
+ myOptions = _this.settings.mapSettings;
+ bounds = new google.maps.LatLngBounds();
+ }
+ else if (_this.settings.pagination === true) {
+ // Update the map to focus on the first point in the new set
+ var nextPoint = new google.maps.LatLng(locationset[0].lat, locationset[0].lng);
+
+ if (page === 0) {
+ _this.settings.mapSettings.center = originPoint;
+ myOptions = _this.settings.mapSettings;
+ }
+ else {
+ _this.settings.mapSettings.center = nextPoint;
+ myOptions = _this.settings.mapSettings;
+ }
+ }
+ else {
+ _this.settings.mapSettings.center = originPoint;
+ myOptions = _this.settings.mapSettings;
+ }
+
+ // Create the map
+ _this.map = new google.maps.Map(document.getElementById(_this.settings.mapID), myOptions);
+
+ // Re-center the map when the browser is re-sized
+ window.addEventListener('resize', function() {
+ var center = _this.map.getCenter();
+ google.maps.event.trigger(_this.map, 'resize');
+ _this.map.setCenter(center);
+ });
+
+ // Add map drag listener if setting is enabled and re-search on drag end
+ if (_this.settings.dragSearch === true ) {
+ _this.map.addListener('dragend', function() {
+ _this.dragSearch(_this.map);
+ });
+ }
+
+ // Load the map
+ $this.data(_this.settings.mapID.replace('#', ''), _this.map);
+
+ // Map set callback.
+ if (_this.settings.callbackMapSet) {
+ _this.settings.callbackMapSet.call(this, _this.map, originPoint, originalZoom, myOptions);
+ }
+
+ // Initialize the infowindow
+ if ( typeof InfoBubble !== 'undefined' && _this.settings.infoBubble !== null ) {
+ var infoBubbleSettings = _this.settings.infoBubble;
+ infoBubbleSettings.map = _this.map;
+
+ infowindow = new InfoBubble(infoBubbleSettings);
+ } else {
+ infowindow = new google.maps.InfoWindow();
+ }
+
+ // Add origin marker if the setting is set
+ _this.originMarker(_this.map, origin, originPoint);
+
+ // Handle pagination
+ $(document).on('click.'+pluginName, '.bh-sl-pagination li a', function (e) {
+ e.preventDefault();
+ // Run paginationChange
+ _this.paginationChange($(this).parent().attr('data-page'));
+ });
+
+ // Inline directions
+ _this.inlineDirections(_this.map, origin);
+
+ // Add markers and infowindows loop
+ for (var y = 0; y <= storeNumToShow - 1; y++) {
+ var letter = '';
+
+ if (page > 0) {
+ letter = String.fromCharCode('A'.charCodeAt(0) + (storeStart + y));
+ }
+ else {
+ letter = String.fromCharCode('A'.charCodeAt(0) + y);
+ }
+
+ var point = new google.maps.LatLng(locationset[y].lat, locationset[y].lng);
+ marker = _this.createMarker(point, locationset[y].name, locationset[y].address, letter, _this.map, locationset[y].category);
+
+ if (_this.useLegacyMarkers()) {
+ marker.set('id', y);
+ } else {
+ marker.bhslID = y;
+ }
+
+ markers[y] = marker;
+
+ // Add marker ID to location data
+ if (_this.useLegacyMarkers()) {
+ locationset[y].markerid = marker.get('id');
+ } else {
+ locationset[y].markerid = marker.bhslID;
+ }
+
+ if (this.settings.dataRaw !== null) {
+ for (var l = 0; l < this.settings.dataRaw.length; l++) {
+ if (this.settings.dataRaw[l] && this.settings.dataRaw[l].hasOwnProperty('id') && this.settings.dataRaw[l].id === locationset[y].id) {
+ this.settings.dataRaw[l].markerid = locationset[y].markerid;
+ }
+ }
+ }
+
+ if (
+ (_this.settings.fullMapStart === true && firstRun === true && _this.settings.querystringParams !== true) ||
+ (_this.settings.mapSettings.zoom === 0) ||
+ (typeof origin === 'undefined') ||
+ (distError === true) ||
+ ((_this.settings.maxDistance === true && firstRun === false) && this.countFilters() > 0)
+ ) {
+ bounds.extend(point);
+ }
+ // Pass variables to the pop-up infowindows
+ _this.createInfowindow(marker, null, infowindow, storeStart, page);
+ }
+
+ // Center and zoom if no origin or zoom was provided, or distance of first marker is greater than distanceAlert
+ if (
+ (_this.settings.fullMapStart === true && firstRun === true && _this.settings.querystringParams !== true) ||
+ (_this.settings.mapSettings.zoom === 0) ||
+ (typeof origin === 'undefined') ||
+ (distError === true) ||
+ ((_this.settings.maxDistance === true && firstRun === false) && this.countFilters() > 0)
+ ) {
+ _this.map.fitBounds(bounds);
+
+ // Prevent zooming in too far after fitBounds
+ var zoomListener = google.maps.event.addListener(_this.map, 'idle', function() {
+ if (_this.map.getZoom() > 16) {
+ _this.map.setZoom(16);
+ }
+ google.maps.event.removeListener(zoomListener);
+ });
+ }
+
+ // Create the links that focus on the related marker
+ var locList = $('.' + _this.settings.locationList + ' ul');
+ locList.empty();
+
+ // Set up the location list markup
+ if (
+ firstRun &&
+ _this.settings.fullMapStartListLimit !== false &&
+ !isNaN(_this.settings.fullMapStartListLimit) &&
+ _this.settings.fullMapStartListLimit !== -1 &&
+ markers.length > _this.settings.fullMapStartListLimit
+ ) {
+ for (var m = 0; m < _this.settings.fullMapStartListLimit; m++) {
+ var currentMarker = markers[m];
+ _this.listSetup(currentMarker, storeStart, page);
+ }
+ } else {
+ $(markers).each(function (x) {
+ var currentMarker = markers[x];
+ _this.listSetup(currentMarker, storeStart, page);
+ });
+ }
+
+ // Length unit swap setup
+ if ($lengthSwap.length) {
+ _this.lengthUnitSwap($lengthSwap);
+
+ $lengthSwap.on('change.'+pluginName, function (e) {
+ e.stopPropagation();
+ _this.lengthUnitSwap($lengthSwap);
+ });
+ }
+
+ // Open nearest location.
+ _this.openNearestLocation(nearestLoc, infowindow, storeStart, page);
+
+ // MarkerClusterer setup.
+ if (_this.useLegacyMarkers()) {
+ if ( typeof MarkerClusterer !== 'undefined' && _this.settings.markerCluster !== null ) {
+ var markerCluster = new MarkerClusterer(_this.map, markers, _this.settings.markerCluster);
+ }
+ } else {
+ if ( typeof markerClusterer !== 'undefined' ) {
+ var customClustererParams = _this.settings.markerCluster;
+
+ new markerClusterer.MarkerClusterer({
+ markers,
+ map: _this.map,
+ customClustererParams
+ });
+ }
+ }
+
+ // Handle clicks from the list
+ _this.listClick(_this.map, infowindow, storeStart, page);
+
+ // Add the list li background colors - this wil be dropped in a future version in favor of CSS
+ $('.' + _this.settings.locationList + ' ul > li:even').css('background', _this.settings.listColor1);
+ $('.' + _this.settings.locationList + ' ul > li:odd').css('background', _this.settings.listColor2);
+
+ // Visible markers list
+ _this.visibleMarkersList(_this.map, markers);
+
+ // Fill in form values from query string parameters.
+ if (_this.settings.querystringParams === true) {
+ var $addressInput = $('#' + _this.settings.addressID);
+ var $searchInput = $('#' + _this.settings.searchID);
+
+ // Address field.
+ if (typeof mappingObj !== 'undefined' && mappingObj.hasOwnProperty('origin') && $addressInput.val() === '') {
+ $addressInput.val(mappingObj.origin);
+ }
+
+ // Name search field.
+ if (typeof mappingObj !== 'undefined' && mappingObj.hasOwnProperty('name') && $searchInput.val() === '') {
+ $searchInput.val(mappingObj.name);
+ }
+ }
+
+ // Modal ready callback
+ if (_this.settings.modal === true && _this.settings.callbackModalReady) {
+ _this.settings.callbackModalReady.call(this, mappingObject);
+ }
+
+ // Filters callback
+ if (_this.settings.callbackFilters) {
+ _this.settings.callbackFilters.call(this, filters, mappingObject);
+ }
+ },
+
+ /**
+ * console.log helper function
+ *
+ * http://www.briangrinstead.com/blog/console-log-helper-function
+ */
+ writeDebug: function () {
+ if (window.console && this.settings.debug) {
+ // Only run on the first time through - reset this function to the appropriate console.log helper
+ if (Function.prototype.bind) {
+ this.writeDebug = Function.prototype.bind.call(console.log, console, 'StoreLocator :');
+ } else {
+ this.writeDebug = function () {
+ arguments[0] = 'StoreLocator : ' + arguments[0];
+ Function.prototype.apply.call(console.log, console, arguments);
+ };
+ }
+ this.writeDebug.apply(this, arguments);
+ }
+ }
+
+ });
+
+ // A really lightweight plugin wrapper around the constructor,
+ // preventing against multiple instantiations and allowing any
+ // public function (ie. a function whose name doesn't start
+ // with an underscore) to be called via the jQuery plugin,
+ // e.g. $(element).defaultPluginName('functionName', arg1, arg2)
+ $.fn[ pluginName ] = function (options) {
+ var args = arguments;
+ // Is the first parameter an object (options), or was omitted, instantiate a new instance of the plugin
+ if (options === undefined || typeof options === 'object') {
+ return this.each(function () {
+ // Only allow the plugin to be instantiated once, so we check that the element has no plugin instantiation yet
+ if (!$.data(this, 'plugin_' + pluginName)) {
+ // 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.
+ $.data(this, 'plugin_' + pluginName, new Plugin( this, options ));
+ }
+ });
+ // Treat this as a call to a public method
+ } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
+ // Cache the method call to make it possible to return a value
+ var returns;
+
+ this.each(function () {
+ var instance = $.data(this, 'plugin_' + pluginName);
+
+ // Tests that there's already a plugin-instance and checks that the requested public method exists
+ if (instance instanceof Plugin && typeof instance[options] === 'function') {
+
+ // Call the method of our plugin instance, and pass it the supplied arguments.
+ returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
+ }
+
+ // Allow instances to be destroyed via the 'destroy' method
+ if (options === 'destroy') {
+ $.data(this, 'plugin_' + pluginName, null);
+ }
+ });
+
+ // If the earlier cached method gives a value back return the value, otherwise return this to preserve chainability.
+ return returns !== undefined ? returns : this;
+ }
+ };
+
+
+})(jQuery, window, document);
diff --git a/src/templates/kml/kml-infowindow-description.html b/src/templates/kml/kml-infowindow-description.html
new file mode 100644
index 0000000..afababf
--- /dev/null
+++ b/src/templates/kml/kml-infowindow-description.html
@@ -0,0 +1,6 @@
+{{#location}}
+
{{name}}
+{{#if description}}
+
{{{description}}}
+{{/if}}
+{{/location}}
diff --git a/src/templates/kml/kml-location-list-description.html b/src/templates/kml/kml-location-list-description.html
new file mode 100644
index 0000000..aeaa7ec
--- /dev/null
+++ b/src/templates/kml/kml-location-list-description.html
@@ -0,0 +1,13 @@
+{{#location}}
+
+ {{marker}}
+
+
+
{{name}}
+ {{#if description}}
+
{{{description}}}
+ {{/if}}
+
+
+
+{{/location}}
diff --git a/src/templates/standard/infowindow-description.html b/src/templates/standard/infowindow-description.html
new file mode 100644
index 0000000..5981a26
--- /dev/null
+++ b/src/templates/standard/infowindow-description.html
@@ -0,0 +1,21 @@
+{{#location}}
+
{{name}}
+
{{address}}
+{{#if address2}}
+
{{address2}}
+{{/if}}
+
{{city}}{{#if city}},{{/if}} {{state}} {{postal}}
+{{#if hours1}}
+
{{hours1}}
+{{/if}}
+{{#if hours2}}
+
{{hours2}}
+{{/if}}
+{{#if hours3}}
+
{{hours3}}
+{{/if}}
+{{#if phone}}
+
{{phone}}
+{{/if}}
+
+{{/location}}
diff --git a/src/templates/standard/location-list-description.html b/src/templates/standard/location-list-description.html
new file mode 100644
index 0000000..ab1f455
--- /dev/null
+++ b/src/templates/standard/location-list-description.html
@@ -0,0 +1,26 @@
+{{#location}}
+
+ {{marker}}
+
+
+
{{name}}
+
{{address}}
+ {{#if address2}}
+
{{address2}}
+ {{/if}}
+
{{city}}{{#if city}},{{/if}} {{state}} {{postal}}
+ {{#if phone}}
+
{{phone}}
+ {{/if}}
+ {{#if web}}
+
+ {{/if}}
+ {{#if distance}}
+
{{distance}} {{length}}
+ {{#if altdistance}}
{{altdistance}} {{altlength}}
{{/if}}
+
+ {{/if}}
+
+
+
+{{/location}}
diff --git a/storelocator.jquery.json b/storelocator.jquery.json
index d1cb96b..980b344 100644
--- a/storelocator.jquery.json
+++ b/storelocator.jquery.json
@@ -2,12 +2,16 @@
"name": "storelocator",
"title": "jQuery Google Maps Store Locator",
"description": "This jQuery plugin takes advantage of Google Maps API version 3 to create an easy to implement store locator. No back-end programming is required, you just need to feed it KML, XML, or JSON data with all the location information.",
- "keywords": ["locator","store", "location", "locations", "maps", "map", "stores", "find"],
- "version": "1.4.9",
+ "keywords": ["jquery","locator","store","dealer","location", "locations", "maps", "map", "stores", "find"],
+ "version": "3.4.1",
"author": {
"name": "Bjorn Holine",
- "url": "http://www.bjornblog.com/"
+ "url": "https://www.bjornblog.com/"
},
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/bjorn2404/jQuery-Store-Locator-Plugin.git"
+ },
"licenses": [
{
"type": "MIT",
@@ -21,4 +25,4 @@
"jquery": ">=1.7",
"handlebars": ">=1.0"
}
-}
\ No newline at end of file
+}
diff --git a/templates/infowindow-description.html b/templates/infowindow-description.html
deleted file mode 100644
index c7b4f1e..0000000
--- a/templates/infowindow-description.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{{#location}}
-
{{name}}
-
{{address}}
-
{{address2}}
-
{{city}}, {{state}} {{postal}}
-
{{hours1}}
-
{{hours2}}
-
{{hours3}}
-
{{phone}}
-
-{{/location}}
\ No newline at end of file
diff --git a/templates/kml-infowindow-description.html b/templates/kml-infowindow-description.html
deleted file mode 100644
index bf408a2..0000000
--- a/templates/kml-infowindow-description.html
+++ /dev/null
@@ -1,4 +0,0 @@
-{{#location}}
-
{{name}}
-
{{{description}}}
-{{/location}}
\ No newline at end of file
diff --git a/templates/location-list-description.html b/templates/location-list-description.html
deleted file mode 100644
index 3893610..0000000
--- a/templates/location-list-description.html
+++ /dev/null
@@ -1,17 +0,0 @@
-{{#location}}
-
- {{marker}}
-
-
-
{{name}}
-
{{address}}
-
{{address2}}
-
{{city}}, {{state}} {{postal}}
-
{{phone}}
-
- {{#if distance}}
{{distance}} {{length}}
-
{{/if}}
-
-
-
-{{/location}}
\ No newline at end of file
diff --git a/test/.jshintrc b/test/.jshintrc
new file mode 100644
index 0000000..2de0248
--- /dev/null
+++ b/test/.jshintrc
@@ -0,0 +1,32 @@
+{
+ "curly": true,
+ "eqeqeq": true,
+ "immed": true,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "sub": true,
+ "undef": true,
+ "unused": true,
+ "boss": true,
+ "eqnull": true,
+ "browser": true,
+ "predef": [
+ "jQuery",
+ "QUnit",
+ "module",
+ "test",
+ "asyncTest",
+ "expect",
+ "start",
+ "stop",
+ "ok",
+ "equal",
+ "notEqual",
+ "deepEqual",
+ "notDeepEqual",
+ "strictEqual",
+ "notStrictEqual",
+ "throws"
+ ]
+}
\ No newline at end of file
diff --git a/test/storeLocator.html b/test/storeLocator.html
new file mode 100644
index 0000000..c6d9f83
--- /dev/null
+++ b/test/storeLocator.html
@@ -0,0 +1,43 @@
+
+
+
+
+
jQuery Google Maps Store Locator Plugin Test Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/storeLocator_test.js b/test/storeLocator_test.js
new file mode 100644
index 0000000..9de6b2d
--- /dev/null
+++ b/test/storeLocator_test.js
@@ -0,0 +1,234 @@
+(function($) {
+ /*
+ ======== A Handy Little QUnit Reference ========
+ http://api.qunitjs.com/
+
+ Test methods:
+ module(name, {[setup][ ,teardown]})
+ test(name, callback)
+ expect(numberOfAssertions)
+ stop(increment)
+ start(decrement)
+ Test assertions:
+ ok(value, [message])
+ equal(actual, expected, [message])
+ notEqual(actual, expected, [message])
+ deepEqual(actual, expected, [message])
+ notDeepEqual(actual, expected, [message])
+ strictEqual(actual, expected, [message])
+ notStrictEqual(actual, expected, [message])
+ throws(block, [expected], [message])
+ */
+
+ /**
+ * This file contains very minimal testing. I may or may not do more extensive tests -
+ * more or less playing around with it for the first time
+ */
+
+ module('storeLocator', {
+ // This will run before each test in this module.
+ setup: function() {
+ this.elems = $('#qunit-fixture').children();
+ }
+ });
+
+ test('is chainable', 1, function() {
+ // Not a bad test to run on collection methods.
+ strictEqual(this.elems.storeLocator({
+ 'infowindowTemplatePath': '../dist/assets/js/plugins/storeLocator/templates/infowindow-description.html',
+ 'listTemplatePath': '../dist/assets/js/plugins/storeLocator/templates/location-list-description.html'
+ }), this.elems, 'Should be chainable');
+ });
+
+
+ module('Independent methods', {
+ // This will run before each test in this module.
+ setup: function() {
+ $('#bh-sl-map-container').storeLocator({
+ 'infowindowTemplatePath': '../dist/assets/js/plugins/storeLocator/templates/infowindow-description.html',
+ 'listTemplatePath': '../dist/assets/js/plugins/storeLocator/templates/location-list-description.html',
+ 'taxonomyFilters': {
+ 'category' : 'category-filters-container1',
+ 'features' : 'category-filters-container2',
+ 'city' : 'city-filter',
+ 'postal': 'postal-filter'
+ }
+ });
+ },
+
+ teardown: function() {
+ $('#bh-sl-map-container').data('plugin_storeLocator').destroy();
+ }
+ });
+
+ /**
+ * Distance calculations
+ */
+ test('geoCodeCalcToRadian()', 3, function() {
+ var $this = $('#bh-sl-map-container').data('plugin_storeLocator');
+ var radiansPerDegree = Math.PI / 180;
+
+ deepEqual($this.geoCodeCalcToRadian(0), 0, 'Zero test');
+ deepEqual($this.geoCodeCalcToRadian(10), 10 * radiansPerDegree, 'Integer test');
+ deepEqual($this.geoCodeCalcToRadian(10.10), 10.10 * radiansPerDegree, 'Float test');
+ });
+
+ test('geoCodeCalcDiffRadian()', 2, function() {
+ var $this = $('#bh-sl-map-container').data('plugin_storeLocator');
+
+ deepEqual($this.geoCodeCalcDiffRadian(10, 5), ($this.geoCodeCalcToRadian(5) - $this.geoCodeCalcToRadian(10)), 'Integer test');
+ deepEqual($this.geoCodeCalcDiffRadian(10.10, 5.5), ($this.geoCodeCalcToRadian(5.5) - $this.geoCodeCalcToRadian(10.10)), 'Float test');
+ });
+
+ /**
+ * Query string
+ *
+ * URL needs to be set to /test/storeLocator.html?bh-sl-address=test for this to pass
+ */
+ test('getQueryString()', 2, function() {
+ var $this = $('#bh-sl-map-container').data('plugin_storeLocator');
+
+ deepEqual($this.getQueryString(), undefined, 'Empty test');
+ deepEqual($this.getQueryString('bh-sl-address'), 'test', 'String test');
+ });
+
+ /**
+ * Get data
+ */
+ asyncTest('getData', 1, function() {
+ var $this = $('#bh-sl-map-container').data('plugin_storeLocator');
+ var dataRequest = $this._getData('44.8896866', '-93.34994890000002', 'Edina,MN');
+
+ setTimeout(function() {
+ ok(dataRequest.done(), 'Data was called successfully');
+ start();
+ }, 1000);
+ });
+
+ /**
+ * Round number
+ */
+ test('roundNumber()', 2, function() {
+ var $this = $('#bh-sl-map-container').data('plugin_storeLocator');
+
+ deepEqual($this.roundNumber(2.345432, 0), 2, 'Round down test');
+ deepEqual($this.roundNumber(2.694928, 0), 3, 'Round up test');
+ });
+
+ /**
+ * Empty object test
+ */
+ test('isEmptyObject()', 2, function() {
+ var $this = $('#bh-sl-map-container').data('plugin_storeLocator');
+ var emptyObj = {};
+ var nonEmptyObj = {
+ 'test': 'testing'
+ };
+
+ deepEqual($this.isEmptyObject(emptyObj), true, 'Empty object test');
+ deepEqual($this.isEmptyObject(nonEmptyObj), false, 'Empty object fail test');
+ });
+
+ /**
+ * Empty object values test
+ */
+ test('hasEmptyObjectVals()', 3, function() {
+ var $this = $('#bh-sl-map-container').data('plugin_storeLocator');
+ var emptyObj = {};
+ var emptyObjVals = {
+ 'test1' : '',
+ 'test2' : ''
+ };
+ var nonEmptyObj = {
+ 'test': 'testing'
+ };
+
+ deepEqual($this.hasEmptyObjectVals(emptyObj), true, 'Empty object test');
+ deepEqual($this.hasEmptyObjectVals(emptyObjVals), true, 'Empty object vals test');
+ deepEqual($this.hasEmptyObjectVals(nonEmptyObj), false, 'Empty object fail test');
+ });
+
+
+ /**
+ * Sort locations (array of objects) numerically by distance test
+ */
+ test('sortNumerically()', 1, function() {
+ var $this = $('#bh-sl-map-container').data('plugin_storeLocator');
+ var positiveTest = [
+ { 'distance': 9 },
+ { 'distance': 2 },
+ { 'distance': 30 },
+ { 'distance': 5 }
+ ];
+ var positiveMatch = [
+ { 'distance': 2 },
+ { 'distance': 5 },
+ { 'distance': 9 },
+ { 'distance': 30 }
+ ];
+
+ $this.sortNumerically(positiveTest);
+
+ deepEqual(positiveTest, positiveMatch, 'Positive test');
+ });
+
+ /**
+ * Filter data test
+ */
+ test('filterData()', 2, function() {
+ var filter, filters, locationset, locationMatch, locationsMatch, inclusiveTest, exclusiveTest;
+ var $this = $('#bh-sl-map-container').data('plugin_storeLocator');
+
+ // Single filter
+ filter = {
+ 'city': ['Minneapolis']
+ };
+
+ // Multi-filter
+ filters = {
+ 'city': ['Minneapolis', 'St Paul']
+ };
+
+ locationset = [{
+ 'city': 'St. Paul',
+ 'state': 'MN'
+ }, {
+ 'city': 'Des Moines',
+ 'state': 'IA'
+ }, {
+ 'city': 'Minneapolis',
+ 'state': 'MN'
+ }];
+
+ locationMatch = [{
+ 'city': 'Minneapolis',
+ 'state': 'MN'
+ }];
+
+ locationsMatch = [{
+ 'city': 'St. Paul',
+ 'state': 'MN'
+ }, {
+ 'city': 'Minneapolis',
+ 'state': 'MN'
+ }];
+
+ inclusiveTest = $.grep(locationset, function (val, i) {
+ return $this.filterData(val, filter);
+ });
+
+ // Inclusive test
+ deepEqual(inclusiveTest, locationMatch, 'Inclusive filtering test');
+
+ $this.settings.exclusiveFiltering = true;
+
+ exclusiveTest = $.grep(locationset, function (val, i) {
+ return $this.filterData(val, filters);
+ });
+
+ // Exclusive test
+ deepEqual(exclusiveTest, locationsMatch, 'Exclusive filtering test');
+ });
+
+
+}(jQuery));