Store Locator

Overview

This tutorial shows you how to build your company's stores locator. You can choose an address from a side menu to see where an exact store is located on a map. Alternatively, you can select the store on the map to see its details in the side menu.

Prerequisites

To start using the TomTom Maps SDK for Web, you need the following:

  • API Key - If you don't have an API key visit a How to get a TomTom API key site and create one.
  • Coordinates
    How to find your company location coordinates
    The simplest way for obtaining these coordinates is to go to API Explorer. First, click on ** Fuzzy Search** and then on a Try it out button. Let's assume your company address is: '100 Century Center Ct 210, San Jose, CA 95112, USA'. You have to place it in a query field, clear other pre-populated fields, then scroll down and press the Execute button. Please take into account that you should not use special HTML characters (like '$','?',&','#') in the address. In the Response you can find the section and there you can see that the very first item from the list of items matched your query. Look at the property following the address fields. There you can see the data you need:
    1<position>
    2 <lat>37.36729</lat>
    3 <lon>-121.91595</lon>
    4</position>
  • GeoJson data
    Create a JSON file with data
    This tutorial uses sample coordinates and addresses stored in the stores.js file. You can download this stores.js file and adapt it for your application. Replace example coordinates and addresses with your stores' coordinates and addresses. Of course, you can add more data to your application. For example, if you want to have a phone number in your store list, just include the "phone" property in your GeoJSON file like this:
    1"properties": {
    2 "address": "Valkensteeg 3, 1012 MH",
    3 "city": "Amsterdam",
    4 "phone": "0123456789"
    5}

Displaying the map

Step 1. First, divide the screen into two parts.

  • There will be the map on the right side and all store's locations grouped by cities in the list on the left side.
  • If you want to add your company logo above the list:
    • Create a folder named img and put the logo there.
    • Create the following HTML content:
1<!DOCTYPE html>
2<html>
3 <head>
4 <title>Pizzeria locator</title>
5 <!-- Replace version in the URL with desired library version -->
6 <link
7 href="https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/<version>/maps/maps.css"
8 rel="stylesheet"
9 type="text/css"
10 />
11 <script src="https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/<version>/maps/maps-web.min.js"></script>
12 <link
13 href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"
14 rel="stylesheet"
15 />
16 <script src="stores.js" type="text/javascript"></script>
17 <link href="styles.css" rel="stylesheet" type="text/css" />
18 <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
19 <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
20 </head>
21 <body>
22 <div class="control-panel">
23 <div class="heading">
24 <img src="img/logo.png" />
25 </div>
26 <div id="store-list"></div>
27 </div>
28 <div class="map" id="map"></div>
29 <script>
30 const apiKey = "<your-api-key>"
31 const map = tt.map({
32 key: apiKey,
33 container: "map",
34 center: [4.57304, 52.13895],
35 zoom: 9,
36 })
37 </script>
38 </body>
39</html>

Step 2. Now include your styles in the styles.css file:

1html {
2 -webkit-box-sizing: border-box
3 box-sizing: border-box;
4}
5
6*,
7*:before,
8*:after {
9 box-sizing: inherit;
10}
11
12body {
13 color: #707070;
14 font-size: 14px;
15 margin: 0;
16 padding: 0;
17}
18
19a {
20 text-decoration: none;
21}
22
23.map {
24 bottom: 0;
25 left: 25%;
26 position: absolute;
27 top: 0;
28 width: 75%;
29 z-index: -1;
30}
31
32.control-panel {
33 -webkit-box-shadow: 0px 0px 12px 0px rgba(0, 0, 0, 0.3);
34 box-shadow: 0px 0px 12px 0px rgba(0, 0, 0, 0.3);
35 height: 100%;
36 left: 0;
37 overflow: hidden;
38 position: absolute;
39 top: 0;
40 width: 25%;
41}
42
43.heading {
44 background: #fff;
45 border-bottom: 1px solid #eee;
46 -webkit-box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.16);
47 box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.16);
48 position: relative;
49 z-index: 1;
50}
51
52.heading > img {
53 height: auto;
54 margin: 10px 0 8px 0;
55 width: 150px;
56}

You will see:

  • The map on the right side of your application.
  • The heading on the left side in a white space.

You will build the list with stores there later.

Adding markers to the map

