Build an Interactive Store Finder with Places UI Kit

Objective

This document walks through the key steps to develop an interactive store finder application using Google Maps Platform, specifically the Maps JavaScript API and the Places UI Kit: Place Details Element. You'll learn how to create a map that displays store locations, dynamically update a list of visible stores, and display rich place information for each store.

Prerequisites

Familiarity with the following is recommended:

Enable Maps JavaScript API and Places UI Kit on your project.

Verify you have loaded the Maps JavaScript API and imported the required libraries for Advanced Markers and Places UI Kit before getting started. This document also assumes a working knowledge of web development, including HTML, CSS, and JavaScript.

Initial setup

The first step is to add a map to the page. This map will be used to display pins relating to your store locations.

There are two ways to add a map to a page:

  1. Using a gmp-map HTML web component
  2. Using JavaScript

Pick the method that works best for your use case. Both ways of implementing the map will work with this guide.

Demo

This demo shows an example of the store finder in action, displaying Google office locations in the Bay Area. The Place Details Element is shown for each location, alongside some example attributes.

Load and Display Store Locations

In this section, we will load and display your store data on the map. This guide assumes that you have a repository of information about your existing stores to pull from. Your store data can come from various sources, such as your database. For this example, we assume a local JSON file (stores.json) with an array of store objects, each representing one store location. Each object should contain at least a name, location (with lat and lng), and a place_id.

There are a variety of ways to retrieve the Place IDs for your store locations if you don't have these already. See the Place ID documentation for more information.

An example store details entry in your stores.json file could look like this. There are fields for Name, Location (lat/lng), and Place ID. There's an object to hold the store opening hours (truncated). There's also two boolean values to help describe bespoke features of the store location.

{
  "name": "Example Store Alpha",
  "location": { "lat": 51.51, "lng": -0.12 },
  "place_id": "YOUR_STORE_PLACE_ID",
  "opening_hours": { "Monday": "09:00 - 17:00", "...": "..." },
  "new_store_design": true,
  "indoor_seating": false
}

In your JavaScript code, fetch the data for your store locations, and display a pin on the map for each one.

An example of how to do this is as follows. This function takes an object containing the details for the stores, and creates a marker based on the location of each one.

function displayInitialMarkers(storeLocations) {
    if (!AdvancedMarkerElement || !LatLng || !mapElement) return;
    storeLocations.forEach(store => {
        if (store.location) {
            const marker = new AdvancedMarkerElement({
                position: new LatLng(store.location.lat, store.location.lng),
                title: store.name
            });
            mapElement.appendChild(marker);
        }
    });
}

Once you have loaded your stores and have pins representing their locations displayed on the map, create a sidebar using HTML and CSS to display details about your individual stores.

An example of how your store locator could look at this stage is as follows:

image

Listen for Map Viewport Changes

To optimize performance and user experience, update your application to display markers and details in the sidebar only when their corresponding locations are within the visible map area (viewport). This involves listening for map viewport changes, debouncing these events, and then redrawing only the necessary markers.

Attach an event listener to the map's idle event. This event fires after any panning or zooming operations have completed, providing a stable viewport for your calculations.

map.addListener('idle', debounce(updateMarkersInView, 300));

The above code snippet listens for the idle event, and calls a debounce function. Using a debounce function makes sure that your marker update logic only runs after the user has stopped interacting with the map for a short period, improving performance.

function debounce(func, delay) {
    let timeoutId;
    return function(...args) {
        const context = this;
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
            func.apply(context, args);
        }, delay);
    };
}

The above code is an example debounce function. It takes a function and delay argument, which can be seen passed in the idle listener. A 300ms delay is enough to wait for the map to stop moving, without adding a noticeable delay to the UI. Once this timeout has expired, the passed function is called, in this case, updateMarkersInView.

The updateMarkersInView function should perform the following actions:

Clear all existing markers from the map

Check if the location of the store falls within the current map bounds, for example:

if (map.getBounds().contains(storeLatLng)) {
  // logic
}

Within the if statement above, write code to display the markers and store details on the side bar, if the store location falls within the map viewport.

Display rich place details using Place Details Element

At this stage, the application displays all store locations, and users can filter them based on the map viewport. To enhance this, rich details about each store, such as photos, reviews, and accessibility information, is added using the Place Details Element. This example specifically utilizes the Place Details Compact Element.

Each store location in your data source must have a corresponding Place ID. This ID uniquely identifies the location on Google Maps and is essential for fetching its details. You would typically acquire these Place IDs beforehand, and store these against each of your store records.

