If you own a chain restaurant, coffee shop, store, or even a company that has different branches in different cities, then you may want to markup all of them on a single map, and show this network on your website. Meta Box will give you an easy way to find exactly what you need!

I created a map like that.

Listings on the map displayed at the same time

There are several listings on the map displayed at the same time. They are all the restaurants in a chain. Each one of them has a custom marker icon. When clicking on the marker, a popup is displayed with the restaurant name and address.

Video Version

Before Getting Started

To have each restaurant displayed in each place, we should save the information about each restaurant in a post of a custom post type, and, obviously, include its location. In this practice, I use custom fields for the location.

We should download and install the Meta Box plugin to have a framework to create a custom post type for the listings and custom fields for the locations.

We also need some Meta Box extensions to have advanced features. Here are the extensions that we will use in this practice:

  • MB Custom Post Type: to create a custom post type for the listings;
  • Meta Box Builder: to have a UI in the back end to create custom fields easily;
  • MB Views (optional): to create a page template for displaying the listings on the map. In another way, you can add code to the theme’s files instead of using this extension. You can choose one of these ways which I’m going to provide you in this practice.

You can install them individually or just use Meta Box AIO for all.

Create a New Custom Post Type

Go to Meta Box and create a new post type for your listings. It can be restaurants, company branches, coffee shops, or fashion stores, etc.

Create a new post type for your listings

After publishing, you will see a new menu displayed. It's your new post type.

A new post type displayed on tha dashboard

Create Custom Fields

As mentioned, I’m going to create these three fields. Maybe you want more fields for more information, just add them as you go. But for demonstration purposes, I just use these ones.

The fields that I created for the practice

Go to Meta Box and create the fields.

Go to Meta Box and create the fields

Choose the Text field for the address.

Choose the Text field for the address

I’ll use the Open Street Maps field for the map.

Use the Open Street Maps field for the map

I have the Address field above where I input the location in text. To autocomplete the address when you type some text in the Address field and connect it with the map, we should fill in the ID of the Address field into the settings of the map field.

Fill in the ID of the Address field into the settings of the map field

Then, you can type some text to the Address field and choose one from the suggested list, or just click on the map, drag and drop the marker to set the location. To know more about this field type, please dig in our documentation for more detail.

For the marker icon, I choose a URL field to save the icon link.

After creating all the fields, move to the Settings tab. Remember to choose the Location as Post type and select Restaurant to apply these fields to it.

Choose the Location as Post type and select Restaurant to apply these fields

Go to the post editor, you will see the created custom fields.

The created custom fields

Display the Map and Listings

I’m going to have a page for the map only. In your case, you might want to add some content to the page, but I’ll skip it to keep this practice simple.

Create a new page

I’ll create a template for the page using both two ways:

  1. Adding code to the theme’s files: you should add some files to the theme’s folder as well as add code to several different files.
  2. Using the MB Views extension of Meta Box: just work on a place only for all. Besides that, the template will not be affected when you change the theme.

You should experiment yourself to find which makes sense to you.

Method 1: Add PHP Code to the Theme

Add Files

Go to your theme folder, and add a new PHP file for the page template. You can name it another way as you want.

Add a new PHP file for the page template

We’ll use some JavaScript for the map, so go to the JS folder, and add a new file.

Go to the JS folder

Add a new file

Add Template

Now, go to the created PHP file and add some code.

<?php
/**
 * Template Name: Listing on a map
 */
get_header();
?>
<div id="map_ID" style="width: 100%; height: 600px"></div>
<?php get_footer(); ?>

Go to the created PHP file and add some code

In there:

get_header(); and <?php get_footer(); ?> are to load the header and footer just like any other pages in your theme.

 

<div id="map" style="width: 100%; height: 600px"></div>   is the main content area of the page, it simply shows a Div with a specific width and height. The Div will be used to display the map via this HTML ID: id="map" .

Show a Div with a specific width and height

Now, go to the created page. Choose the template that we have just created.

Choose the template that we have just created

Get Data

Next, to regulate which content will be displayed on the page, go to add code to the functions.php file.

