Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 49 additions & 21 deletions samples/ui-kit-place-search-nearby/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,67 @@
<!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>
<title>Place Search Nearby with Google Maps</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="style.css" />
<script type="module" src="./index.js" defer></script>
<!-- prettier-ignore -->
<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: "weekly", internalUsageAttributionIds: "gmp_mcp_codeassist_v0.1_github"});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

internalUsageAttributionIds maybe should be removed? (leave that to show external usage of MCP?)

</script>
</head>
<body>
<!--[START maps_ui_kit_place_search_nearby_map] -->
<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="container">
<gmp-map center="-37.813,144.963" zoom="16" map-id="DEMO_MAP_ID" disable-default-ui></gmp-map>
<div class="ui-panel">
<div class="controls">
<select name="types" class="type-select">
<option value="">Select a place type</option>
<option value="cafe">Cafe</option>
<label for="type-select">Select a place type:</label>
<select id="type-select" class="type-select">
<option value="cafe" selected>Cafe</option>
<option value="restaurant">Restaurant</option>
<option value="electric_vehicle_charging_station">
EV charging station
</option>
<option value="electric_vehicle_charging_station"
>EV charging station</option
>
Comment on lines +26 to +32

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may be simpler with label wrapping vs for-id.

TBD when seeing the desired UI

</select>
</div>
<div class="list-container">
<gmp-place-list selectable style="display: none;"></gmp-place-list>
<gmp-place-search orientation="vertical" selectable>
<gmp-place-all-content></gmp-place-all-content>
<gmp-place-nearby-search-request
location-restriction="1000@-37.813, 144.963">
</gmp-place-nearby-search-request>
</gmp-place-search>
</div>
</div>
</gmp-map>
</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>
<div id="details-container">
<gmp-place-details-compact orientation="horizontal"
truncation-preferred
style="width: 400px;
padding: 0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a comment for why this is inline style instead of stylesheet?

margin: 0;
border: none;
background-color: transparent;
color-scheme: light;">
<gmp-place-details-place-request
place="places/ChIJ3S-JXmauEmsRUcIaWtf4MzE"></gmp-place-details-place-request>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could have no place id to start, and then have the rest of the init flow populate it from the first, auto-triggered, search results?

<gmp-place-content-config>
<gmp-place-media></gmp-place-media>
<gmp-place-rating></gmp-place-rating>
<gmp-place-price></gmp-place-price>
<gmp-place-accessible-entrance-icon></gmp-place-accessible-entrance-icon>
<gmp-place-open-now-status></gmp-place-open-now-status>
<gmp-place-attribution
light-scheme-color="gray"
dark-scheme-color="white"></gmp-place-attribution>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider a comment on why the attribution is getting color locked this way

</gmp-place-content-config>
</gmp-place-details-compact>
</div>

<!--[END maps_ui_kit_place_search_nearby_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>
<!--[END maps_ui_kit_place_search_nearby] -->
<!--[END maps_ui_kit_place_search_nearby] -->
246 changes: 140 additions & 106 deletions samples/ui-kit-place-search-nearby/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,132 +6,166 @@
/* [START maps_ui_kit_place_search_nearby] */

/* [START maps_ui_kit_place_search_nearby_query_selectors] */
const map = document.querySelector("gmp-map") as any;
const placeList = document.querySelector("gmp-place-list") as any;
const typeSelect = document.querySelector(".type-select") as any;
const placeDetails = document.querySelector("gmp-place-details") as any;
const placeDetailsRequest = document.querySelector('gmp-place-details-place-request') as any;
// Query selectors for various elements in the HTML file.
const map = document.querySelector('gmp-map') as google.maps.MapElement
const placeSearch = document.querySelector('gmp-place-search') as any
const placeSearchQuery = document.querySelector(
'gmp-place-nearby-search-request'
) as any
const placeDetails = document.querySelector('gmp-place-details-compact') as any
const placeRequest = document.querySelector(
'gmp-place-details-place-request'
) as any

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any because we're pending typing update pushing, right?

const typeSelect = document.querySelector('.type-select') as HTMLSelectElement
/* [END maps_ui_kit_place_search_nearby_query_selectors] */
let markers = {};
let infoWindow;

async function initMap(): Promise<void> {
await google.maps.importLibrary('places');
const { LatLngBounds } = await google.maps.importLibrary('core') as google.maps.CoreLibrary;
const { InfoWindow } = await google.maps.importLibrary('maps') as google.maps.MapsLibrary;
const { spherical } = await google.maps.importLibrary('geometry') as google.maps.GeometryLibrary;

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();

// Global variables for the map, markers, and info window.
const markers: Map<string, google.maps.marker.AdvancedMarkerElement> = new Map()
let infoWindow

let AdvancedMarkerElement: typeof google.maps.marker.AdvancedMarkerElement
let LatLngBounds: typeof google.maps.LatLngBounds

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider - do we need all these to be globals?


// The init function is called when the page loads.
async function init(): Promise<void> {
// Import the necessary libraries from the Google Maps API.
const { InfoWindow } = (await google.maps.importLibrary(
'maps'
)) as google.maps.MapsLibrary
await google.maps.importLibrary('places')
const markerLib = (await google.maps.importLibrary(
'marker'
)) as google.maps.MarkerLibrary
const coreLib = (await google.maps.importLibrary(
'core'
)) as google.maps.CoreLibrary
Comment on lines +32 to +41

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe have these load in parallel, instead of in sequence?


AdvancedMarkerElement = markerLib.AdvancedMarkerElement
LatLngBounds = coreLib.LatLngBounds
Comment on lines +43 to +44

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest {} = ... destructuring inline above instead


// Create a new info window and set its content to the place details element.
infoWindow = new InfoWindow({
content: placeDetails,
ariaLabel: 'Place Details',
headerDisabled: true,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's check with a11y - what's the impact of disabling the header without offering our a custom close button?

pixelOffset: { width: 0, height: -40 } as google.maps.Size,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious: why is a typecast needed here?

})

// Set the map options.
map.innerMap.setOptions({
mapTypeControl: false,
clickableIcons: false,
});
mapTypeControl: false,
streetViewControl: false,
})

// Add a click listener to the map to hide the info window when the map is clicked.
map.innerMap.addListener('click', () => {
hideInfoWindow()
})
/* [START maps_ui_kit_place_search_nearby_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;
// Add event listeners to the type select and place search elements.
typeSelect.addEventListener('change', searchPlaces)

placeSearch.addEventListener('gmp-select', (event: Event) => {
const { place } = event as any
if (markers.has(place.id)) {
markers.get(place.id)!.click()
}
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();
});
})
placeSearch.addEventListener(
'gmp-load',
() => {
searchPlaces()
},
{ once: true }
)
}
/* [END maps_ui_kit_place_search_nearby_event] */
// The searchPlaces function is called when the user changes the type select or when the page loads.
function searchPlaces() {
// Get the map bounds and center.
const bounds = map.innerMap.getBounds()!
const cent = map.innerMap.getCenter()!
const ne = bounds.getNorthEast()
const sw = bounds.getSouthWest()
// Calculate the diameter of the map bounds and cap the radius at 50000.
const diameter = google.maps.geometry.spherical.computeDistanceBetween(
ne,
sw
)
const cappedRadius = Math.min(diameter / 2, 50000) // Radius cannot be more than 50000.

// Close the info window and clear the markers.
infoWindow.close()

for (const marker of markers.values()) {
marker.map = null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
marker.map = null
marker.remove();

tiny tiny bit better practice, I think

}
markers.clear()

// Set the place search query and add an event listener to the place search element.
if (typeSelect.value) {
// map.style.height = '75vh';
placeSearch.style.visibility = 'visible'
placeSearchQuery.maxResultCount = 10
placeSearchQuery.locationRestriction = {
center: { lat: cent.lat(), lng: cent.lng() },
radius: cappedRadius,
}
});
/* [END maps_ui_kit_place_search_nearby_event] */
placeSearchQuery.includedTypes = [typeSelect.value]
placeSearch.addEventListener('gmp-load', () => addMarkers(), {
once: true,
})
}
}

