Set up wp rest api v2 for displaying cpt with handlebars.js

, 10/02/2016

Recently I’ve been thinking about increasing my personnal website’s performances. I like to work with wordpress but it’s a bit heavy when it comes to making a portfolio with not so many  projects. So i dove into the wp-rest api plugin and decided to create this tutorial on how to deal with wp rest api and the custom post types of wordpress.

Here’s is the plan :

root folder : index.html which will retrieve the content of all the custom post types as json format
root subdirectory ( called lab ) : the wordpress installation which will output my custom post types data

The aim was to display the post title, the date, the post content, the post excerpt, the category and the featured image url on the index page of my root directory call « index.html ». But the plugin does not export the taxonomy’s terms and the featured image url natively, so I had to find a way to deal with it.

1 – Install wordpress in a subdirectory

To do so refer to the famous 5mn installation of wordpress

2 – Install wp-rest api

Install wp-rest-api-v2 from the plugin’s directory of wordpress
Create some posts then go to : http://yourdomain.com/lab/wp-json/wp/v2/posts (i chose « lab » as a subdirectory’s name)
You can now view the json response containing your regular post data

3 – Install your theme and set up the theme functions.php

I chose to install Html5 blank theme because it’s better for performance and because I like to start a project with a blank theme. Now we need to configure the custom post type support for the rest api. To do so, open functions.php

Register the custom post type and the custom taxonomy

add_action('init', 'create_custom_post_type'); 
function create_custom_post_type()
{
    register_post_type('projects', // Set the name of the post type
        array(
        'labels' => array(
            'name' => __('Projets', 'html5blank'), // Rename these to suit
            'singular_name' => __('HTML5 Blank Custom Post', 'html5blank'),
            'add_new' => __('Nouveau projet', 'html5blank'),
            'add_new_item' => __('Ajouter un nouveau projet', 'html5blank'),
            'edit' => __('Editer', 'html5blank'),
            'edit_item' => __('Editer le projet', 'html5blank'),
            'new_item' => __('Nouveau projets', 'html5blank'),
            'view' => __('Voir', 'html5blank'),
            'view_item' => __('View le projet', 'html5blank'),
            'search_items' => __('Rechercher un projet', 'html5blank'),
            'not_found' => __('Projet non trouvé', 'html5blank'),
            'not_found_in_trash' => __('Projet non trouvé dans la corbeille', 'html5blank')
        ),
        'public' => true,
        'hierarchical' => true, // Allows your posts to behave like Hierarchy Pages
        'has_archive' => true,
        'supports' => array(
            'title',
            'editor',
            'excerpt',
            'thumbnail'
        ), // Go to Dashboard Custom HTML5 Blank post for supports
        'can_export' => true, // Allows export in Tools > Export
        'taxonomies' => array(
            // 'post_tag',
            'project_cat'
        ),// Add Category and Post Tags support,

         'rewrite' => array( 'slug' => 'projets', 'with_front' => true )
    ));

    // register the taxonomy ( set the name : here it is : project_cat )
    register_taxonomy( 'project_cat', 'type', array( 'hierarchical' => true, 'label' => 'Types de projet', 'query_var' => true, 'rewrite' => true ) 
); 
}

Next add REST API support to the registered post type

add_action( 'init', 'custom_post_type_rest_support', 25 );
function custom_post_type_rest_support() {
    global $wp_post_types;
    //be sure to set this to the name of your post type!
    $post_type_name = 'projects';
    if( isset( $wp_post_types[ $post_type_name ] ) ) {
        $wp_post_types[$post_type_name]->show_in_rest = true;
        $wp_post_types[$post_type_name]->rest_base = $post_type_name;
        $wp_post_types[$post_type_name]->rest_controller_class = 'WP_REST_Posts_Controller';
    }
}

And add rest api support for the custom taxonomy

add_action( 'init', 'custom_taxonomy_rest_support', 30 );
function custom_taxonomy_rest_support() {
    $mytax = get_taxonomy( 'project_cat' );
    $mytax->show_in_rest = true;
}

You can now access to the json response that countains all your custom post type data :

> http://yourdomain.com/lab/wp-json/wp/v2/projects