Go to add code to the functions.php file

add_action( 'wp_enqueue_scripts', function() {
    if ( is_page_template( 'tbl-listings-map.php' ) ) {
        wp_enqueue_style( 'leaflet', 'https://unpkg.com/[email protected]/dist/leaflet.css', [], '1.5.1' );
        wp_enqueue_script( 'leaflet', 'https://unpkg.com/[email protected]/dist/leaflet.js', [], '1.5.1', true );

        wp_enqueue_script( 'list', get_parent_theme_file_uri( 'js/list.js' ), ['jquery', 'leaflet'], '1.0', true );

        $locations = [];
        $query = new WP_Query( [
            'post_type' => 'restaurant',
        ] );
        foreach ( $query->posts as $post ) {
            $location = rwmb_get_value( 'location', '', $post->ID );
            $location['title'] = $post->post_title;
            $location['address'] = rwmb_get_value( 'address', '', $post->ID );
            $location['icon'] = rwmb_get_value( 'icon_url', '', $post->ID );
            $locations[] = $location;
        }
        wp_localize_script( 'list', 'Locations', $locations );
    }
} );

Add code to the functions.php file

In there:

if ( is_page_template( 'tbl-listings-map.php' ) ) { is to check if the current page is using the created template ('tbl-listings-map.php' is the name of the PHP file that I have just created, you should replace it with your own), the below lines of code will be applied.

Check if the current page is using the created template, the below lines of code will be applied

wp_enqueue_style( 'leaflet', 'https://unpkg.com/[email protected]/dist/leaflet.css', [], '1.5.1' );
wp_enqueue_script( 'leaflet', 'https://unpkg.com/[email protected]/dist/leaflet.js', [], '1.5.1', true );

I’m going to use the Leaflet library for CSS and JS to have the map with a beautiful look. So those lines are to enqueue the libraries.

Those lines are to enqueue the libraries

wp_enqueue_script( 'list', get_parent_theme_file_uri( 'js/list.js' ), ['jquery', 'leaflet'], '1.0', true );

This is to enqueue the created file in the JS folder.

Enqueue the created file in the JS folder

I created an array to store all the data about the locations.

The array to store all the data about the locations

$query = new WP_Query( [
    'post_type' => 'restaurant',
] );

This line of code is the query to get data from the wanted post type. And, restaurant is the slug of the post type of the restaurants that I’m using.

The query to get data from the wanted post type

There will be multiple posts since there are multiple locations, so we have a loop.

We have a loop

Inside the loop, these are the associative arrays to get data of each element of the location.

The associative arrays to get data of each element of the location

We use the rwmb_get_value function to get data from the custom fields. 'location' , 'address' , and 'icon_url' are the ID of the fields.

The function to get data from the custom fields

  • rwmb_get_value('location','',post->ID) will return values from the Location field which is the Open Street Map. They include both the latitude and longitude.
  • $post->post_title; is to get the title of the post, it’s also the restaurant name.
  • rwmb_get_value( 'address', '', $post->ID ); is to get the address in text from the Address field.
  • rwmb_get_value( 'icon_url', '', $post->ID ); is to get the icon for the marker.

All of that data will be transferred to the created array by this: $locations[] = $location;

wp_localize_script( 'list', 'Locations', $locations ); is to pass the values from the $locations array to a JavaScript Object that has named Locations.

Pass the values from the $locations array to a JavaScript Object that has named Locations

We’ll use this object in the created JS file to display all the locations on the map.

Display Data Using JavaScript

We just got the data so far and haven't displayed them yet.

To do it, go to the created JS file to add code.

( function ( document, Locations, L ) {
    // Set the first location.
    var First_Location_Point = L.latLng( Locations[ 0 ].latitude, Locations[ 0 ].longitude );

    // Map options.
    var map_options = {
        center: First_Location_Point,
        zoom: 13
    };

    // Initialize the map.
    var mapObject = L.map( document.querySelector( '#map_ID' ), map_options );

    // Set tile layer for Open Street Map.
    var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    });
        mapObject.addLayer( tileLayer );

        // // Show marker for each location.
        Locations.forEach( function ( location_on_map ) {
            // Marker options.
            var marker_options = {
                title: location_on_map.title,
                icon: L.icon( {
                    iconUrl: location_on_map.icon
                } )
        };
        var Location_Point = L.latLng( location_on_map.latitude, location_on_map.longitude );
        var marker = L.marker( Location_Point, marker_options ).addTo( mapObject );

        // Show name of the restaurant when click on the icon.
        marker.bindPopup( '<b>' + location_on_map.title + '</b><br>' + location_on_map.address ).openPopup();
    } );

} )( document, Locations, L );