Integrate Place Details Compact Element in the application

When a store is determined to be within the current map viewport and is being rendered in the sidebar, you can dynamically create and insert a Place Details Compact Element for it.

For the current store being processed, retrieve the Place ID from your data. The Place ID is used to control which place the element will display.

In JavaScript, dynamically create an instance of PlaceDetailsCompactElement. A new PlaceDetailsPlaceRequestElement is also created, the Place ID passed to it, and this is appended to the PlaceDetailsCompactElement. Finally, use PlaceContentConfigElement to configure the content the element will display.

The following function assumes that the necessary Place UI Kit libraries are imported and available in the scope where this function is called, and storeData passed to the function contains place_id.

This function will return the element, and the calling code will be responsible for appending it to the DOM.

function createPlaceDetailsCompactElement(storeData) {
    // Create the main details component
    const detailsCompact = new PlaceDetailsCompactElement();
    detailsCompact.setAttribute('orientation', 'vertical'); // Or 'horizontal'

    // Specify the Place ID
    const placeRequest = new PlaceDetailsPlaceRequestElement();
    placeRequest.place = storeData.place_id;
    detailsCompact.appendChild(placeRequest);

    // Configure which content elements to display
    const contentConfig = new PlaceContentConfigElement();
    // For this example, we'll render media, rating, accessibility, and attribution:
    contentConfig.appendChild(new PlaceMediaElement({ lightboxPreferred: true }));
    contentConfig.appendChild(new PlaceRatingElement());
    contentConfig.appendChild(new PlaceAccessibleEntranceIconElement());
    // Configure attribution
    const placeAttribution = new PlaceAttributionElement();
    placeAttribution.setAttribute('light-scheme-color', 'gray');
    placeAttribution.setAttribute('dark-scheme-color', 'gray');
    contentConfig.appendChild(placeAttribution);
    detailsCompact.appendChild(contentConfig);
    // Return the element
    return detailsCompact;
}

In the example code above, the element is configured to display the place photos, review rating, and accessibility information. This can be customized by adding or removing other available content elements, see the PlaceContentConfigElement documentation for all available options.

The Place Details Compact Element supports styling through CSS custom properties. This lets you tailor its appearance (colors, fonts, etc.) to match your application's design. Apply these custom properties in your CSS file. See the reference documentation for PlaceDetailsCompactElement for the supported CSS properties.

An example of how your application could look at this stage:

image

Enhance the store finder

You've built a solid foundation for your store finder application. Now, consider several ways to extend its functionality and create an even richer, more user-centric experience.

Add Autocomplete

Improve how users find areas to search for stores by integrating a search input with Place Autocomplete. When users type an address, neighborhood, or point of interest and select a suggestion, program the map to automatically center on that location, triggering an update of nearby stores. Achieve this by adding an input field and attaching the Place Autocomplete functionality to it. On select of a suggestion, the map can be centred at that point. Remember to configure it to bias or restrict results to your operational area.

Detect location

Offer immediate relevance, especially for mobile users, by implementing functionality to detect their current geographical location. After obtaining browser permission to detect their location, automatically center the map on their position and display the closest stores. Users highly value this Near Me feature when looking for immediate options. Add a button or an initial prompt to request location access.

Show distance and directions

Once a user identifies a store of interest, significantly enhance their journey by integrating the Routes API. For each store you list, calculate and display the distance from the user's current location, or from the searched location. Furthermore, provide a button or link that uses the Routes API to generate a route from the user's location to the selected store. You can then display this route on your map or link out to Google Maps for navigation, creating a seamless transition from finding a store to actually getting there.

By implementing these extensions, you can use more capabilities of the Google Maps Platform to build a more comprehensive and convenient store locator that directly addresses common user needs.

Conclusion

This guide has demonstrated the core steps to build an interactive store finder. You've learned how to display your own store locations on a map using the Maps JavaScript API, dynamically update the visible stores based on viewport changes, and significantly, how to display your own store data in line with the Places UI Kit. By using your existing store information, including Place IDs, with the Place Details Element, you can present rich, standardized details for each of your locations, creating a robust foundation for a user-friendly store locator.

Try Maps JavaScript API and Places UI Kit to offer powerful, component-based tools for rapidly developing sophisticated location-based applications. By combining these features, you can create engaging and informative experiences for your users.

Contributors

Henrik Valve | DevX Engineer