WordPress Plugins: Adding Stores to a Map

ruannawrites

Ruanna

Posted on September 11, 2020

WordPress Plugins: Adding Stores to a Map

WordPress Plugins + TomTom – Part 3

In the previous two articles in this series, 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:

  register_activation_hook(__FILE__, 'ttlocator_install');
Enter fullscreen mode Exit fullscreen mode

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

    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 ); 
    } 
Enter fullscreen mode Exit fullscreen mode

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:

   <div class="ttlocator-add-store-page"> 
Enter fullscreen mode Exit fullscreen mode

      #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.

<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;
Enter fullscreen mode Exit fullscreen mode

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:

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); 
       }); 
}); 
Enter fullscreen mode Exit fullscreen mode

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:

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.") 

   } 

}  
Enter fullscreen mode Exit fullscreen mode

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

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

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

} 
Enter fullscreen mode Exit fullscreen mode

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:

if(storeLocations.length > 0) { 
   storeLocations.forEach(store => addStoreMarkerToMap(store)); 
   var markerGroup = new tomtom.L.featureGroup(markers); 
   fitMapToMarkerGroup(markerGroup); 
} 
Enter fullscreen mode Exit fullscreen mode

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.

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

} 
Enter fullscreen mode Exit fullscreen mode

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:

var marker = tomtom.L.marker(location).addTo(map); 
Enter fullscreen mode Exit fullscreen mode

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:

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

fitMapToMarkerGroup(markerGroup);
Enter fullscreen mode Exit fullscreen mode

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

map.fitBounds(markerGroup.getBounds().pad(0.2)); ,/pre>
Enter fullscreen mode Exit fullscreen mode

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

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 account.

💖 💪 🙅 🚩
ruannawrites
Ruanna

Posted on September 11, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related