Go to the created JS file to add code

When someone goes to the page and looks at the map for the first time, the map should be fixed at a specific point. So, I created a point via this line:

var First_Location_Point = L.latLng( Locations[ 0 ].latitude, Locations[ 0 ].longitude );

This point will be regulated based on the first set of values in the stored value string.

This part is to set the options for the map.

Set the options for the map

The center point of the map shows for the first time when a user looks at it will be set as the first location.

var mapObject = L.map( document.querySelector( '#map_ID' ), options );

This is to initialize the map. It also means to display the map. The map will be displayed in the place where this HTML ID was set. We did it in the php file for the page template.

Initialize the map

The map will have settings as the options as we set.

The map will have settings as the options as we set

This part is to set a tile layer, and copyright for the map.

Set a tile layer, and copyright for the map

The next part is the main one of this practice to show all the markers on the map at the same time.

The main one of this practice to show all the markers on the map at the same time

Locations.forEach( function ( location_on_map ) { } );

This is a loop to get all the data from this object.

I created two variables to store the data about each location.

I created two variables to store the data about each location

Then transfer all of them to the mapObject variable. This variable is the one we use to initialize the map above. It also means that we finished adding markers to the map.

Transfer all of them to the mapObject variable

To have a popup to show the detailed information of each marker when clicking on each one,  I have this:

marker.bindPopup( '<b>' + location_on_map.title + '</b><br>' + location_on_map.address ).openPopup();

A popup to show the detailed information of each marker when clicking on each one

Now, go to the page on the frontend. If nothing goes wrong, here's what you'll see.

Listings on a map page

For each marker, display a popup when click on the icon of each restaurant. The popup will show the title and address of the restaurant in detail.

In the case that you don’t want to touch the theme files, let’s go ahead to see another way with MB Views.

Method 2: Use MB Views

Go to Views in Meta Box and create a new template.

Go to Views in Meta Box and create a new template

Add Template

I’m going to add code to the Template tab. The code will be quite the same as what we added in both php files in the first method.

<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
<div id="map" style="width: 100%; height: 600px"></div>
{% set args = { post_type: 'restaurant' } %}
{% set posts = mb.get_posts( args ) %}
{% set restaurantsArray = [] %}
{% for post in posts %}
{% set restaurantsArray = restaurantsArray|merge( 
      [
         { 
            'longitude':
            'latitude':
            'address':
            'title':
            'icon':
         }
      ] ) %}
{% endfor %}
<div class="restaurants-list" data-items='{{restaurantsArray|json_encode()}}'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.slim.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>

Add code to the Template tab

You can see that there is not much difference in code between the two methods.

<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" /> is to register the Leaflet CSS Library.

<div id="map" style="width: 100%; height: 600px"></div> is to display the map. We also use JavaScript later and use id='map_ID' of the map.

We have {% set args = { post_type: 'restaurant' } %} to query data from the restaurant post type.

Query data from the restaurant post type

We use the mb.get_posts( args ) function to get posts.

The function to get posts

I’m also creating a new empty array to get data about the location from each post. You can name the array as any name as you want.

Create a new empty array to get data about the location from each post

