diff --git a/samples/place-autocomplete-data-session/index.html b/samples/place-autocomplete-data-session/index.html index c84980e9..02babac7 100644 --- a/samples/place-autocomplete-data-session/index.html +++ b/samples/place-autocomplete-data-session/index.html @@ -6,35 +6,34 @@ --> - - Place Autocomplete Data API Session + + Place Autocomplete Data API Session - - - - - - -
- - Powered by Google - - - - - + + + + + + + + +
+ +
+
+
    +
    +
    + + diff --git a/samples/place-autocomplete-data-session/index.ts b/samples/place-autocomplete-data-session/index.ts index c9f7ab53..cb688153 100644 --- a/samples/place-autocomplete-data-session/index.ts +++ b/samples/place-autocomplete-data-session/index.ts @@ -5,52 +5,69 @@ */ // [START maps_place_autocomplete_data_session] -let titleElement; -let resultsContainerElement; -let inputElement; - +const mapElement = document.querySelector('gmp-map') as google.maps.MapElement; +let innerMap: google.maps.Map; +let marker: google.maps.marker.AdvancedMarkerElement; +let titleElement = document.querySelector('.title') as HTMLElement; +let resultsContainerElement = document.querySelector('.results') as HTMLElement; +let inputElement = document.querySelector('input') as HTMLInputElement; +let tokenStatusElement = document.querySelector('.token-status') as HTMLElement; let newestRequestId = 0; +let tokenCount = 0; -// Add an initial request body. -const request = { +// Create an initial request body. +const request: google.maps.places.AutocompleteRequest = { input: '', - locationRestriction: { west: -122.44, north: 37.8, east: -122.39, south: 37.78 }, - origin: { lat: 37.7893, lng: -122.4039 }, - includedPrimaryTypes: ['restaurant'], - language: 'en-US', - region: 'us', -}; - -function init() { - titleElement = document.getElementById('title'); - resultsContainerElement = document.getElementById('results'); - inputElement = document.querySelector('input'); + includedPrimaryTypes: [ + 'restaurant', + 'cafe', + 'museum', + 'park', + 'botanical_garden', + ], +} + +async function init() { + await google.maps.importLibrary('maps'); + innerMap = mapElement.innerMap; + innerMap.setOptions({ + mapTypeControl: false, + }); + + // Update request center and bounds when the map bounds change. + google.maps.event.addListener(innerMap, 'bounds_changed', async () => { + request.locationRestriction = innerMap.getBounds(); + request.origin = innerMap.getCenter(); + }); + inputElement.addEventListener('input', makeAutocompleteRequest); - refreshToken(request); } async function makeAutocompleteRequest(inputEvent) { - // Reset elements and exit if an empty string is received. - if (inputEvent.target.value == '') { - titleElement.innerText = ''; + // To avoid race conditions, store the request ID and compare after the request. + const requestId = ++newestRequestId; + + const { AutocompleteSuggestion } = (await google.maps.importLibrary( + 'places' + )) as google.maps.PlacesLibrary; + + if (!inputEvent.target?.value) { + titleElement.textContent = ''; resultsContainerElement.replaceChildren(); return; } // Add the latest char sequence to the request. - request.input = inputEvent.target.value; - - // To avoid race conditions, store the request ID and compare after the request. - const requestId = ++newestRequestId; + request.input = (inputEvent.target as HTMLInputElement).value; // Fetch autocomplete suggestions and show them in a list. - // @ts-ignore - const { suggestions } = await google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions(request); + const { suggestions } = + await AutocompleteSuggestion.fetchAutocompleteSuggestions(request); // If the request has been superseded by a newer request, do not render the output. if (requestId !== newestRequestId) return; - titleElement.innerText = `Query predictions for "${request.input}"`; + titleElement.innerText = `Place predictions for "${request.input}"`; // Clear the list first. resultsContainerElement.replaceChildren(); @@ -58,44 +75,74 @@ async function makeAutocompleteRequest(inputEvent) { for (const suggestion of suggestions) { const placePrediction = suggestion.placePrediction; + if (!placePrediction) { + continue; + } + // Create a link for the place, add an event handler to fetch the place. - const a = document.createElement('a'); - a.addEventListener('click', () => { - onPlaceSelected(placePrediction!.toPlace()); + // We are using a button element to take advantage of its a11y capabilities. + const placeButton = document.createElement('button'); + placeButton.addEventListener('click', () => { + onPlaceSelected(placePrediction.toPlace()); }); - a.innerText = placePrediction!.text.toString(); + placeButton.textContent = placePrediction.text.toString(); + placeButton.classList.add('place-button'); // Create a new list item element. const li = document.createElement('li'); - li.appendChild(a); + li.appendChild(placeButton); resultsContainerElement.appendChild(li); } } // Event handler for clicking on a suggested place. -async function onPlaceSelected(place) { +async function onPlaceSelected(place: google.maps.places.Place) { + const { AdvancedMarkerElement } = (await google.maps.importLibrary( + 'marker' + )) as google.maps.MarkerLibrary; + await place.fetchFields({ - fields: ['displayName', 'formattedAddress'], + fields: ['displayName', 'formattedAddress', 'location'], }); - const placeText = document.createTextNode(`${place.displayName}: ${place.formattedAddress}`); - resultsContainerElement.replaceChildren(placeText); - titleElement.innerText = 'Selected Place:'; + + resultsContainerElement.textContent = `${place.displayName}: ${place.formattedAddress}`; + titleElement.textContent = 'Selected Place:'; inputElement.value = ''; - refreshToken(request); + + await refreshToken(); + + // Remove the previous marker, if it exists. + if (marker) { + marker.remove(); + } + + // Create a new marker. + marker = new AdvancedMarkerElement({ + map: innerMap, + position: place.location, + title: place.displayName, + }) + + // Center the map on the selected place. + if (place.location) { + innerMap.setCenter(place.location); + innerMap.setZoom(15); + } } // Helper function to refresh the session token. -function refreshToken(request) { +async function refreshToken() { + const { AutocompleteSessionToken } = (await google.maps.importLibrary( + 'places' + )) as google.maps.PlacesLibrary; + + // Increment the token counter. + tokenCount++; + // Create a new session token and add it to the request. - request.sessionToken = new google.maps.places.AutocompleteSessionToken(); + request.sessionToken = new AutocompleteSessionToken(); + tokenStatusElement.textContent = `Session token count: ${tokenCount}`; } -declare global { - interface Window { - init: () => void; - } - } - window.init = init; +init(); // [END maps_place_autocomplete_data_session] -void 0; // No-op to preserve the last region tag comment. -export { }; diff --git a/samples/place-autocomplete-data-session/style.css b/samples/place-autocomplete-data-session/style.css index dd36896d..dfff1a9d 100644 --- a/samples/place-autocomplete-data-session/style.css +++ b/samples/place-autocomplete-data-session/style.css @@ -22,14 +22,55 @@ body { padding: 0; } -a { +.place-button { + height: 3rem; + width: 100%; + background-color: transparent; + text-align: left; + border: none; cursor: pointer; - text-decoration: underline; - color: blue; } -input { +.place-button:focus-visible { + outline: 2px solid #0056b3; + border-radius: 2px; +} + +.input { width: 300px; + font-size: small; + margin-bottom: 1rem; +} + +/* Styles for the floating panel */ +.controls { + background-color: #fff; + border-radius: 8px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); + font-family: sans-serif; + font-size: small; + margin: 12px; + padding: 1rem; +} + +.title { + font-weight: bold; + margin-top: 1rem; + margin-bottom: 0.5rem; +} + +.results { + list-style-type: none; + margin: 0; + padding: 0; +} + +.results li:not(:last-child) { + border-bottom: 1px solid #ddd; +} + +.results li:hover { + background-color: #eee; } -/* [END maps_place_autocomplete_data_session] */ \ No newline at end of file +/* [END maps_place_autocomplete_data_session] */