The PlaceListElement
, also known as Place Search, is an HTML element that renders the results of a place search in a list. There are two ways to
configure the gmp-place-list
element:
- Search nearby request to configure the widget to render search results from a Places Nearby Search.
- Search by text request to configure the widget to render search results from a Places Text Search.
Search nearby request
The following example renders the Place
List 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, an info window showing
place details is displayed for the selected place. To add the Place List element to the map, add a
gmp-place-list
element to the HTML page as shown in the following snippet:
<gmp-map center="-37.813,144.963" zoom="10" map-id="DEMO_MAP_ID"> <div class="overlay" slot="control-inline-start-block-start"> <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-list selectable style="display: none;"></gmp-place-list> </div> </div> </gmp-map> <gmp-place-details style="display: none;"> <gmp-place-details-place-request></gmp-place-details-place-request> <gmp-place-all-content></gmp-place-all-content> </gmp-place-details>
Several querySelector
calls are used to select the page elements for interaction:
const map = document.querySelector("gmp-map"); const placeList = document.querySelector("gmp-place-list"); const typeSelect = document.querySelector(".type-select"); const placeDetails = document.querySelector("gmp-place-details"); const placeDetailsRequest = document.querySelector('gmp-place-details-place-request');
When the user clicks the search button,
configureFromSearchNearbyRequest
is called, and the Place List element displays
the results (markers are added in the addMarkers
helper function). The following
snippet also shows the code for handling click events on the Place List using the gmp-placeselect
event:
placeDetails.addEventListener('gmp-load', (event) => { // Center the info window on the map. map.innerMap.fitBounds(placeDetails.place.viewport, { top: 500, left: 400 }); }); typeSelect.addEventListener('change', (event) => { // First remove all existing markers. for (marker in markers) { markers[marker].map = null; } markers = {}; if (typeSelect.value) { placeList.style.display = 'block'; placeList.configureFromSearchNearbyRequest({ locationRestriction: getContainingCircle(map.innerMap.getBounds()), includedPrimaryTypes: [typeSelect.value], }).then(addMarkers); // Handle user selection in Place Details. placeList.addEventListener('gmp-placeselect', ({ place }) => { markers[place.id].click(); }); } });
See the complete code example
JavaScript
const map = document.querySelector("gmp-map"); const placeList = document.querySelector("gmp-place-list"); const typeSelect = document.querySelector(".type-select"); const placeDetails = document.querySelector("gmp-place-details"); const placeDetailsRequest = document.querySelector('gmp-place-details-place-request'); let markers = {}; let infoWindow; async function initMap() { await google.maps.importLibrary('places'); const { LatLngBounds } = await google.maps.importLibrary('core'); const { InfoWindow } = await google.maps.importLibrary('maps'); const { spherical } = await google.maps.importLibrary('geometry'); infoWindow = new InfoWindow; let marker; function getContainingCircle(bounds) { const diameter = spherical.computeDistanceBetween(bounds.getNorthEast(), bounds.getSouthWest()); const calculatedRadius = diameter / 2; const cappedRadius = Math.min(calculatedRadius, 50000); // Radius cannot be more than 50000. return { center: bounds.getCenter(), radius: cappedRadius }; } findCurrentLocation(); map.innerMap.setOptions({ mapTypeControl: false, clickableIcons: false, }); placeDetails.addEventListener('gmp-load', (event) => { // Center the info window on the map. map.innerMap.fitBounds(placeDetails.place.viewport, { top: 500, left: 400 }); }); typeSelect.addEventListener('change', (event) => { // First remove all existing markers. for (marker in markers) { markers[marker].map = null; } markers = {}; if (typeSelect.value) { placeList.style.display = 'block'; placeList.configureFromSearchNearbyRequest({ locationRestriction: getContainingCircle(map.innerMap.getBounds()), includedPrimaryTypes: [typeSelect.value], }).then(addMarkers); // Handle user selection in Place Details. placeList.addEventListener('gmp-placeselect', ({ place }) => { markers[place.id].click(); }); } }); } async function addMarkers() { const { AdvancedMarkerElement } = await google.maps.importLibrary('marker'); const { LatLngBounds } = await google.maps.importLibrary('core'); const bounds = new LatLngBounds(); if (placeList.places.length > 0) { placeList.places.forEach((place) => { let marker = new AdvancedMarkerElement({ map: map.innerMap, position: place.location }); markers[place.id] = marker; bounds.extend(place.location); marker.addListener('gmp-click', (event) => { if (infoWindow.isOpen) { infoWindow.close(); } placeDetailsRequest.place = place.id; placeDetails.style.display = 'block'; placeDetails.style.width = '350px'; infoWindow.setOptions({ content: placeDetails, }); infoWindow.open({ anchor: marker, map: map.innerMap }); }); map.innerMap.setCenter(bounds.getCenter()); map.innerMap.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); map.innerMap.panTo(pos); map.innerMap.setZoom(14); }, () => { console.log('The Geolocation service failed.'); map.innerMap.setZoom(14); }); } else { console.log('Your browser doesn\'t support geolocation'); map.innerMap.setZoom(14); } } initMap();
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; } gmp-map { box-sizing: border-box; height: 600px; } .overlay { position: relative; top: 40px; margin: 20px; width: 400px; } .controls { display: flex; gap: 10px; margin-bottom: 10px; height: 32px; } .search-button { background-color: #5491f5; color: #fff; border: 1px solid #ccc; border-radius: 5px; width: 100px; cursor: pointer; } .type-select { border: 1px solid #ccc; border-radius: 5px; flex-grow: 1; padding: 0 10px; } .list-container { height: 400px; overflow: auto; border-radius: 10px; } gmp-place-list { background-color: #fff; font-size: large }
HTML
<!DOCTYPE html> <html> <head> <title>Place List Nearby Search with Google Maps</title> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="style.css"> <script type="module" src="./index.js"></script> </head> <body> <gmp-map center="-37.813,144.963" zoom="10" map-id="DEMO_MAP_ID"> <div class="overlay" slot="control-inline-start-block-start"> <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-list selectable style="display: none;"></gmp-place-list> </div> </div> </gmp-map> <gmp-place-details style="display: none;"> <gmp-place-details-place-request></gmp-place-details-place-request> <gmp-place-all-content></gmp-place-all-content> </gmp-place-details> <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: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "alpha"});</script> </body> </html>
Try Sample
Search by text request
This example renders the Place List element in response to a user text search. When a
result is selected, an info window showing place details is displayed for the selected place. To
add the Place List element to the map, add a gmp-place-list
element to the HTML page,
as shown in the following snippet:
<gmp-map center="37.395641,-122.077627" zoom="10" map-id="DEMO_MAP_ID"> <div class="overlay" slot="control-inline-start-block-start"> <div class="text-search-container"> <input type="text" id="textSearchInput" placeholder="Enter text search..."> <button id="textSearchButton">Search</button> </div> <div class="list-container"> <gmp-place-list selectable style="display: none;"></gmp-place-list> </div> </div> <gmp-place-details style="display: none;"> <gmp-place-details-place-request></gmp-place-details-place-request> <gmp-place-all-content></gmp-place-all-content> </gmp-place-details> </gmp-map>
Several querySelector
calls are used to select the page elements for interaction:
const map = document.querySelector("gmp-map"); const placeList = document.querySelector("gmp-place-list"); const placeDetails = document.querySelector("gmp-place-details"); let marker = document.querySelector('gmp-advanced-marker'); const textSearchInput = document.getElementById('textSearchInput'); const textSearchButton = document.getElementById('textSearchButton'); const placeDetailsRequest = document.querySelector('gmp-place-details-place-request');
When the search function is run after the user enters a search query,
configureFromSearchByTextRequest
is called, and the Place List element renders
the results (markers are added in the addMarkers
helper function). The following
snippet shows the code for the function:
async function searchByTextRequest() { if (textSearchInput.value !== "") { placeList.style.display = "block"; placeList.configureFromSearchByTextRequest({ locationRestriction: bounds, textQuery: textSearchInput.value, }).then(addMarkers); // Handle user selection in Place Details. placeList.addEventListener("gmp-placeselect", ({ place }) => { markers[place.id].click(); }); } }
See the complete code example
The following example uses the Place List UI component to display places based on a text search using
configureFromSearchNearbyRequest
, and adds clickable markers to the map to show
place details upon selection.
JavaScript
const map = document.querySelector("gmp-map"); const placeList = document.querySelector("gmp-place-list"); const placeDetails = document.querySelector("gmp-place-details"); let marker = document.querySelector('gmp-advanced-marker'); const textSearchInput = document.getElementById('textSearchInput'); const textSearchButton = document.getElementById('textSearchButton'); const placeDetailsRequest = document.querySelector('gmp-place-details-place-request'); let markers = {}; let infoWindow; let center = { lat: 37.395641, lng: -122.077627 }; // Mountain View, CA. let bounds; async function initMap() { const { Map, InfoWindow } = await google.maps.importLibrary("maps"); const { Place } = await google.maps.importLibrary("places"); // Set bounds for location restriction. bounds = new google.maps.LatLngBounds({ lat: 37.37808200917261, lng: -122.13741583377849 }, { lat: 37.416676154341324, lng: -122.02261728794109 }); infoWindow = new google.maps.InfoWindow; // Center the map map.innerMap.panTo(center); map.innerMap.setZoom(14); map.innerMap.setOptions({ mapTypeControl: false, clickableIcons: false, }); // Fire when the Place Details Element is loaded. placeDetails.addEventListener('gmp-load', (event) => { // Center the info window on the map. map.innerMap.fitBounds(placeDetails.place.viewport, { top: 500, left: 400 }); }); // Handle clicks on the search button. textSearchButton.addEventListener('click', searchByTextRequest); // Handle enter key on text input. textSearchInput.addEventListener('keydown', (event) => { if (event.key === 'Enter') { searchByTextRequest(); } }); } async function searchByTextRequest() { if (textSearchInput.value !== "") { placeList.style.display = "block"; placeList.configureFromSearchByTextRequest({ locationRestriction: bounds, textQuery: textSearchInput.value, }).then(addMarkers); // Handle user selection in Place Details. placeList.addEventListener("gmp-placeselect", ({ place }) => { markers[place.id].click(); }); } } async function addMarkers() { const { AdvancedMarkerElement } = await google.maps.importLibrary("marker"); const { LatLngBounds } = await google.maps.importLibrary("core"); const bounds = new LatLngBounds(); // First remove all existing markers. for (marker in markers) { markers[marker].map = null; } markers = {}; if (placeList.places.length > 0) { placeList.places.forEach((place) => { let marker = new AdvancedMarkerElement({ map: map.innerMap, position: place.location, }); markers[place.id] = marker; bounds.extend(place.location); marker.collisionBehavior = google.maps.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL; marker.addListener('gmp-click', (event) => { if (infoWindow.isOpen) { infoWindow.close(); } // Set the Place Details request to the selected place. placeDetailsRequest.place = place.id; placeDetails.style.display = "block"; placeDetails.style.width = "350px"; infoWindow.setOptions({ content: placeDetails }); infoWindow.open({ anchor: marker, map: map.innerMap }); placeDetails.addEventListener('gmp-load', () => { map.innerMap.fitBounds(place.viewport, { top: 400, left: 400 }); }); }); map.innerMap.setCenter(bounds.getCenter()); map.innerMap.fitBounds(bounds); }); } } initMap();
CSS
html, body { height: 100%; margin: 0; } gmp-map { box-sizing: border-box; height: 500px; } gmp-place-list { background-color: #fff; font-size: large } .overlay { position: relative; top: 20px; margin: 20px; width: 400px; } .list-container { height: 400px; overflow: auto; border-radius: 10px; } /* Styles for the text search container and its elements */ .text-search-container { display: flex; /* Arrange input and button side-by-side */ gap: 8px; /* Space between input and button */ padding-bottom: 10px; /* Space below the search bar */ border-bottom: 1px solid #eee; /* Separator line */ } #textSearchInput { flex-grow: 1; /* Allow input to take available space */ padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; font-size: 1rem; } #textSearchInput:focus { outline: none; border-color: #4a80e8; /* Highlight on focus */ box-shadow: 0 0 0 2px rgba(74, 128, 232, 0.2); } #textSearchButton { padding: 8px 15px; background-color: #4a80e8; /* Example button color */ color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; transition: background-color 0.2s ease-in-out; } #textSearchButton:hover { background-color: #356ac0; /* Darker shade on hover */ }
HTML
<!DOCTYPE html> <html> <head> <title>Place List Text Search with Google Maps</title> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="style.css"> <script type="module" src="./index.js"></script> </head> <body> <gmp-map center="37.395641,-122.077627" zoom="10" map-id="DEMO_MAP_ID"> <div class="overlay" slot="control-inline-start-block-start"> <div class="text-search-container"> <input type="text" id="textSearchInput" placeholder="Enter text search..."> <button id="textSearchButton">Search</button> </div> <div class="list-container"> <gmp-place-list selectable style="display: none;"></gmp-place-list> </div> </div> <gmp-place-details style="display: none;"> <gmp-place-details-place-request></gmp-place-details-place-request> <gmp-place-all-content></gmp-place-all-content> </gmp-place-details> </gmp-map> <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: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "alpha"});</script> </body> </html>