Build a store locator using Mapbox.js
Familiarity with front-end development concepts. Some advanced JavaScript required.
Mapbox.js is no longer in active development. To learn more about our newer mapping tools see Build a store locator with Mapbox GL JS.
In this tutorial, you'll learn how to create a store locator map. Using your new map, you'll be able to browse through a list of locations from a sidebar and select a specific store to view more information. Selecting a marker on the map will highlight the selected store on the sidebar.
For this tutorial, you're going to use Sweetgreen, a local salad shop, as an example. They have a healthy number of locations, plus their salads are delicious.
This guide gets into a few more advanced JavaScript concepts with Mapbox.js. If you're new to Mapbox.js, we recommend you read our Extending with Mapbox.js guide first.
Getting started
Before writing any code, create a local folder called "building-a-store-locator-js" to house your project files. This folder is referred to as your project folder from here on out.
There are a few resources you'll need to gather before we get started:
- A tileset ID. A tileset ID points to a unique map you have created with Mapbox. For the purposes of this guide, you'll use the tileset ID
mapbox.k8xv42t9
, but you can substitute your own custom tileset if you like! - Your access token. The token is used to associate a map with your account:
L.mapbox.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
Mapbox.js. The Mapbox JavaScript API for building maps.
Data. We collected Sweetgreen's locations and marked up the data in GeoJSON for your convenience.
Custom map marker. Your finished map will have some fancy custom images for its markers. Download and save the image to your project folder.
- A text editor. You'll be writing HTML, CSS, and JavaScript after all.
Add structure
In your project folder, create an index.html
file. Set up the document by adding reference the Mapbox.js JavaScript library and its accompanying CSS:
<script src='https://api.mapbox.com/mapbox.js/v3.2.1/mapbox.js'></script>
<link href='https://api.mapbox.com/mapbox.js/v3.2.1/mapbox.css' rel='stylesheet' />
Next, markup the page to create a map container and sidebar listing:
<div class='sidebar pad2'>Listing</div>
<div id='map' class='map pad2'>Map</div>
Apply a bit of CSS so you can visualize what the layout looks like:
body {
background: #404040;
color: #f8f8f8;
font: 500 20px/26px 'Helvetica Neue', Helvetica, Arial, sans-serif;
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
}
/**
* The page is split between map and sidebar - the sidebar gets 1/3, map
* gets 2/3 of the page. You can adjust this to your personal liking.
*/
.sidebar {
width: 33.3333%;
}
.map {
border-left: 1px solid #fff;
position: absolute;
left: 33.3333%;
width: 66.6666%;
top: 0;
bottom: 0;
}
.pad2 {
padding: 20px;
box-sizing: border-box;
}
Initialize map
Now that you have set up the structure of the project, bring your map to life by initializing it with Mapbox.js.
First, create an instance of L.mapbox.map
and store it in a variable called map
. You'll also use setView
to focus the map to Washington DC where all the shops are located. Here, you'll need to update your tileset ID and access token. Add the script under your HTML:
L.mapbox.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const map = L.mapbox
.map('map')
.setView([38.909671288923, -77.034084142948], 13)
.addLayer(L.mapbox.styleLayer('mapbox://styles/mapbox/light-v11'));
Load data
Before you put any markers on your map, store all the GeoJSON data you downloaded above in a variable called geojson
. Once the map is initialized, you can use featureLayer.setGeoJSON
to load features on the map by referencing the geojson
variable:
L.mapbox.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const geojson = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-77.034084142948,
38.909671288923
]
},
"properties": {
"phoneFormatted": "(202) 234-7336",
"phone": "2022347336",
"address": "1471 P St NW",
"city": "Washington DC",
"country": "United States",
"crossStreet": "at 15th St NW",
"postalCode": "20005",
"state": "D.C."
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-77.049766,
38.900772
]
},
"properties": {
"phoneFormatted": "(202) 507-8357",
"phone": "2025078357",
"address": "2221 I St NW",
"city": "Washington DC",
"country": "United States",
"crossStreet": "at 22nd St NW",
"postalCode": "20037",
"state": "D.C."
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-77.043929,
38.910525
]
},
"properties": {
"phoneFormatted": "(202) 387-9338",
"phone": "2023879338",
"address": "1512 Connecticut Ave NW",
"city": "Washington DC",
"country": "United States",
"crossStreet": "at Dupont Circle",
"postalCode": "20036",
"state": "D.C."
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-77.0672,
38.90516896
]
},
"properties": {
"phoneFormatted": "(202) 337-9338",
"phone": "2023379338",
"address": "3333 M St NW",
"city": "Washington DC",
"country": "United States",
"crossStreet": "at 34th St NW",
"postalCode": "20007",
"state": "D.C."
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-77.002583742142,
38.887041080933
]
},
"properties": {
"phoneFormatted": "(202) 547-9338",
"phone": "2025479338",
"address": "221 Pennsylvania Ave SE",
"city": "Washington DC",
"country": "United States",
"crossStreet": "btwn 2nd & 3rd Sts. SE",
"postalCode": "20003",
"state": "D.C."
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-76.933492720127,
38.99225245786
]
},
"properties": {
"address": "8204 Baltimore Ave",
"city": "College Park",
"country": "United States",
"postalCode": "20740",
"state": "MD"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-77.097083330154,
38.980979
]
},
"properties": {
"phoneFormatted": "(301) 654-7336",
"phone": "3016547336",
"address": "4831 Bethesda Ave",
"cc": "US",
"city": "Bethesda",
"country": "United States",
"postalCode": "20814",
"state": "MD"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-77.359425054188,
38.958058116661
]
},
"properties": {
"phoneFormatted": "(571) 203-0082",
"phone": "5712030082",
"address": "11935 Democracy Dr",
"city": "Reston",
"country": "United States",
"crossStreet": "btw Explorer & Library",
"postalCode": "20190",
"state": "VA"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-77.10853099823,
38.880100922392
]
},
"properties": {
"phoneFormatted": "(703) 522-2016",
"phone": "7035222016",
"address": "4075 Wilson Blvd",
"city": "Arlington",
"country": "United States",
"crossStreet": "at N Randolph St.",
"postalCode": "22203",
"state": "VA"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-75.28784,
40.008008
]
},
"properties": {
"phoneFormatted": "(610) 642-9400",
"phone": "6106429400",
"address": "68 Coulter Ave",
"city": "Ardmore",
"country": "United States",
"postalCode": "19003",
"state": "PA"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-75.20121216774,
39.954030175164
]
},
"properties": {
"phoneFormatted": "(215) 386-1365",
"phone": "2153861365",
"address": "3925 Walnut St",
"city": "Philadelphia",
"country": "United States",
"postalCode": "19104",
"state": "PA"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-77.043959498405,
38.903883387232
]
},
"properties": {
"phoneFormatted": "(202) 331-3355",
"phone": "2023313355",
"address": "1901 L St. NW",
"city": "Washington DC",
"country": "United States",
"crossStreet": "at 19th St",
"postalCode": "20036",
"state": "D.C."
}
}
]
};
const map = L.mapbox
.map('map')
.setView([38.909671288923, -77.034084142948], 13)
.addLayer(L.mapbox.styleLayer('mapbox://styles/mapbox/light-v11'))
.featureLayer.setGeoJSON(geojson);
Alternatively, you can save the GeoJSON as a .geojson
file and load the file on to the map. If you do this, you will need to run your application from a local web server (something like http://localhost/building-a-store-locator-js.html
). If you try to open your index.html
file directly in your browser, you will receive a Cross-origin Resource Sharing (CORS) error.
Build listings and tooltips
You have some markers on the map, but you still need to build the listings and tooltips. Rather than creating each manually, you can put JavaScript to work to read through the GeoJSON and dynamically build listings and set tooltips for us. This means that if you need to add a location then you only need to update the GeoJSON.
L.mapbox.featureLayer
allows you to iterate over data before it is added to the map using the eachLayer
method. This means that JavaScript will run through each point, populate the custom tooltip, add each location listing to the sidebar, and listen for and respond to a user's click event to track when a marker or listing is selected:
const locations = L.mapbox.featureLayer().addTo(map);
locations.setGeoJSON(geojson);
locations.eachLayer((locale) => {
// Iterate over each marker.
});
The variable locations
references your new L.mapbox.featureLayer
, then taps into eachLayer
. The passed parameter locale
is each marker object.
At this point you can start populating content into each listing using information provided by each locale
. Nice! But you still need to update the sidebar HTML to hold the listing information:
<div class='sidebar'>
<div class='heading'>
<h1>Our locations</h1>
</div>
<div id='listings' class='listings'></div>
</div>
Next, update your CSS to accommodate your layout changes:
body {
color: #404040;
font: 400 15px/22px 'Helvetica Neue', sans-serif;
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
}
* {
box-sizing: border-box;
}
h1 {
font-size: 22px;
margin: 0;
font-weight: 400;
}
a {
color: #404040;
text-decoration: none;
}
a:hover {
color: #101010;
}
.sidebar {
position: absolute;
width: 33.3333%;
height: 100%;
top: 0;
left: 0;
overflow: hidden;
border-right: 1px solid rgba(0, 0, 0, 0.25);
}
.pad2 {
padding: 20px;
}
.quiet {
color: #888;
}
.map {
position: absolute;
left: 33.3333%;
width: 66.6666%;
top: 0;
bottom: 0;
}
.heading {
background: #fff;
border-bottom: 1px solid #eee;
height: 60px;
line-height: 60px;
padding: 0 10px;
}
.listings {
height: 100%;
overflow: auto;
padding-bottom: 60px;
}
.listings .item {
border-bottom: 1px solid #eee;
padding: 10px;
text-decoration: none;
}
.listings .item:last-child {
border-bottom: none;
}
.listings .item .title {
display: block;
color: #00853e;
font-weight: 700;
}
.listings .item .title small {
font-weight: 400;
}
.listings .item.active .title,
.listings .item .title:hover {
color: #8cc63f;
}
.listings .item.active {
background-color: #f8f8f8;
}
::-webkit-scrollbar {
width: 3px;
height: 3px;
border-left: 0;
background: rgba(0, 0, 0, 0.1);
}
::-webkit-scrollbar-track {
background: none;
}
::-webkit-scrollbar-thumb {
background: #00853e;
border-radius: 0;
}
/* Marker tweaks */
.leaflet-popup-close-button {
display: none;
}
.leaflet-popup-content {
font: 400 15px/22px 'Source Sans Pro', 'Helvetica Neue', sans-serif;
padding: 0;
width: 200px;
}
.leaflet-popup-content-wrapper {
padding: 0;
}
.leaflet-popup-content h3 {
background: #91c949;
color: #fff;
margin: 0;
display: block;
padding: 10px;
border-radius: 3px 3px 0 0;
font-weight: 700;
margin-top: -15px;
}
.leaflet-popup-content div {
padding: 10px;
}
.leaflet-container .leaflet-marker-icon {
cursor: pointer;
}
Now you can put the finishing touches on your script. Outside of eachLayer
, create a variable called listings
that selects the listing container in the HTML. With this selected you can append each listing into the sidebar:
const listings = document.getElementById('listings');
locations.eachLayer((locale) => {
// Shorten locale.feature.properties to `prop` so we're not
// writing this long form over and over again.
const prop = locale.feature.properties;
const listing = listings.appendChild(document.createElement('div'));
listing.className = 'item';
const link = listing.appendChild(document.createElement('a'));
link.href = '#';
link.className = 'title';
link.innerHTML = `${prop.address}`;
if (prop.crossStreet) {
link.innerHTML += ` <br /><small>${prop.crossStreet}</small>`;
}
const details = listing.appendChild(document.createElement('div'));
details.innerHTML = `${prop.city}`;
if (prop.phone) {
details.innerHTML += ` · ${prop.phoneFormatted}`;
}
});
The link
variable should have the special behavior of panning to its associated marker on the map. By binding an onclick
event, you can target the current locale
object in context, pan the map to the object's coordinates, and trigger its tooltip to appear.
link.onclick = function () {
// 1. Toggle an active class for `listing`. View the source in the demo link for example.
// 2. When a menu item is clicked, animate the map to center
// its associated locale and open its popup.
map.setView(locale.getLatLng(), 16);
locale.openPopup();
};
Each marker tooltip requires unique information. Use the bindPopup
method to pass content to your popups.
const popup = 'Sweetgreen';
locale.bindPopup(popup);
Finally, make sure that when a user clicks a marker that it sets the associated listing in the sidebar as active by binding a click
event handler to each locale
.
locale.on('click', () => {
// 1. Toggle an active class for `listing`. View the source in the demo link for example.
// 2. center the map on the selected marker.
map.setView(locale.getLatLng(), 16);
});
At this point, your whole script should look like:
const listings = document.getElementById('listings');
const locations = L.mapbox.featureLayer().addTo(map);
locations.setGeoJSON(geojson);
function setActive(el) {
const siblings = listings.getElementsByTagName('div');
for (const sibling of siblings) {
sibling.classList.remove('active');
}
el.classList.add('active');
}
locations.eachLayer((locale) => {
// Shorten locale.feature.properties to `prop` so you don't
// have to write this long form over and over again.
const prop = locale.feature.properties;
// Each marker on the map.
let popup = `<h3>Sweetgreen</h3><div>${prop.address}`;
const listing = listings.appendChild(document.createElement('div'));
listing.className = 'item';
const link = listing.appendChild(document.createElement('a'));
link.href = '#';
link.className = 'title';
link.innerHTML = `${prop.address}`;
if (prop.crossStreet) {
link.innerHTML += `<br /><small class="quiet">${prop.crossStreet}</small>`;
popup += `<br /><small class="quiet">${prop.crossStreet}</small>`;
}
const details = listing.appendChild(document.createElement('div'));
details.innerHTML = `${prop.city}`;
if (prop.phone) {
details.innerHTML += ` · ${prop.phoneFormatted}`;
}
link.onclick = function () {
setActive(listing);
// When a menu item is clicked, animate the map to center
// its associated locale and open its popup.
map.setView(locale.getLatLng(), 16);
locale.openPopup();
return false;
};
// Marker interaction
locale.on('click', () => {
// 1. center the map on the selected marker.
map.panTo(locale.getLatLng());
// 2. Set active the markers associated listing.
setActive(listing);
});
locale.bindPopup(popup);
});
There's a lot of code inside the eachLayer
function because it makes sure each event is bound to its respective elements.
Add custom markers
You could add style to our markers by adding property keys to each feature in the GeoJSON, but for this example you want to give your stores unique and fancy markers.
To give your markers a custom icon, use the setIcon
function inside the locations.eachLayer
function you created earlier. In setIcon
, pass L.icon
with your own custom options by copying and pasting the following right after locale.bindPopup(popup);
:
locale.setIcon(
L.icon({
iconUrl: 'marker.png',
iconSize: [56, 56],
iconAnchor: [28, 28],
popupAnchor: [0, -34]
})
);
Freshen up your map's type with the Source Sans Pro font:
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700' rel='stylesheet'>
You'll also need to update the font
property in body
style:
font:400 15px/22px 'Source Sans Pro', 'Helvetica Neue', sans-serif;
Finished product
You have completed the store locator!
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><title>Demo: Build a store locator using Mapbox.js</title><meta name="viewport" content="width=device-width, initial-scale=1" /><linkhref="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700"rel="stylesheet"/><script src="https://api.mapbox.com/mapbox.js/v3.2.1/mapbox.js"></script><linkhref="https://api.mapbox.com/mapbox.js/v3.2.1/mapbox.css"rel="stylesheet"/><style>body {color: #404040;font: 400 15px/22px 'Source Sans Pro', 'Helvetica Neue', sans-serif;margin: 0;padding: 0;-webkit-font-smoothing: antialiased;}* {box-sizing: border-box;}h1 {font-size: 22px;margin: 0;font-weight: 400;}a {color: #404040;text-decoration: none;}a:hover {color: #101010;} .sidebar {position: absolute;width: 33.3333%;height: 100%;top: 0;left: 0;overflow: hidden;border-right: 1px solid rgba(0, 0, 0, 0.25);}.pad2 {padding: 20px;}.quiet {color: #888;}.map {position: absolute;left: 33.3333%;width: 66.6666%;top: 0;bottom: 0;}.heading {background: #fff;border-bottom: 1px solid #eee;min-height: 60px;line-height: 60px;padding: 0 10px;}.listings {height: 100%;overflow: auto;padding-bottom: 60px;}.listings .item {border-bottom: 1px solid #eee;padding: 10px;text-decoration: none;}.listings .item:last-child {border-bottom: none;}.listings .item .title {display: block;color: #00853e;font-weight: 700;}.listings .item .title small {font-weight: 400;}.listings .item.active .title,.listings .item .title:hover {color: #8cc63f;}.listings .item.active {background-color: #f8f8f8;} ::-webkit-scrollbar {width: 3px;height: 3px;border-left: 0;background: rgba(0, 0, 0, 0.1);}::-webkit-scrollbar-track {background: none;}::-webkit-scrollbar-thumb {background: #00853e;border-radius: 0;} /* Marker tweaks */.leaflet-popup-close-button {display: none;}.leaflet-popup-content {font: 400 15px/22px 'Source Sans Pro', 'Helvetica Neue', sans-serif;padding: 0;width: 200px;}.leaflet-popup-content-wrapper {padding: 0;}.leaflet-popup-content h3 {background: #91c949;color: #fff;margin: 0;display: block;padding: 10px;border-radius: 3px 3px 0 0;font-weight: 700;margin-top: -15px;}.leaflet-popup-content div {padding: 10px;}.leaflet-container .leaflet-marker-icon {cursor: pointer;}</style></head><body><div class="sidebar"><div class="heading"><h1>Our locations</h1></div><div id="listings" class="listings"></div></div><div id="map" class="map"></div><script>L.mapbox.accessToken = '<your access token here>';const geojson = [{'type': 'FeatureCollection','features': [{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-77.034084142948, 38.909671288923]},'properties': {'phoneFormatted': '(202) 234-7336','phone': '2022347336','address': '1471 P St NW','city': 'Washington DC','country': 'United States','crossStreet': 'at 15th St NW','postalCode': '20005','state': 'D.C.'}},{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-77.049766, 38.900772]},'properties': {'phoneFormatted': '(202) 507-8357','phone': '2025078357','address': '2221 I St NW','city': 'Washington DC','country': 'United States','crossStreet': 'at 22nd St NW','postalCode': '20037','state': 'D.C.'}},{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-77.043929, 38.910525]},'properties': {'phoneFormatted': '(202) 387-9338','phone': '2023879338','address': '1512 Connecticut Ave NW','city': 'Washington DC','country': 'United States','crossStreet': 'at Dupont Circle','postalCode': '20036','state': 'D.C.'}},{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-77.0672, 38.90516896]},'properties': {'phoneFormatted': '(202) 337-9338','phone': '2023379338','address': '3333 M St NW','city': 'Washington DC','country': 'United States','crossStreet': 'at 34th St NW','postalCode': '20007','state': 'D.C.'}},{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-77.002583742142, 38.887041080933]},'properties': {'phoneFormatted': '(202) 547-9338','phone': '2025479338','address': '221 Pennsylvania Ave SE','city': 'Washington DC','country': 'United States','crossStreet': 'btwn 2nd & 3rd Sts. SE','postalCode': '20003','state': 'D.C.'}},{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-76.933492720127, 38.99225245786]},'properties': {'address': '8204 Baltimore Ave','city': 'College Park','country': 'United States','postalCode': '20740','state': 'MD'}},{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-77.097083330154, 38.980979]},'properties': {'phoneFormatted': '(301) 654-7336','phone': '3016547336','address': '4831 Bethesda Ave','cc': 'US','city': 'Bethesda','country': 'United States','postalCode': '20814','state': 'MD'}},{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-77.359425054188, 38.958058116661]},'properties': {'phoneFormatted': '(571) 203-0082','phone': '5712030082','address': '11935 Democracy Dr','city': 'Reston','country': 'United States','crossStreet': 'btw Explorer & Library','postalCode': '20190','state': 'VA'}},{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-77.10853099823, 38.880100922392]},'properties': {'phoneFormatted': '(703) 522-2016','phone': '7035222016','address': '4075 Wilson Blvd','city': 'Arlington','country': 'United States','crossStreet': 'at N Randolph St.','postalCode': '22203','state': 'VA'}},{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-75.28784, 40.008008]},'properties': {'phoneFormatted': '(610) 642-9400','phone': '6106429400','address': '68 Coulter Ave','city': 'Ardmore','country': 'United States','postalCode': '19003','state': 'PA'}},{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-75.20121216774, 39.954030175164]},'properties': {'phoneFormatted': '(215) 386-1365','phone': '2153861365','address': '3925 Walnut St','city': 'Philadelphia','country': 'United States','postalCode': '19104','state': 'PA'}},{'type': 'Feature','geometry': {'type': 'Point','coordinates': [-77.043959498405, 38.903883387232]},'properties': {'phoneFormatted': '(202) 331-3355','phone': '2023313355','address': '1901 L St. NW','city': 'Washington DC','country': 'United States','crossStreet': 'at 19th St','postalCode': '20036','state': 'D.C.'}}]}];const map = L.mapbox.map('map').setView([38.909671288923, -77.034084142948], 16).addLayer(L.mapbox.styleLayer('mapbox://styles/mapbox/light-v11')); map.scrollWheelZoom.disable(); const listings = document.getElementById('listings');const locations = L.mapbox.featureLayer().addTo(map); locations.setGeoJSON(geojson); function setActive(el) {const siblings = listings.getElementsByTagName('div');for (const sibling of siblings) {sibling.classList.remove('active');} el.classList.add('active');} locations.eachLayer((locale) => {// Shorten locale.feature.properties to just `prop` so we're not// writing this long form over and over again.const prop = locale.feature.properties; // Each marker on the map.let popup = `<h3>Sweetgreen</h3><div>${prop.address}`; const listing = listings.appendChild(document.createElement('div'));listing.className = 'item'; const link = listing.appendChild(document.createElement('a'));link.href = '#';link.className = 'title'; link.innerHTML = `${prop.address}`;if (prop.crossStreet) {link.innerHTML += `<br /><small class="quiet">${prop.crossStreet}</small>`;popup += `<br /><small class="quiet">${prop.crossStreet}</small>`;} const details = listing.appendChild(document.createElement('div'));details.innerHTML = `${prop.city}`;if (prop.phone) {details.innerHTML += ` · ${prop.phoneFormatted}`;} link.onclick = function () {setActive(listing); // When a menu item is clicked, animate the map to center// its associated locale and open its popup.map.setView(locale.getLatLng(), 16);locale.openPopup();return false;}; // Marker interactionlocale.on('click', () => {// 1. center the map on the selected marker.map.panTo(locale.getLatLng()); // 2. Set active the markers associated listing.setActive(listing);}); locale.bindPopup(popup); locale.setIcon(L.icon({iconUrl: 'marker.png',iconSize: [56, 56],iconAnchor: [28, 28],popupAnchor: [0, -34]}));});</script></body></html>
If you're interested in loading your GeoJSON from a file, you can download the alternative code below. Heads up! You'll need to serve these files to avoid a CORS error.
Download the alternative codeNext steps
Nice job! We hope you're set with the tools to create your own store locator.
For bonus points, if a store's location data is added to Foursquare you could extend this example even further by pulling data directly from Foursquare's API. API requests return geolocated information and details about a place. This would allow for more dynamic data like user submitted images or the total number of check-ins a location has received.