WordPress Plugins: Adding Stores to a Map
Tags: wordpress, javascript, tutorial
Published: 2020-09-11 21:21:08

#WordPress Plugins + TomTom – Part 3

In the previous [two articles in this series](https://developer.tomtom.com/blog/build-different/wordpress-plugins-and-tomtom-admin-panel%C2%A0), we've been creating a WordPress Plugin that uses the TomTom Maps APIs to display a map of storefront locations on a business website. 

We started creating a WordPress Plugin that makes adding the Map Display API to any WordPress-based website quick and easy. So far, we've built the basic Plugin elements that allow the Plugin to appear in the WordPress administrator interface and the admin panel interface of the Plugin. At this point the map appears in the admin panel. 

In this article we'll: 

* Set up the database to save store location data.  
* Add an interface to enable administrators to create the list of store locations.
* Convert store addresses to coordinates by using the Geocoding endpoint of the TomTom Search API.  
* Add markers for store locations in the administrator map.

#Creating a Store Database

We have a map, but no data to display on it. For this example, we'll save store location data in (and retrieve it from) a local SQL database. This is a common option for WordPress sites. 

First, we need to set up the database table. The following command runs a **ttlocator_install()** function when a user activates the Plugin: 

```javascript
  register_activation_hook(__FILE__, 'ttlocator_install');
```

The **ttlocator_install()** itself creates the database table, but only if one does not already exist:

 
```javascript
    function ttlocator_install() { 
        global $wpdb; 
        $table_name = $wpdb->prefix . "tomtom_locator_locations"; 
        $charset_collate = $wpdb->get_charset_collate(); 

        $sql = "CREATE TABLE $table_name ( 
          id mediumint(9) NOT NULL AUTO_INCREMENT, 
          name text, 
          address text, 
          city text, 
          state tinytext, 
          country text, 
          postcode tinytext, 
          latitude decimal(10,6), 
          longitude decimal(10,6), 
          PRIMARY KEY(id) 
        ) $charset_collate;"; 

        require_once(ABSPATH . "wp-admin/includes/upgrade.php"); 
        dbDelta( $sql ); 
    } 
```

The table includes fields for data that the user will enter: the store name, address (including city, state, country and postcode). The database provides a store ID and a primary key.

The Map Display API uses latitude and longitude coordinates to display points of interest on a map. We've included latitude and longitude fields in the database, but we won't make the user find this data on their own. Instead, we'll add a feature that uses the address and the TomTom Search API to find the latitude and longitude automatically.

 #Adding Stores to the Map

Earlier, you may have noticed a call to **ttlocator_add_store_html()**. This function generates HTML that will be used to add new store locations: 

```javascript
   <div class="ttlocator-add-store-page"> 
```

       #Add Store

     Start by adding a store name and address, then click **Lookup** to see the new store on the map.        A street address plus the city and state/province is usually enough. 

          If you're happy with the address marker that pops up on the map, click **Save**.      If not, add more detail to the address and then click **Lookup** again to refine your search. 

```javascript
<div class="ttlocator-row"> 
           <div class="ttlocator-field-label"> 
               <label for="store-name">Store Name</label> 
           </div>
        
<div class="ttlocator-text-field"> 
               <input name="store-name" style="width: 100%" type="text" /> 
           </div> 
       </div> 
     
<div class="ttlocator-row"> 
           <div class="ttlocator-field-label"> 
               <label for="store-address">Store Address</label> 
           </div> 
          
<div class="ttlocator-text-field"> 
               <input name="store-address" style="width: 100%" type="text" /> 
           </div> 
          
<div class="ttlocator-field-button"> 
               <button class="button button-primary ttlocator-lookup-button"> 
                   Lookup 
               </button> 
           </div> 
       </div> 
   
<div class="ttlocator-row ttlocator-lookup-message-area"> 
           <p id="ttlocator-store-lookup-messages"> </p> 
       </div> 
       
<div class="ttlocator-row"> 
           <button class="button ttlocator-add-store-cancel">Cancel</button> 
           
<div class="ttlocator-add-store-save"><button class="button button-primary">Save</button></div> 
       </div> 
   </div> 

   <?php xmp=""?>?php&gt;
```

There’s nothing fancy here, it just generates HTML elements that we will interact with via JavaScript. The user will enter a store name and address, and then click “Lookup” to call the TomTom Search API to find information about the address.

If the address is found, it will be shown on the map. If the users like what they see, they’ll be given an opportunity to add the store to their map database.

#Getting Coordinates with the Search API

Let’s focus on the Lookup button’s click handler, because that’s where the magic happens: 

```javascript
jQuery('.ttlocator-lookup-button').click(function() { 
   var query = jQuery("input[name='store-address']").val(); 
   tomtom.fuzzySearch() 
       .key(window.tomtomSdkKey) 
       .query(query) 
       .go() 
       .then(locateCallback) 
       .catch(function(error) { 
           console.log(error); 
       }); 
}); 
```

We start by getting the value of the store-address input. We’re going to use the address entered here as the query we send to the TomTom Fuzzy Search API. The Fuzzy Search API is able to find addresses even if your user is imprecise about the data they enter. For example, entering “1 Yonge St, Toronto” as a fuzzy search query will result in the correct address being found, even though you didn’t provide a province, country, or post code. As you can see in the call above, we use the TomTom Fuzzy Search API to create the search, pass it our TomTom API key, give it our query, tell it to begin the search, and pass it a callback that will be called then they query is complete.

The **locateCallback()** function is quite large, so let’s quickly walk through it: 

```javascript
function locateCallback(result) { 
   jQuery('#ttlocator-store-lookup-messages').text(''); 
   var filteredResult = result && result.filter(r => r.type === "Point Address") || []; 
   if(filteredResult.length > 0) { 
       jQuery('.ttlocator-add-store-save').show(); 
       var topResult = filteredResult[0]; 
       var address = topResult.address; 
       var newStoreName = jQuery('input[name="store-name"]').val(); 
       // save new store address info so we can add it to database 
       // after user confirms it is correct. 
       newStoreAddress = { 
           streetAddress: address.streetNumber + " " + address.streetName, 
           city: address.municipality.split(",")[0], 
           state: address.countrySubdivision, 
           postCode: address.extendedPostalCode || address.postalCode, 
           country: address.country, 
           lat: topResult.position.lat, 
           lon: topResult.position.lon 
       }; 
 

       var location = [topResult.position.lat, topResult.position.lon]; 
       map.setView(location, 15); 
       var marker = tomtom.L.marker(location).addTo(map); 
       marker.bindPopup("" + newStoreName + "
" + address.freeformAddress) 
           .openPopup(); 
       newMarker = marker; 
   } else { 

       jQuery('#ttlocator-store-lookup-messages').text("Address not found. Try changing the address or adding more information, such as country and zip/postal code.") 

   } 

}  
```

We start by filtering the results returned by the TomTom Fuzzy Search API to remove all results other than those with type “Point Address” — with Fuzzy Search there will sometimes be multiple search results for a given address. One of them, with type “Point Address”, will contain highly accurate information about the address itself. Other results may be about businesses or other POIs (Points of Interest) that reside at the address.

Since we only need the address information, we filter the results to remove everything else. If we find an address, we open a popup on the map so the user can ensure that the new location appears in the correct place.

If we don’t find an address, we inform the user so they can try a different address or add more information about the address they entered.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/db8c6zp0t1zz6nqrecxu.jpg)