And then, have a loop to get data from all the posts, and assign them to the array using this:
{% set restaurantsArray = restaurantsArray|merge( .

Have a loop to get data from all the posts, and assign them to the array

These are the keys of the data we’re getting from posts. They are saved in some fields of the post, so we will insert a corresponding field for each one of them.

Insert a corresponding field for each one of them

Click on the Insert Field button, and choose a field from the list.

Click on the Insert Field button, and choose a field from the list

The longitude is the data saved in the Open Street Map field. It is named Location, so choose it.

he longitude is the data saved in the Open Street Map field. It is named Location

There will be some options of the map for the output data, just choose Longitude.

Choose Longitude

After removing the curly braces, the generated code will be like this.

After removing the curly braces, the generated code will be like this

Do likewise for the latitude. Remember that these data are compulsory to locate a position on the map.

Next, insert the Address fields, Post title, and marker icon.

We also have a field for marker icons

In the first method, WordPress has the wp_localize_script) function to convert data from PHP to JavaScript. But with MB Views, we do not use it. Instead of that, we use the class="restaurants-list" HTML class and transfer all the values from the restaurantsArray array to this class.

We use the class="restaurants-list" HTML class and transfer all the values from the restaurantsArray array to this class

This line of code is to declare the Leaflet JavaScript library.

This line of code is to declare the Leaflet JavaScript library

That’s all for the template.

Add JavaScript

Still in the view, move to the JavaScript tab, and use the code that we use in the JS file in the first way.

Use the code that we use in the JS file in the first way

There will be a difference in the first part since we use HTML class to store data:

(function (document, L) {
      const Locations = $('.restaurants-list').data('items')
})(document, L);

All the following lines of code are exactly the same with the script we use in the JS file in the first method. So, I’ll not dig into them anymore.

Apply Template to the Page

Go to the Settings section of the view, and set the type as Singular.

Go to the Settings section of the view, and set the type as Singular

Then add a rule to set the location as the created page.

Add a rule to set the location as the created page

Go to the page, the map and all the markers also displayed.

Listings on the map displayed at the same time

Last Words

There is not much difference between the two methods, I pointed to those ones that you can compare and see how we should do in both ways. In personal thinking and experience, I choose MB Views. So, what do you choose? Let’s try and enjoy each! If you want to suggest any topic for tutorials, feel free to leave a comment and keep track of our blog. Thanks.

16 thoughts on “How To Display All Listings On A Map With Meta Box

  1. Perfect!
    Do you have a plan to write about creating filters for this page also? So people can sort, search by fields, categories...?

  2. Hi! It looks like the OSM field no longer exists, is this correct?

    I would like to make use of another Maps solution that was not Google Maps.

    Thanks!

    1. Hi Steve,

      In the View, you can just add the HTML code

      <div id="map" style="width: 100%; height: 600px"></div>

      and assign to the specific page as the template.

  3. This works well for me. I'm wondering if there is a way to do this while using the leaflet.markercluster plugin so that the locations cluster when zoomed out. Anyone know about this?

  4. As many have commented over the past 3 years, it would be EXTREMELY helpful to be able to 1) filter/facet posts based on what is shown in the map when you pan and zoom and 2) filter the map's pins for what has been filtered by search/facet tools on the page.

    I recognize that this might be able to be done with 3rd party plugins, like facet wp, but I'd like to do it with just Meta Box. Some instructions, such as those you created here (https://metabox.io/create-ota-website-with-meta-box-p2/) would be very helpful.

    In fact, this is the single factor that is preventing me from moving to Meta Box from Toolset.

  5. Is it possible to build a Storelocator with Metabox?
    For this it would be necessary that you can search e.g. in a radius of 50 miles around a location and all stores are displayed. In addition, the possibility to select by filters, whether the stores are displayed, for example, Chinese food or Italian or Mexican...

    With the possibility that the stores themselves can then edit their own data using frontend editing, but only certain ones, the rest should be displayed reading, this would be the absolute best store locator on the market and a killer feature for using Metabox.

  6. Great insights! Meta Box makes it super easy to manage multiple locations and display them on a map. Perfect for businesses with multiple branches!

    1. For filters (not related maps), they have some series with many topics. You can refer to them. Go to metabox.io > Blog > View all series (on the right sidebar)

Leave a Reply

Your email address will not be published. Required fields are marked *