1import { helpers } from './helpers.js'; 2import { settings } from './settings.js'; 3import { loadGoogleMaps } from './gmapsLoader.js'; 4import { setMarkersManager, getMarkersManager, MapMarkersManager, showDealerDetails, showDealerOffers, updateDealersList, resetDealerSearch, locationFieldAutocomplete, highlightRowOnDealerList } from './mapHelpers.js'; 5 6export const map = (() => { 7 8 async function initMap( mapId) { 9 const mapContainer = document.querySelector( mapId ); 10 if (!mapContainer) return; 11 12 // sanity-check configu 13 if ( 14 !window.SP.mapConfig?.MAP_KEY || 15 !Array.isArray(window.SP.mapConfig.dealersData) || 16 !window.SP.mapConfig.dealersData.length 17 ) { 18 console.warn('Map init error â invalid or missing map data'); 19 return; 20 } 21 22 // load gmap script 23 await loadGoogleMaps(window.SP.mapConfig.MAP_KEY); 24 25 const map = new google.maps.Map(mapContainer, { 26 mapId: 'dealers-map', 27 center: settings.location.initial, 28 zoom: settings.location.initial.zoom, 29 disableDefaultUI: true, 30 gestureHandling: 'cooperative', 31 }); 32 33 // save initial dealers data for reset 34 window.SP.mapConfig.initialDealersData = [...window.SP.mapConfig.dealersData]; 35 36 const markers = new MapMarkersManager(map); 37 setMarkersManager(markers); 38 39 // put markers on map 40 markers.add(window.SP.mapConfig.dealersData, (dealer) => { 41 42 switch (mapId) { 43 case '#dealer-map': 44 showDealerDetails(dealer, markers); 45 highlightRowOnDealerList(dealer); 46 break; 47 48 case '#offer-map': 49 showDealerOffers(dealer, markers); 50 break; 51 } 52 }); 53 54 if( mapId == '#offer-map'){ 55 const backBtn = document.querySelector('[dealer-back]'); 56 const dialog = backBtn.closest('dialog'); 57 58 backBtn.addEventListener('click', () => { 59 helpers.clearDialogTemplate(dialog); 60 dialog.close(); 61 62 // Back to the view the page opens with. Recomputed from the markers, not replayed from 63 // defaultView: saveDefaultView() reads getCenter()/getZoom() right after fitBounds(), which 64 // Google Maps applies asynchronously, so that snapshot holds the pre-fit position. 65 markers.fitToMarkers(); 66 markers.unhighlight(); 67 }); 68 } 69 70 // fit view for all markers 71 markers.fitToMarkers(); 72 73 74 // click on dealer row in list 75 document.addEventListener('click', (e) => { 76 const row = e.target.closest('[show-dealer-details]'); 77 if (!row) return; 78 79 const dealerId = row.getAttribute('show-dealer-details'); 80 const isActive = row.classList.contains('active'); 81 82 const dealer = window.SP.mapConfig.dealersData?.find( 83 (d) => d.id == dealerId 84 ); 85 86 if (!dealer ){ 87 console.warn('Dealer not found in mapConfig.dealersData') 88 return; 89 } 90 91 // clear content 92 document.querySelector('.find-dealer__details').innerHTML = ''; 93 // unselect all rows 94 document.querySelectorAll('[show-dealer-details].active').forEach(el => el.classList.remove('active')); 95 document.querySelectorAll('[show-dealer-details]').forEach(el => el.setAttribute('aria-selected' , false)); 96 97 if (isActive) { 98 markers.restoreDefaultView(); 99 } else { 100 showDealerDetails(dealer, markers); 101 } 102 }); 103 104 105 // click on 'find dealers near me' btn 106 const btn = document.querySelector('[find-dealer-near-user]'); 107 if (btn) { 108 btn.addEventListener('click', () => { 109 if (!navigator.geolocation || btn.disabled) return; 110 111 btn.disabled = true; 112 navigator.geolocation.getCurrentPosition( 113 (position) => { 114 const query = `${position.coords.latitude},${position.coords.longitude}`; 115 116 const queries = { 117 queryValue: query, 118 rangeValue: '100 km' 119 }; 120 121 updateDealersList(queries); 122 btn.disabled = false; 123 }, 124 () => { 125 btn.disabled = false; 126 } 127 ); 128 }); 129 } 130 131 132 // click on 'reset search' btn 133 const resetBtn = document.querySelector('[reset-dealer-search]'); 134 if (resetBtn) { 135 resetBtn.addEventListener('click', () => { 136 resetDealerSearch(markers, mapId); 137 }); 138 } 139 140 } 141 142 143 144 /** 145 * Initializes Google Places autocomplete integration. 146 * 147 * Attaches the autocomplete to the location input field, retrieves 148 * location details via API, and updates a hidden field with latitude/longitude. 149 * 150 * @function initGooglePlaces 151 */ 152 function initGooglePlaces(mapId) { 153 const mapContainer = document.querySelector( mapId ); 154 if (!mapContainer) return; 155 156 const searchForm = document.querySelector('.find-dealer__control__search'); 157 if (!searchForm) return; 158 159 const locationInput = searchForm.querySelector('[name="location"]'); 160 const queryInput = searchForm.querySelector('[name="query"]'); 161 const rangeInput = searchForm.querySelector('[name="range"]'); 162 163 const formSubmit = searchForm.querySelector('button[type="submit"]'); 164 165 if( !locationInput || !queryInput || !rangeInput ) return; 166 167 // Reset queryInput when typing 168 locationInput.addEventListener('input', () => { 169 queryInput.value = ''; 170 }); 171 172 // Clear input button 173 const clearBtn = searchForm.querySelector('[clear-location-input]'); 174 if (clearBtn) { 175 const updateClearBtn = () => { 176 clearBtn.hidden = !locationInput.value.trim(); 177 }; 178 179 locationInput.addEventListener('input', updateClearBtn); 180 locationInput.addEventListener('update', updateClearBtn); 181 182 clearBtn.addEventListener('click', () => { 183 locationInput.value = ''; 184 queryInput.value = ''; 185 clearBtn.hidden = true; 186 locationInput.dispatchEvent(new CustomEvent('clear')); 187 locationInput.focus(); 188 }); 189 } 190 191 // Attach autocomplete 192 locationFieldAutocomplete(locationInput, (loc) => { 193 if (!loc?.id || !loc?.value) return; 194 195 // Prepare query params 196 const params = new URLSearchParams({ 197 locationId: loc.id, 198 locationName: loc.value, 199 }).toString(); 200 201 locationInput.classList.add('loading'); 202 formSubmit.classList.add('disabled'); 203 204 // Fetch detailed data 205 fetch(`${location.origin}
205/${settings.endpoints.locationDetails}?${params}`, { 206 method: 'GET', 207 headers: { 'Accept': 'application/json' }, 208 }) 209 .then((res) => res.json()) 210 .then((json) => { 211 const data = json.data; 212 213 // extract lat/lng 214 if (data.result?.geometry) { 215 const location = data.result.geometry.location?.lat + ',' + data.result.geometry.location?.lng; 216 queryInput.value = location; // Update hidden field with coord 217 } 218 }) 219 .catch((err) => { 220 console.error('Location details request failed:', err); 221 }) 222 .finally(() => { 223 locationInput.classList.remove('loading'); 224 formSubmit.classList.remove('disabled'); 225 }); 226 }); 227 228 if (searchForm.dataset.jsEventInitialized === 'true') { 229 return; 230 } 231 232 // Handles search form submission via GET fetch request. 233 searchForm.addEventListener('submit', (e) => { 234 e.preventDefault(); 235 236 const queries = { 237 'locationValue' : locationInput.value.trim(), 238 'queryValue' : queryInput.value.trim(), 239 'rangeValue' : rangeInput.value.trim() 240 } 241 242 if ( !queries.queryValue && !queries.locationValue ) { 243 resetDealerSearch(getMarkersManager(), mapId); 244 return; 245 } 246 247 updateDealersList( queries ); 248 249 }); 250 251 searchForm.dataset.jsEventInitialized = 'true'; 252 } 253 254 function setup( mapId ) { 255 initMap( mapId ); 256 initGooglePlaces( mapId ); 257 } 258 259 return { 260 setup, 261 initMap, 262 initGooglePlaces, 263 }; 264})(); 265 266window.SP = window.SP || {}; 267window.SP.map = map;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.