#Saving a Store to the Database

Since we’ve made it possible for a user to look up a store location and save it, we need back-end code to add the store to the database. 

We do this in the following function located in **store-locator.php()**: 

```javascript
function ttlocator_add_location() { 
   if (!is_admin()) wp_die(); 
   global $wpdb; 
   $table_name = $wpdb->prefix . "tomtom_locator_locations"; 

 
   $name = wp_strip_all_tags($_POST["name"]); 
   $address = wp_strip_all_tags($_POST["address"]); 
   $city = wp_strip_all_tags($_POST["city"]); 
   $state = wp_strip_all_tags($_POST["state"]); 
   $country = wp_strip_all_tags($_POST["country"]); 
   $postcode = wp_strip_all_tags($_POST["postcode"]); 
   $latitude = wp_strip_all_tags($_POST["latitude"]); 
   $longitude = wp_strip_all_tags($_POST["longitude"]); 


   $success = $wpdb->query($wpdb->prepare(" 
       INSERT INTO $table_name ( 
         name, 
         address, 
         city, 
         state, 
         country, 
         postcode, 
         latitude, 
         longitude 
       ) 

       VALUES (%s, %s, %s, %s, %s, %s, %f, %f); 

   ", array($name, $address, $city, $state, $country, $postcode, $latitude, $longitude))); 

   if(!$success) { 
       status_header(500); 

   } 

   wp_die(); 

} 
```

