The WordPress REST API is a really awesome way to get WordPress content in the JSON format so you can use it in a JavaScript-driven interface. Normally, this means that your page loads without any content included and then you make an AJAX request to the server for the content as JSON, and render it on the page.
For a first page request, that is one extra request. As long as you’re on the server, why not get the content, as JSON, included in the response to the first request.
In this post, I will discuss using the REST API’s controllers to get properly formatted REST API responses without an additional HTTP request. Then as an example, we will make a post archive page, that gets its first page of posts using this method and then can switch to the second page of posts using an AJAX request for that content.
Getting Started
To demonstrate this, I will walk you through creating a simple Twenty Seventeen child theme that uses this method. It will handle archives for the default post type post. I posted the theme on Github, if you want to add support for single post views or other post types, please fork and share what you create.
I’m using Twenty Seventeen as my base theme and Handlebars.js as my templating system. You can use your preferred tools if you want.
To get started, create a child theme. In its functions.php we need a function to get posts, formatted as a REST API response that we can pass to wp_localize_script(). To do this, we can create exactly the same classes that WordPress would use when responding to an actual WordPress HTTP request:
- WP_REST_Request – Define our request arguments
- WP_REST_Post_Controller – Get the posts
- WP_REST_Response – Format response
This function creates the request manually, so we don’t need an extra HTTP request, and passes it through the post controller to do the query. Then the function wp_rest_ensure_response() gives us a proper response object, which has a method get_data() that we can use with wp_localize_script()
<?php /** * Get REST API response for posts * * @param int $page * * @return array */ function example_rest_theme_get_posts( $page = 1 ){ $controller = new WP_REST_Posts_Controller( 'post' ); $request = new WP_REST_Request('', '', array( 'page' => 1 )); $items = $controller->get_items($request ); return array( 'posts' => rest_ensure_response( $items )->get_data() ); }
Now we can set up a JavaScript file to load, and pass it this data, like this:
<?php add_action( 'wp_enqueue_scripts', function(){ wp_enqueue_style( 'twentyseventeen-style', get_template_directory_uri() . '/style.css' ); wp_register_script( 'handlebarsjs', '//cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.6/handlebars.min.js' ); wp_enqueue_script( 'theme', get_stylesheet_directory_uri() . '/theme.js', array( 'jquery','handlebarsjs' ) ); $post_data = example_rest_theme_get_posts( get_query_var( 'page', 1 ) ); wp_localize_script( 'theme', 'EX_THEME', $post_data ); });
Notice that we are setting the page argument with the query var “paged” that way if someone enters on page 2 of the archive, they will start with page 2 of posts.
One last thing, before we move onto is JavaScript time. In the child theme, create a home.php file and copy the contents of index.php from the child theme there. Then remove everything inside of #main — IE the loop.
At the bottom of the file, you will want to add a template to display your content or use wp_footer to output it. Since I am using Handlebars.js, this is what my template looks like:
<script id="entry-template" type="text/x-handlebars-template"> <article id="post-{{id}}" class="post-{{id}} post type-post status-publish "> <header class="entry-header"> <h2 class="entry-title"><a href="{{link}}" rel="bookmark">{{title.rendered}}</a></h2> </header> <div class="entry-content"> {{{content.rendered}}} </div> </article> </script>
Render Posts With JavaScript
In the last section, we told WordPress to load a JavaScript file called theme.js from the child theme’s directory. Go ahead and create that. In there, we will want to loop through the posts that we passed to the DOM with wp_localize_script using our template and place them in the container #main.
jQuery(function($) { if( 'object' == typeof EX_THEME && 'undefined' != EX_THEME.posts ){ var posts = EX_THEME.posts, template = Handlebars.compile($('#entry-template').html()), $main = $( '#main' ); $.each( posts, function( i, post ){ $main.append( template(post) ); }); } });
Now go to the home page of the site and the posts should load in via JavaScript. That’s cool, but no pagination, yet.
Adding Pagination
To make our pagination smart, we will want to only show a previous link on the second page or greater. Also, we don’t want to show a pagination link on the last page. In both, PHP and JavaScript can make use of the X-WP-TotalPages header to get the total number of pages.
So, first, let’s update the PHP function to get posts, to also return the current page and the total number of pages:
This will give us the information we need for pagination. We will also need a new template for pagination:
<script id="pagination-template" type="text/x-handlebars-template"> <nav class="navigation " role="navigation" id="post-pagination"> {{#if hasMore }} <a href="#main" id="nav-next">Next</a> {{/if}} {{#if hasLess }} <a href="#main" id="nav-previous">Previous</a> {{/if}} </nav> </script>
With that in place, we will update our JavaScript with a function to render the pagination. I say a function, since we will need to run this on the initial page load and then rerun it after we load new posts. In the template, there is a conditional based on a variable “hasMore” and a variable “hasLess”. Therefore, the function will need to set those up based on conditional logic to determine if we have more pages than the current page, and if the current page is greater than zero:
jQuery(function($) { if( 'object' == typeof EX_THEME && 'undefined' != EX_THEME.posts ){ var posts = EX_THEME.posts, template = Handlebars.compile($('#entry-template').html()), paginationTemplate = Handlebars.compile($('#pagination-template').html()), $main = $( '#main' ), pages = parseInt( EX_THEME.pages ), page = parseInt( EX_THEME.page ); $.each( posts, function( i, post ){ $main.append( template(post) ); }); pagination( pages ); function pagination( pages) { var hasMore, hasLess; if( page > 1 ){ hasLess = true; } if( pages > page ){ hasMore = true; } var pagination = { hasMore: hasMore, hasLess: hasLess }; $main.append(paginationTemplate(pagination)); $( '#nav-next' ).on( 'click', function(){ page++; getPosts(); }); $( '#nav-previous' ).on( 'click', function(){ page--; getPosts(); }); } } });
This will run our pagination display, but not actually handle the pagination itself. For that, we will need to make an actual AJAX request, empty out #main and re-render posts and pagination.
I will trust you know how to add an AJAX request using jQuery for REST API post content. If not, please review some of my older REST API post on Torque.
Here is the updated script with support for pagination:
jQuery(function($) { if( 'object' == typeof EX_THEME && 'undefined' != EX_THEME.posts ){ var posts = EX_THEME.posts, template = Handlebars.compile($('#entry-template').html()), paginationTemplate = Handlebars.compile($('#pagination-template').html()), $main = $( '#main' ), pages = parseInt( EX_THEME.pages ), page = parseInt( EX_THEME.page ); $.each( posts, function( i, post ){ $main.append( template(post) ); }); pagination( pages ); function pagination( pages) { var hasMore, hasLess; if( page > 1 ){ hasLess = true; } if( pages > page ){ hasMore = true; } var pagination = { hasMore: hasMore, hasLess: hasLess }; $main.append(paginationTemplate(pagination)); $( '#nav-next' ).on( 'click', function(){ page++; getPosts(); }); $( '#nav-previous' ).on( 'click', function(){ page--; getPosts(); }); } function getPosts(){ $.get( EX_THEME.api + '?page='+ page ).done( function(posts,status,xhr ){ var pages = xhr.getResponseHeader( 'X-WP-TotalPages' ); $main.empty(); $.each( posts, function( i, post ){ $main.append( template(post) ); }); pagination( pages ); }); } } });
Making It Your Own
That should be enough to get you started. If you want to take this further, I would recommend using the History API to update the URL on pagination. Also, you can add this to other post types, and add switching to single post view to create a pretty neat child theme. If I were doing that, I’d use VueJS — checkout my posts on using VueJS with the WordPress REST API here. It would be a lot cleaner, and probably use less code.
The real reason for this post was to show how to avoid an extra HTTP request when showing REST API data. As a bonus, you got a cool example of how to make a no page refresh pagination post archive.
No Comments