Step 1. To create markers on the map, copy and paste the following code to your index.html file:

1stores.features.forEach(function (store, index) {
2 const city = store.properties.city
3 const address = store.properties.address
4 const location = store.geometry.coordinates
5 let cityStoresList = document.getElementById(city)
6 const marker = new tt.Marker()
7 .setLngLat(location)
8 .setPopup(new tt.Popup({ offset: 35 }).setHTML(address))
9 .addTo(map)
10})

Step 2. The following script creates markers by iterating through the JSON file.

  1. You need to define the address, location, marker, city, and cityStoresList variables and get that data from your JSON.
    • For now, you will see markers on the map and pop-ups showing up after clicking the specific marker.
  2. Now, add the names of the cities to the menu on the left, and then add event listeners to them.
    • After doing this, you will be zoomed in on this city on the map after clicking the city on the list.
  3. When the script finds a new city in the store list, it creates:
    • a HTML5 h3 element (heading with the name of the city) and
    • a div (the container for all stores in this city) and adds them to the application.
  4. If the script adds a store with the city that already exists on the list, then an else statement is executed where we add the marker to the appropriate markersCity.
    • By using the markersCity empty array, you can properly group your JSON data by city.
    • By adding an event listener on cityStoresListHeading, you can capture a click on a city header and set a map view on this city.
    1const markersCity = [];
    2const list = document.getElementById('store-list');
    3stores.features.forEach(function (store, index) {
    4[..]
    5 const city = store.properties.city;
    6 const address = store.properties.address;
    7 const location = store.geometry.coordinates;
    8 const marker = new tt.Marker({}).setLngLat(location).setPopup(new tt.Popup({offset: 35}).setHTML(address)).addTo(map);
    9 markersCity[index] = {marker, city};
    10 let cityStoresList = document.getElementById(city);
    11 if (cityStoresList === null) {
    12 const cityStoresListHeading = list.appendChild(document.createElement('h3'));
    13 cityStoresListHeading.innerHTML = city;
    14 cityStoresList = list.appendChild(document.createElement('div'));
    15 cityStoresList.id = city;
    16 cityStoresList.className = 'list-entries-container';
    17 cityStoresListHeading.addEventListener('click', function (e) {
    18 map.fitBounds(getMarkersBoundsForCity(e.target.innerText), {padding: 50});
    19 });
    20 }
    21 const details = buildLocation(cityStoresList, address);
    22 function getMarkersBoundsForCity(city) {
    23 const bounds = new tt.LngLatBounds();
    24 markersCity.forEach(markerCity => {
    25 if (markerCity.city === city) {
    26 bounds.extend(markerCity.marker.getLngLat());
    27 }
    28 });
    29 return bounds;
    30 }
    31 [..]

Now you should see the map and markers placed in Amsterdam and Rotterdam, and the names of cities on the left side.

  • Click Amsterdam to zoom-in on the markers located in Amsterdam.
  • Click Rotterdam to zoom-in on the markers located in Rotterdam.

Adding the list of stores

Step 1. To build the list with all of your stores,

1function buildLocation(htmlParent, text) {
2 const details = htmlParent.appendChild(document.createElement("a"))
3 details.href = "#"
4 details.className = "list-entry"
5 details.innerHTML = text
6 return details
7}
var details = buildLocation(cityStoresList, address)
  1. Create the buildLocation function.
  2. Define the details variable.
  3. Create a new element (a) using the HTML DOM appendChild() method. See the following code example:
  4. Now each address has the attribute a href="#" and a class "list-entry".
    • All addresses related to one city are packed in a div that has a class named "city-holder".
  5. Here is the code that executes the buildLocation function:

Step 2. Style your store-list element. See at the following CSS code:

1#store-list {
2 height: 100%;
3 overflow: auto;
4}
5
6#store-list .list-entries-container .list-entry {
7 border-bottom: 1px solid #e8e8e8;
8 display: block;
9 padding: 10px 50px 10px;
10}
11
12#store-list .list-entries-container .list-entry:nth-of-type(even) {
13 background-color: #f5f5f5;
14}
15
16#store-list .list-entries-container .list-entry:hover,
17#store-list .list-entries-container .list-entry.selected {
18 background-color: #cdde75;
19 border-bottom-color: #cdde75;
20}

Defining interactive functions on the markers and the list

