The PlaceSearchElement
is an HTML element that renders the results of a place search in a list. There are two ways to
configure the gmp-place-search
element:
- Use search nearby request to render search results using the
PlaceNearbySearchRequestElement
- Use search by text request to render search results using
the
PlaceTextSearchRequestElement
Search nearby request
Select a place type from the menu to see nearby search results for that place type.
The following example renders the Place
Search element in response to a nearby search. For simplicity, only three place types are listed:
cafe, restaurant, and EV charging station. When a result is selected, a marker and
PlaceDetailsCompactElement are displayed for the selected place. To add the
Place Search element to the map, add a
gmp-place-search
element containing a
gmp-place-nearby-search-request
element to the HTML page as shown in the
following snippet:
<div class="list-container"> <div id="map-container"></div> <div class="controls"> <select name="types" class="type-select"> <option value="">Select a place type</option> <option value="cafe">Cafe</option> <option value="restaurant">Restaurant</option> <option value="electric_vehicle_charging_station">EV charging station</option> </select> </div> <div class="list-container"> <gmp-place-search orientation="vertical" selectable> <gmp-place-all-content> </gmp-place-all-content> <gmp-place-nearby-search-request ></gmp-place-nearby-search-request> </gmp-place-search> </div> <div id="details-container"> <gmp-place-details-compact orientation="horizontal"> <gmp-place-details-place-request></gmp-place-details-place-request> <gmp-place-all-content></gmp-place-all-content> </gmp-place-details-compact> </div>
Several querySelector
calls are used to select the page elements for interaction:
const mapContainer = document.getElementById("map-container"); const placeSearch = document.querySelector("gmp-place-search"); const placeSearchQuery = document.querySelector("gmp-place-nearby-search-request"); const detailsContainer = document.getElementById("details-container"); const placeDetails = document.querySelector("gmp-place-details-compact"); const placeRequest = document.querySelector("gmp-place-details-place-request"); const typeSelect = document.querySelector(".type-select");
When the user selects a place type from the menu, the
gmp-place-nearby-search-request
element is updated, and the Place Search element
displays the results (markers are added in the addMarkers
helper function):
typeSelect.addEventListener('change', (event) => { event.preventDefault(); searchPlaces(); }); function searchPlaces(){ const bounds = gMap.getBounds(); placeDetailsPopup.map = null; if (typeSelect.value) { placeSearch.style.display = 'block'; placeSearchQuery.maxResultCount = 10; placeSearchQuery.locationRestriction = { center: cent, radius: 1000 }; placeSearchQuery.includedTypes = [typeSelect.value]; placeSearch.addEventListener('gmp-load', addMarkers, { once: true }); } }
See the complete code example
JavaScript
const mapContainer = document.getElementById("map-container"); const placeSearch = document.querySelector("gmp-place-search"); const placeSearchQuery = document.querySelector("gmp-place-nearby-search-request"); const detailsContainer = document.getElementById("details-container"); const placeDetails = document.querySelector("gmp-place-details-compact"); const placeRequest = document.querySelector("gmp-place-details-place-request"); const typeSelect = document.querySelector(".type-select"); let markers = {}; let gMap; let placeDetailsPopup; let spherical; let AdvancedMarkerElement; let LatLngBounds; let LatLng; async function init() { console.log("init"); ({ spherical } = await google.maps.importLibrary('geometry')); const {Map} = await google.maps.importLibrary("maps"); await google.maps.importLibrary("places"); ({AdvancedMarkerElement} = await google.maps.importLibrary("marker")); ({LatLngBounds, LatLng} = await google.maps.importLibrary("core")); let mapOptions = { center: {lat: -37.813, lng: 144.963}, zoom: 16, mapTypeControl: false, clickableIcons: false, mapId: 'DEMO_MAP_ID' }; gMap = new Map(mapContainer, mapOptions); placeDetailsPopup = new AdvancedMarkerElement({ map: null, content: placeDetails, zIndex: 100 }); findCurrentLocation(); gMap.addListener('click', (e) => { hidePlaceDetailsPopup(); }); typeSelect.addEventListener('change', (event) => { event.preventDefault(); searchPlaces(); }); placeSearch.addEventListener("gmp-select", ({ place }) => { if (markers[place.id]) { markers[place.id].click(); } }); } function searchPlaces(){ const bounds = gMap.getBounds(); const cent = gMap.getCenter(); const ne = bounds.getNorthEast(); const sw = bounds.getSouthWest(); const diameter = spherical.computeDistanceBetween(ne, sw); const cappedRadius = Math.min((diameter / 2 ), 50000); // Radius cannot be more than 50000. placeDetailsPopup.map = null; for(const markerId in markers){ if (Object.prototype.hasOwnProperty.call(markers, markerId)) { markers[markerId].map = null; } } markers = {}; if (typeSelect.value) { mapContainer.style.height = '75vh'; placeSearch.style.display = 'block'; placeSearchQuery.maxResultCount = 10; placeSearchQuery.locationRestriction = { center: cent, radius: cappedRadius }; placeSearchQuery.includedTypes = [typeSelect.value]; placeSearch.addEventListener('gmp-load', addMarkers, { once: true }); console.log("selection!"); console.log(cappedRadius); } } async function addMarkers(){ const bounds = new LatLngBounds(); placeSearch.style.display = 'block'; if(placeSearch.places.length > 0){ placeSearch.places.forEach((place) => { let marker = new AdvancedMarkerElement({ map: gMap, position: place.location }); marker.metadata = {id: place.id}; markers[place.id] = marker; bounds.extend(place.location); marker.addListener('click',(event) => { placeRequest.place = place; placeDetails.style.display = 'block'; placeDetailsPopup.position = place.location; placeDetailsPopup.map = gMap; gMap.fitBounds(place.viewport, {top: 0, left: 400}); placeDetails.addEventListener('gmp-load',() => { gMap.fitBounds(place.viewport, {top: 0, right: 450}); }, { once: true }); }); gMap.setCenter(bounds.getCenter()); gMap.fitBounds(bounds); }); } } async function findCurrentLocation(){ const { LatLng } = await google.maps.importLibrary("core"); if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (position) => { const pos = new LatLng(position.coords.latitude,position.coords.longitude); gMap.panTo(pos); gMap.setZoom(16); }, () => { console.log('The Geolocation service failed.'); gMap.setZoom(16); }, ); } else { console.log("Your browser doesn't support geolocation"); gMap.setZoom(16); } } function hidePlaceDetailsPopup() { if (placeDetailsPopup.map) { placeDetailsPopup.map = null; placeDetails.style.display = 'none'; } } init();
CSS
html, body { height: 100%; margin: 0; } body { display: flex; flex-direction: column; font-family: Arial, Helvetica, sans-serif; } h1 { font-size: large; text-align: center; } #map-container { flex-grow: 1; max-height:600px; box-sizing: border-box; width: 100%; height: 100vh; } .controls { position: absolute; top: 40px; right: 40px; } .list-container { display: flex; position: absolute; max-height: 500px; top: 80px; right: 40px; overflow-y: none; } .type-select { width: 400px; height: 32px; border: 1px solid #000; border-radius: 10px; flex-grow: 1; padding: 0 10px; } gmp-place-search { width: 400px; margin: 0; border-radius: 10px; display: none; border: none; } gmp-place-details-compact { width: 350px; max-height: 800px; margin-right: 20px; display: none; border: none; } gmp-place-details-compact::after { content: ''; position: absolute; bottom: -18px; left: 50%; transform: translateX(-50%); width: 20px; height: 20px; background-color: white; box-shadow: 2px 2px 5px 0 rgba(0,0,0,0.2); z-index: 1; clip-path: polygon(0% 0%, 100% 0%, 50% 100%); transform-origin: center center; } @media (prefers-color-scheme: dark) { /* Style for Dark mode */ gmp-place-details-compact::after { background-color: #131314; } }
HTML
<!DOCTYPE html> <html> <head> <title>Place Search with Compact Place Details Element</title> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> <script type="module" src="./index.js"></script> </head> <body> <div id="map-container"></div> <div class="controls"> <select name="types" class="type-select"> <option value="">Select a place type</option> <option value="cafe">Cafe</option> <option value="restaurant">Restaurant</option> <option value="electric_vehicle_charging_station">EV charging station</option> </select> </div> <div class="list-container"> <gmp-place-search orientation="vertical" selectable> <gmp-place-all-content> </gmp-place-all-content> <gmp-place-nearby-search-request ></gmp-place-nearby-search-request> </gmp-place-search> </div> <div id="details-container"> <gmp-place-details-compact orientation="horizontal"> <gmp-place-details-place-request></gmp-place-details-place-request> <gmp-place-all-content></gmp-place-all-content> </gmp-place-details-compact> </div> <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))}) ({key: "YOUR_API_KEY", v: "weekly"}); </script> </body> </html>
Search by text request
Enter a search term in the input field and click the Search button to get a list of places that match the term.
This example renders the Place Search element in response to a user text search. When a result is
selected, a marker and
PlaceDetailsCompactElement are displayed for the selected place. To add the
Place Search element to the map, add a
gmp-place-search
element containing a
gmp-place-search-text-search-request
element to the HTML page as shown in the
following snippet:
<div id="map-container"></div> <div class="controls"> <input type="text" class="query-input" /> <button class="search-button">Search</button> </div> <div class="list-container"> <gmp-place-search orientation="vertical" selectable> <gmp-place-all-content> </gmp-place-all-content> <gmp-place-text-search-request></gmp-place-text-search-request> </gmp-place-search> </div> <div id="details-container"> <gmp-place-details-compact orientation="horizontal"> <gmp-place-details-place-request></gmp-place-details-place-request> <gmp-place-all-content></gmp-place-all-content> </gmp-place-details-compact> </div>
Several querySelector
calls are used to select the page elements for interaction:
const mapContainer = document.getElementById("map-container"); const placeSearch = document.querySelector("gmp-place-search"); const placeSearchQuery = document.querySelector("gmp-place-text-search-request"); const queryInput = document.querySelector(".query-input"); const searchButton = document.querySelector(".search-button"); const detailsContainer = document.getElementById("details-container"); const placeDetails = document.querySelector("gmp-place-details-compact"); const placeRequest = document.querySelector("gmp-place-details-place-request");
When the search function is run after the user enters a search query, the
gmp-place-text-search-request
element is updated, and the Place Search element
displays the results (markers are added in the addMarkers
helper function):
searchButton.addEventListener("click", searchPlaces); queryInput.addEventListener("keydown", (event) => { if (event.key == 'Enter') { event.preventDefault(); searchPlaces(); } }); function searchPlaces(){ if (queryInput.value) { placeSearch.style.display = 'block'; placeSearchQuery.textQuery = queryInput.value; placeSearchQuery.locationBias = gMap.getBounds(); placeSearch.addEventListener('gmp-load', addMarkers, { once: true }); } }
See the complete code example
JavaScript
const mapContainer = document.getElementById("map-container"); const placeSearch = document.querySelector("gmp-place-search"); const placeSearchQuery = document.querySelector("gmp-place-text-search-request"); const queryInput = document.querySelector(".query-input"); const searchButton = document.querySelector(".search-button"); const detailsContainer = document.getElementById("details-container"); const placeDetails = document.querySelector("gmp-place-details-compact"); const placeRequest = document.querySelector("gmp-place-details-place-request"); let markers = {}; let previousSearchQuery = ''; let gMap; let placeDetailsPopup; let AdvancedMarkerElement; let LatLngBounds; let LatLng; async function init() { const {Map} = await google.maps.importLibrary("maps"); await google.maps.importLibrary("places"); ({AdvancedMarkerElement} = await google.maps.importLibrary("marker")); ({LatLngBounds, LatLng} = await google.maps.importLibrary("core")); let mapOptions = { center: {lat: 37.422, lng: -122.085}, zoom: 2, mapTypeControl: false, clickableIcons: false, mapId: 'DEMO_MAP_ID' }; gMap = new Map(mapContainer, mapOptions); placeDetailsPopup = new AdvancedMarkerElement({ map: null, content: placeDetails, zIndex: 100 }); findCurrentLocation(); gMap.addListener('click', (e) => { hidePlaceDetailsPopup(); }); searchButton.addEventListener("click", searchPlaces); queryInput.addEventListener("keydown", (event) => { if (event.key == 'Enter') { event.preventDefault(); searchPlaces(); } }); placeSearch.addEventListener("gmp-select", ({ place }) => { if (markers[place.id]) { markers[place.id].click(); } }); } function searchPlaces(){ if (queryInput.value.trim() === previousSearchQuery) { return; } previousSearchQuery = queryInput.value.trim(); placeDetailsPopup.map = null; for(const markerId in markers){ if (Object.prototype.hasOwnProperty.call(markers, markerId)) { markers[markerId].map = null; } } markers = {}; if (queryInput.value) { // mapContainer.style.height = '75vh'; placeSearch.style.display = 'block'; placeSearchQuery.textQuery = queryInput.value; placeSearchQuery.locationBias = gMap.getBounds(); placeSearch.addEventListener('gmp-load', addMarkers, { once: true }); } } async function addMarkers(){ const bounds = new LatLngBounds(); if(placeSearch.places.length > 0){ placeSearch.places.forEach((place) => { let marker = new AdvancedMarkerElement({ map: gMap, position: place.location }); marker.metadata = {id: place.id}; markers[place.id] = marker; bounds.extend(place.location); marker.addListener('click',(event) => { placeRequest.place = place; placeDetails.style.display = 'block'; placeDetailsPopup.position = place.location; placeDetailsPopup.map = gMap; gMap.fitBounds(place.viewport, {top: 200, right: 450}); }); gMap.setCenter(bounds.getCenter()); gMap.fitBounds(bounds); }); } } async function findCurrentLocation(){ const { LatLng } = await google.maps.importLibrary("core"); if (navigator.geolocation) { navigator.geolocation.getCurrentPosition( (position) => { const pos = new LatLng(position.coords.latitude,position.coords.longitude); gMap.panTo(pos); gMap.setZoom(16); }, () => { console.log('The Geolocation service failed.'); gMap.setZoom(16); }, ); } else { console.log("Your browser doesn't support geolocation"); gMap.setZoom(16); } } function hidePlaceDetailsPopup() { if (placeDetailsPopup.map) { placeDetailsPopup.map = null; placeDetails.style.display = 'none'; } } init();
CSS
html, body { height: 100%; margin: 0; } body { display: flex; flex-direction: column; font-family: Arial, Helvetica, sans-serif; } h1 { font-size: large; text-align: center; } #map-container { flex-grow: 1; max-height:600px; box-sizing: border-box; width: 100%; height: 100vh; } .controls { border-radius: 5px; position: absolute; top: 40px; right: 40px; } .search-button { background-color: #4b4b4b; color: #fff; border: 1px solid #000; border-radius: 10px; width: 80px; height: 40px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.35); } .query-input { border: 1px solid #ccc; border-radius: 10px; width: 315px; height: 40px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.35); } .list-container { display: flex; position: absolute; max-height: 500px; top: 100px; right: 40px; overflow-y: none; } gmp-place-search { width: 400px; margin: 0; border-radius: 10px; display: none; border: none; } gmp-place-details-compact { width: 350px; max-height: 800px; display: none; border: none; transform: translateY(calc(-40%)); } gmp-place-details-compact::after { content: ''; position: absolute; bottom: -18px; left: 50%; transform: translateX(-50%); width: 20px; height: 20px; background-color: white; box-shadow: 2px 2px 5px 0 rgba(0,0,0,0.2); z-index: 1; clip-path: polygon(0% 0%, 100% 0%, 50% 100%); transform-origin: center center; } @media (prefers-color-scheme: dark) { /* Style for Dark mode */ gmp-place-details-compact::after { background-color: #131314; } }
HTML
<!DOCTYPE html> <html> <head> <title>Place Search with a Details Popup</title> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> <script type="module" src="./index.js"></script> </head> <body> <div id="map-container"></div> <div class="controls"> <input type="text" class="query-input" /> <button class="search-button">Search</button> </div> <div class="list-container"> <gmp-place-search orientation="vertical" selectable> <gmp-place-all-content> </gmp-place-all-content> <gmp-place-text-search-request></gmp-place-text-search-request> </gmp-place-search> </div> <div id="details-container"> <gmp-place-details-compact orientation="horizontal"> <gmp-place-details-place-request></gmp-place-details-place-request> <gmp-place-all-content></gmp-place-all-content> </gmp-place-details-compact> </div> <script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))}) ({key: "YOUR_API_KEY", v: "weekly"}); </script> </body> </html>