In my last article for Torque, I gave an introduction to using the JavaScript framework VueJS with the WordPress REST API. In that post, you learned how to combine the WordPress REST API with VueJS and jQuery AJAX to show and edit single posts. It was also an introduction to the Vue constructor and template syntax.
This week, I want to introduce components and some additional data attributes. Components allow for greater code reuse throughout the application. As a practical example, this article will show how to create a post list and a single post component.
Since this is VueJS, the code will be relatively simple, but take advantage of all of the powers of a reactive framework.
Getting Started
For this article, I created a very simple WordPress theme, which I posted on Github. Of course, you can build your own theme from scratch, but if you want you can use the template I’ve created. I started by taking the header.php, footer.php, and index.php from Twenty Fifteen and removing most of the PHP including the loop.
The one important thing I did was enqueue VueJS and jQuery in my functions.php. Do this before you create a simple theme with no loop using your favorite framework, or download my theme.
I also used this function, via wp_localize_script to add to the DOM a variable called config with the API URL and the REST API nonce.
The First Component
Creating VueJS components is very similar to creating a Vue instance. We are going to create a “post-list” component to use in our Vue instance. Because this component is separate from the Vue instance, we can reuse it.
Vue components are created using vue.component(). The result of creating a component is that we have an HTML element that will run as a mini app. Here is the HTML markup we are going to use:
<div id="app"> <post-list></post-list> </div>
That’s it. We will need to provide a template and write the component and main Vue instance, but that’s all the HTML for the app we need. The final thing we need to create is a template, but the syntax is going to be the same as we used in the last article.
For this template I used a script of the type “text/html” so that way the browser would not render it until it was needed. Inside I used the v-for attribute to loop through posts. This syntax is like a PHP for each loop, but backwards. We use for “post in posts” in VueJS and inside of the loop, post is the current post being shown. Here is the template:
<script type="text/html" id="post-list-tmpl"> <div id="posts"> <div v-for="post in posts"> <article v-bind:id="'post-' + post.id"> <header> <h2 class="post-title"> {{post.title.rendered}} </h2> </header> <div class="entry-content" v-html="post.excerpt.rendered"></div> </article> </div> </div> </script>
Now we’re ready for some JavaScript. Like in the last article, we start by using jQuery to get some data via the WordPress REST API, but instead of one post, we get a list of posts. This time when it’s ready we put the posts into our component’s data, instead of the Vue instance:
(function($){ var vue; $.when( $.get( config.api.posts ) ).then( function( d ){ Vue.component('post-list', { template: '#post-list-tmpl', data: function () { return { posts: d } }, }); vue = new Vue({ el: '#app', data: {} }); }); })( jQuery );
Notice that the main Vue instance has a data component, but has no data in it. The component constructor gets the data, and has a similar “el” argument, which is used to say which element inside of the template to render in. We also tell it the identifier for the template.
That’s all we need for a list to show post title and excerpt.
Dynamic DOM Manipulation
Now let’s modify the component a little bit to create a way to dynamically show the content of one post while hiding the excerpt of the other posts. The markup changes and JavaScript code will be much simpler than if we had to do something similar with jQuery.
The magic is going to come from the v-if attribute. When applied to an element, this attribute decides if that element should be shown or hidden, based on the value passed to it. We can use part of the current model, or a function defined in the component’s methods.
For our use, we will base everything on two new properties of the component’s data — list and show. List will be a boolean that will start as true, to represent that we are in list mode. Show will be an integer to represent the post ID being shown and will start out as 0, since we are starting with non-shown.
Before we do that, let’s modify our template with inline onclick handlers, like I showed last week, to handle hide and show events:
<script type="text/html" id="post-list-tmpl"> <div id="posts"> <div v-for="post in posts"> <article v-bind:id="'post-' + post.id"> <header> <h2 class="post-title"> {{post.title.rendered}} </h2> </header> <div class="entry-content" v-html="post.excerpt.rendered" v-if="list"></div> <div class="entry-content" v-html="post.content.rendered" v-if="post.id == show"></div> <a href="#" class="close" role="button" v-on:click.stop="close" v-if="post.id == show">Close</a> <a href="#" class="read-more" role="button" v-on:click.stop="read(post.id)" v-if="list">Read More</a> </article> </div> </div> </script>
Now we have the post excerpt showing if that property of data is true, and the post content showing when the post ID equals the property shown. Also, we have added a read more and close button that uses the same conditional logic.
Notice that the read more and close buttons use inline click handlers. These will need to get defined in the component’s methods argument. Also, see how the read() function takes post.id, that’s great as we will need it to set the value for show. Here is the updated component with those two functions, both of which are just two lines:
(function($){ var vue; $.when( $.get( config.api.posts ) ).then( function( d ){ Vue.component('post-list', { template: '#post-list-tmpl', data: function () { return { list: true, show: 0, posts: d } }, methods: { read: function( id ){ this.list = false; this.show = id; }, close: function(){ this.list = true; this.show = 0; } } }); vue = new Vue({ el: '#app', data: {} }); }); })( jQuery );
When read is clicked, it sets the value of show to the ID of the post the click came from and sets the value of list to false. That’s all it takes to hide all the excerpts and just show that post. When close() is clicked show is set to 0 and list is set to true and everything goes back to the way it was.
Single Post Component
Now let’s create a second component for single post. For now, we will use a single.php file in the WordPress theme for this. Next week, we’ll add a router to dynamically switch between these views.
Before starting, I wanted to make sure this single.php would work with all public post types, not just posts and pages, so I added this to my functions.php:
<?php /** Show all public post types in REST API */ add_action('init', function () { global $wp_post_types; foreach (get_post_types() as $post_type) { if ($wp_post_types[$post_type]->public && !$wp_post_types[$post_type]->show_in_rest) { $wp_post_types[$post_type]->show_in_rest = true; if (empty($wp_post_types[$post_type]->rest_base)) { $wp_post_types[$post_type]->rest_base = $wp_post_types[$post_type]->name; } } } }, 40000);
Now in my single PHP, I created a simplified version of what I had before with my HTML markup for my app and template:
<div id="app"> <post></post> </div> <script type="text/html" id="post-tmpl"> <div id="post"> <article v-bind:id="'post-' + post.id"> <header> <h2 class="post-title"> {{post.title.rendered}} </h2> </header> <div class="entry-content" v-html="post.content.rendered"></div> </article> </div> </script>
Now we will need to make a new component, which will be much simpler than last time. I did it all in my single.php, which ended up looking like this:
<?php get_header(); ?> <div id="primary" class="content-area"> <main id="main" class="site-main" role="main"> <div id="app"> <post></post> </div> <script type="text/html" id="post-tmpl"> <div id="post"> <article v-bind:id="'post-' + post.id"> <header> <h2 class="post-title"> {{post.title.rendered}} </h2> </header> <div class="entry-content" v-html="post.content.rendered"></div> </article> </div> </script> <?php $post_type = get_post_type_object( get_post_type( ) ); ?> <script> (function($){ var config = { api: { posts: "<?php echo esc_url_raw( rest_url( 'wp/v2/' . $post_type->rest_base . '/' ) ); ?>" }, nonce: "<?php echo wp_create_nonce( 'wp_rest' ); ?>" }; var vue; $.when( $.get( config.api.posts + '1' ) ).then( function( d ){ var post = Vue.component( 'post', { template: '#post-tmpl', data: function(){ return { post: d } } }); vue = new Vue({ el: '#app', data: {} }); }); })( jQuery ); </script> </main><!-- .site-main --> </div><!-- .content-area --> <?php get_footer(); ?>
This is very similar to last time, but no loop, and it’s a separate component.
Improving The Components For Reuse
So far, for simplicities sake, I have been creating these components inside a component of jQuery.get(). We get the posts or post and then use that to make the component. That works, but it makes our components strongly tied to the HTTP request and hard to reuse.
So, I want to talk about the early life-cycle of a component and how we can move that AJAX call inside the component. This will make the components lazy-loading and allow them to be re-used in a router, which is the next part of this series.
When a component is loaded, if it has a method called mounted, that method will be called. This is the method we can use to call a function in the component’s methods property or elsewhere to get the data from the WordPress REST API.
Here is our new post-list component:
Vue.component('post-list', { template: '#post-list-tmpl', data: function () { return { list: true, show: 0, posts: [] } }, mounted: function () { this.getPosts(); }, methods: { getPosts: function () { var self = this; $.get( config.api.posts, function( r ){ self.$set( self, 'posts', r ); }); }, read: function( id ){ this.list = false; this.show = id; }, close: function(){ this.list = true; this.show = 0; } } });
Notice how we still have value in data for posts, but it is empty. If we didn’t do that, it would not be considered a reactive property of the component, and we couldn’t update it later. The other important thing to notice is that we used this.$set() to update its value in the success callback of $.get().
Where To Next?
That’s how we can create reusable components in using VueJS. We now have a listview and a single view. Since they are not strongly tied to any API query we could use them with different post types, or we could supply the data without AJAX, at least on single page load.
We are very close to everything we need for a starter single page web WordPress theme. Next week we will dive into the VueJS router so we can switch states between routes without new page loads and things will start getting more interesting. Tune in next week where we will hook both of those components up to a router to build a more complete app.
To learn more about the REST API JavaScript frameworks, check out Caldera Learn.
1 Comment