async function addMarkers(){
const { AdvancedMarkerElement } = await google.maps.importLibrary('marker') as google.maps.MarkerLibrary;
const { LatLngBounds } = await google.maps.importLibrary('core') as google.maps.CoreLibrary;

const bounds = new LatLngBounds();
// The addMarkers function is called when the place search element loads.
async function addMarkers() {
// Create a new LatLngBounds object and make the place search element visible.
const bounds = new LatLngBounds()
placeSearch.style.visibility = 'visible'

if(placeList.places.length > 0){
placeList.places.forEach((place) => {
let marker = new AdvancedMarkerElement({
// Iterate over the places and create a marker for each place.
if (placeSearch.places.length > 0) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (placeSearch.places.length > 0) {

placeSearch.places.forEach is a no-op if len=0. (i.e. safe to call)

placeSearch.places.forEach((place) => {
const marker = new AdvancedMarkerElement({
map: map.innerMap,
position: place.location
});
position: place.location,
collisionBehavior:
google.maps.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL,
})

markers[place.id] = marker;
bounds.extend(place.location);
// Add the marker to the markers map and extend the bounds.
markers.set(place.id, marker)
bounds.extend(place.location)

/* [START maps_ui_kit_place_search_nearby_click_event] */
marker.addListener('gmp-click', (event) => {
if(infoWindow.isOpen){
infoWindow.close();
// Add a click listener to the marker to show the info window.
marker.addListener('click', () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
marker.addListener('click', () => {
marker.addEventListener('click', () => {

if (place.viewport) {
map.innerMap.fitBounds(place.viewport)
} else {
map.innerMap.panTo(place.location)
map.innerMap.setZoom(18)
}

placeDetailsRequest.place = place.id;
placeDetails.style.display = 'block';
placeDetails.style.width = '350px';
infoWindow.setOptions({
content: placeDetails,
});
infoWindow.open({
anchor: marker,
map: map.innerMap
});
});
placeRequest.place = place
infoWindow.setPosition(place.location)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider attaching the info window to the marker instead? I'd need to check, but I think it will look a tad nicer, taking certain offsets into account

placeDetails.style.visibility = 'visible' // Ensure place details are visible
infoWindow.open(map.innerMap)
})
/* [END maps_ui_kit_place_search_nearby_click_event] */

map.innerMap.setCenter(bounds.getCenter());
map.innerMap.fitBounds(bounds);
});
})
// Set the map center and fit the bounds.
map.innerMap.setCenter(bounds.getCenter())
map.innerMap.fitBounds(bounds)
Comment on lines +159 to +160

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting - does this particular set of calls produce a particularly desired effect? (I usually just see fitBounds or setBounds called - but no extra setCenter)

}
}

async function findCurrentLocation(){
const { LatLng } = await google.maps.importLibrary('core') as google.maps.CoreLibrary;
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);
}

// The hideInfoWindow function is called when the user clicks on the map.
function hideInfoWindow() {
infoWindow.close()
placeDetails.style.visibility = 'hidden'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

once the placeDetails is moved to inside the IW, it will naturally only be shown when the IW is visible.
consider removing this function, omitting code that handles placeDetails's visibility, and just controlling the set via the IW

}

initMap();
init()
/* [END maps_ui_kit_place_search_nearby] */
Loading