Step 1. After defining the details variable, and before adding the buildLocation function:

  1. Add an event listener on the marker.
  2. After clicking the marker, we can see the relevant element on the list. See the following code example:
1marker._element.addEventListener(
2 "click",
3 (function (details, city) {
4 const activeItem = document.getElementsByClassName("selected")
5 return function () {
6 if (activeItem[0]) {
7 activeItem[0].classList.remove("selected")
8 }
9 details.classList.add("selected")
10 }
11 })(details, city)
12)

The function is enclosed in brackets. This is an “immediately-invoked function expression (IIFE)”. IIFE is a very useful construct which allows the immediate execution of functions as soon as they are created. They also let us isolate variable declarations so there is no need to create global variables. If you want to read more about immediately-invoked function expressions, see the Mozilla documentation: IIFE.

Step 2. Now, do the same thing for the details variable and create the closeAllPopups() function. See the following code example:

1details.addEventListener(
2 "click",
3 (function (marker) {
4 const activeItem = document.getElementsByClassName("selected")
5 return function () {
6 if (activeItem[0]) {
7 activeItem[0].classList.remove("selected")
8 }
9 details.classList.add("selected")
10 map.easeTo({
11 center: marker.getLngLat(),
12 zoom: 18,
13 })
14 closeAllPopups()
15 marker.togglePopup()
16 }
17 })(marker)
18)
19
20function closeAllPopups() {
21 markersCity.forEach((markerCity) => {
22 if (markerCity.marker.getPopup().isOpen()) {
23 markerCity.marker.togglePopup()
24 }
25 })
26}

Now, if you click on the address on the list you will be zoomed-in on the relevant marker, and the pop-up will show up.

Adding jQuery UI accordion to the app

Use the jQuery UI accordion to display collapsible content panels. See more about accordion and how to use it at: JQuery UI - Accordion..

1$(function () {
2 $("#store-list").accordion({
3 icons: {
4 header: "ui-icon-plus",
5 activeHeader: "ui-icon-minus",
6 },
7 heightStyle: "content",
8 collapsible: true,
9 active: false,
10 })
11})

Add CSS styles for your accordion:

1.ui-accordion h3.ui-accordion-header {
2 background-color: #f4f6f8;
3 border-color: #dddfe0;
4 border-style: solid;
5 border-width: 0 0 3px 0;
6 color: #707070;
7 display: block;
8 font-size: 1.143em;
9 margin: 0;
10 padding: 15px 20px;
11}
12
13.ui-accordion h3.ui-accordion-header.ui-state-active {
14 color: #fff;
15 background-color: #bdd731;
16 border-bottom-color: #a2ba24;
17}
18
19.ui-accordion .ui-accordion-content {
20 border: none;
21 padding: 0;
22}
23
24.ui-icon,
25.ui-widget-content .ui-icon {
26 margin-right: 15px;
27}

Your application is almost ready! But you can still improve one thing.

  1. Notice that when you have the Amsterdam list dropped down and you click on some marker located in Rotterdam, you cannot see the relevant element on the list because the Rotterdam stores list is collapsed.
  2. You can fix this by adding the following function:
1function openCityTab(selected_id) {
2 const storeListElement = $("#store-list")
3 const index = storeListElement.find("div.list-entries-container")
4 for (let j = 0; j < index.length; j++) {
5 if (index[j].id === selected_id) {
6 storeListElement.accordion("option", {
7 active: j,
8 })
9 }
10 }
11}

Add the openCityTab function execution within the event listener attached to the marker:

1marker.getElement().addEventListener(
2 "click",
3 (function (city) {
4 const activeItem = document.getElementsByClassName("selected")
5 return function () {
6 if (activeItem[0]) {
7 activeItem[0].classList.remove("selected")
8 }
9 details.classList.add("selected")
10 openCityTab(city)
11 }
12 })(city)
13)

Summary

From this tutorial you have learned:

  • How to create a list with your stores and their locations using GeoJSON.
  • How to show your stores on the map.
  • How to define pop-ups for each of the markers.
  • How to build a side-menu listing your stores (grouped by city).
  • How to use jQuery UI accordion to create collapsible drop-down lists for each group.
  • How to attach event listeners: click on group, click on marker, click on list - in order to allow different application interactions.

You can find all of the source code at: GitHub.