As you can see, the json reponse does not retrieve by default the featured image url. Morover, it doesn’t display the category name of the post. Here are some snippets to do it :

Modify json response to handle featured image url which is not present by default

add_action( 'rest_api_init', 'featured_image_thumbnail_url' );
// Modifying Responses : get featured image url
function featured_image_thumbnail_url() {

    // More info http://v2.wp-api.org/extending/modifying/
    register_api_field(
        'projects',
        'featured_image_url',
        array(
            'get_callback'    => 'get_featured_image_url',
            'update_callback' => null,
            'schema'          => null,
        )
    );
}

// get the value of the featured image url field
function get_featured_image_url( $object, $field_name, $request ) {
    $thumbnail_id = get_post_thumbnail_id( $object->ID );
    $thumbnail = wp_get_attachment_image_src($thumbnail_id, 'full');
    return $thumbnail[0];
}

Modify json response to retrieve our custom taxonomy terms by post type

// retireve cat / taxonomy term in post response
add_action( 'rest_api_init', 'add_terms_to_response' );
function add_terms_to_response() {
    register_rest_field( 'projects',
        'project_cat',
        array(
            'get_callback'    => 'slug_get_project_cat',
            'update_callback' => null,
            'schema'          => null,
        )
    );
}
function slug_get_project_cat( $object, $field_name, $request ) {
    $terms = wp_get_object_terms( $object[ 'id' ], 'project_cat', null);
    foreach( $terms as $term )
    $term_names[] = $term->name;
    $fetchTerms = implode( ', ', $term_names );
    return $fetchTerms;
}

WordPress and wp-rest-api are installed, and we set support for the api in functions.php to handle custom post type and custom taxonomies. Now it’s time to create our custom front end for displaying data

4 – Create your index.html file in the root of your site

First, load the js librairies : jQuery and Handlebar.js


<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.js"></script>

Next create the recipient for handlebar.js content :

<h1>My work</h1>
<div id="items-list">
    This is were handlebar.js will display the content of post 
</div>

Next create the handlebar.js template structure for displaying custom post types content like slug / id / featured_image_url /post title / post excerpt / date / project_cat and content :

<script id="items-template" type="text/x-handlebars-template">
   <article id="{{slug}}" data-id="{{id}}"  data-category="" class="item project-{{id}} ">
       <figure class="cover" style="background-image : url({{featured_image_url}})"></figure>
       <div class="caption">
            <h3 class="post-title">{{{title.rendered}}}</h3>
            <p>{{dateFormat date}}</p>
            {{{excerpt.rendered}}}
            <span class="cat">{{project_cat}}</span>
            <a href="#" class="view-item" data-name="">Voir le projet</a>
       </div>
       <div class="item-content">{{{content.rendered}}}</div> 
    </article>
</script>

And finally grab the json response with ajax
If load function works as expected, handlebar.js will compile #items-template and then it will display all posts in #items-list. ( Be sure to check your domain url )

<script type="text/javascript">
 
(function() {

    // Handlebar helper for displaying date
    Handlebars.registerHelper('dateFormat', function(context) {
        var date = new Date(context),
        day = date.getDate(),
        month = date.getMonth() + 1,
        year = String(date.getFullYear()).substr(2,3);
        return (day < 10 ? '0' : '') + day + '.' + (month < 10 ? '0' : '') + month + '.' + year;
    });

    // greb the json data then compile to template
    $.ajax({
        url: 'http://yourdomain.com/lab/wp-json/wp/v2/projects', // fetch post type projects
        data: {
            filter: {
               'posts_per_page': -1,
                // 'orderby' : 'title',
                // 'order' : 'DESC'
             }
        },
        dataType: 'json',
        scriptCharset: "utf-8",
        type: 'GET',
        success: function(data) {
             // templating data with handlebar.js
             $.each( data, function(index, post) {
                  var source = $('#items-template').html();
                  var template = Handlebars.compile(source);
                  var html = template(post);
                  $('#items-list').append(html);
              });
        },
        error: function() {
              // error code
              alert('something went wrong ! Be sure to load file from server like localhost');
        }
    });
})();
</script>

That’s it, you’re done !