This function is registered to receive AJAX requests. This will let us submit new stores via JavaScript instead of needing to do a form post. Although there’s nothing wrong with doing it the old-fashioned way, receiving store additions via AJAX gives us more flexibility in the way we’ll build our UI.

We start by verifying that the user is an admin and exit immediately if they’re not. Next, we do a little bit of database setup.

Then, we read all of the data that was submitted in the body of the POST request. We use **wp_strip_all_tags** on every piece of data we read to prevent XSS attacks.

Then, we use a prepared statement to insert the new store location in the database. Finally, if store creation failed, we set an error status code to let the caller know that database insertion failed.

#Rendering Store Markers on the Map

Now that we’ve created our admin page, added a map to it, and are able to save stores in the database, displaying stores on the map is easy. All we’ll need to do is add a marker to the map for each store. Recall that we’ve already made all of our store locations available to JavaScript by storing them in the storeLocations variable. 

Starting on line 20 of **locator.js**, you’ll see the following code: 

```javascript
if(storeLocations.length > 0) { 
   storeLocations.forEach(store => addStoreMarkerToMap(store)); 
   var markerGroup = new tomtom.L.featureGroup(markers); 
   fitMapToMarkerGroup(markerGroup); 
} 
```

You’ll notice that it calls a couple of helper functions: **addStoreMarkerToMap()** and **fitMapToMarkerGroup()**. I’ll include the code for those below, and then we’ll walk through all of our map marker code.

```javascript
function addStoreMarkerToMap(store) { 
   var location = [store.latitude, store.longitude]; 
   var marker = tomtom.L.marker(location).addTo(map); 
   marker.bindPopup("" + store.name + "
" + store.address); 
   markers.push(marker); 
} 


function fitMapToMarkerGroup(markerGroup) { 
   map.fitBounds(markerGroup.getBounds().pad(0.2)); 
   if (map.getZoom() > MAX_ZOOM_ON_LOAD) { 
       map.setZoom(MAX_ZOOM_ON_LOAD); 
   } 

} 
```

Starting at the beginning of our store addition code, we see that we’re calling **addStoreMarkerToMap()** on each store in storeLocations.

Looking in **addStoreMarkerToMap()**, we see that it only takes us three lines of code to add a marker to our map. We start by creating an array containing our store’s latitude and longitude. This is the coordinate format Leaflet is expecting when you ask it to create a map marker.

Next, we create the marker and add it to the map with the following call:

```javascript
var marker = tomtom.L.marker(location).addTo(map); 
```

Then we bind a popup to the marker. The popup is the bubble that map users will see when they click on one of the markers representing a store location. We add the popup by making a call to **bindPopup()** on the marker we just created. The only parameter we pass to this function is a string containing the HTML that should appear inside the marker.

Finally, we add the marker to the markers array so it will be available to any other functions that need to work with the markers on our map.

Jumping back to our initial map setup code, we see the following:

```javascript
var markerGroup = new tomtom.L.featureGroup(markers); 

fitMapToMarkerGroup(markerGroup);
```
 
Here, we’re starting by creating a Leaflet FeatureGroup. This groups our markers together, which makes it easier for us to perform operations on the map based on the collective characteristics of all our map’s markers.

Finally, we call **fitMapToMarkerGroup()**:

```javascript
map.fitBounds(markerGroup.getBounds().pad(0.2)); ,/pre>
```

As you can see, we make a call to the map’s **fitBounds** method to politely ask it to fit itself to the marker group’s boundaries. We also add a bit of padding to ensure that none of the markers get cut off by the edge of the map.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zg18t3egm7eamjyb6gi0.jpg)

#Next Steps

In this article we started created the Plugin features that enable a site administrator to configure the stores map for a website, adding stores to the database and displaying them on the map.  

There are alternatives to using a database for your store data that include hardcoding the store data into your Plugin code or saving the data in a separate file. We won't cover those options in this series of articles. Note, however, that no matter which way you choose to store the data, you'll need to provide both the address information and the latitude and longitude coordinates. 

In the next article we'll use the same tools to build a user-facing map widget that we can insert into a public web page. This widget will use the same Maps SDK for Web resources and will draw its data from the store database configured through the admin panel. 

Please find the TomTom Store Locator source code on our [GitHub](https://github.com/tomtom-international/tomtom-blogpost-store-locator) account.