For over a year now, I have been talking to the WordPress community about JavaScript, specifically AngularJS. Not only have I expressed the growing significance of JavaScript to people in conversation and blog posts, but I have also emphasized its importance in several of my WordCamp talks in 2015. It appears that Matt Mullenweg shares this sentiment, because earlier this month at WordCamp US, he urged everyone to “learn JavaScript, deeply.”
In this article, I’m going to walk through the fundamentals of creating a better admin interface for plugin and theme developers using AngularJS. I’ll also demonstrate how you can take it to the next level.
I won’t, however, discuss why this is so significant for front-end development or how the REST API changes things to make it easier to build JavaScript applications. If you’re interested in learning more about this, Josh Pollock and I have written extensively about it already on Torque.
Before you begin, you must determine what it is you are administering on the site specifically. To keep this simple, let’s say your plugin creates a custom post type (CPT) that is hidden from the sidebar. This use case is simple — you have a CPT to store data, but you don’t want to use the default WordPress edit screen. Instead, you want to associate data to it using post meta but also have the UX be unique and easy for the user.
It’s All About UX
User experience, or UX, is important, and should be carefully assessed before you begin. The WordPress dashboard is no longer limited to its native capabilities. With the REST API, it can now be modified to fit specific needs, and, yes, improve UX. Now, you can build a JavaScript app which really gives you the flexibility of a unique UX and UI. This is one of the real powers of the REST API and what gets so many developers excited. So think your UX through before you start.
This is one of the real powers of the REST API and what gets so many developers excited. So let me reiterate, think your UX through before you begin.
Advanced Code Warning
This tutorial is going to skim through a lot of the basics. If you want to follow along with my code on GitHub, you’ll need a working knowledge of npm, gulp, and JavaScript.
Preflight Check: Create A plugin And Add Some Content
Note: This is just a foundational step that’s necessary when starting from scratch. If you already have a plugin or theme with content (or data), you can skip this section.
Before you get started, you need data to look at and manipulate. Start by creating a plugin, then add a Post Type and some posts. I am using a site_option to save the content that has been created.
You can view my code for CPT creation and content creation on GitHub.
When I created the CPT, I made sure to set show_in_menu to false so that it doesn’t show up like every other CPT in the left sidebar of the admin dashboard.
Step 1: Create menu, Add In The App DOM, And Enqueue JavaScript
The next step is to get your base UI and DOM set up for your app to take over. I’m not going to go into the details of creating an admin menu page or enqueueing scripts, but do have my code online.
Admin Menu Page
The menu page DOM is pretty simple. You have a navigation bar and your base content. If you are just starting, I recommend skipping the nav until all states are ready to go. This can be one of the last things you code.
You can view the full code here.
Also, before you get too far, test to make sure you coded this right by refreshing your WordPress dashboard. If you get errors in PHP or the JavaScript console, that’s a good sign. Try commenting out the enqueue to see if the DOM renders. If it does, this verifies the menu page is working as it should. We all know that the WordPress menu code isn’t always the most friendly.
Enqueue Admin JS
I have enqueued admin-app-scripts.js, which is a concatenated file comprised of a few other files including angular, angular-ui-router, angular-resource, and my admin-app. I use Gulp to handle the concatenation. If you want to use my setup, you will need to run gulp js in your command line after you have installed all npm dependencies using the npm install.
You can view the full code here.
To see the files that are being concatenated, or to add new ones, go to lines 6-11 of gulpFile.js file.
Localize Script: You can see in the admin_js.php that I used localize_script to create a local JavaScript object. Specifically, I wanted to store a dynamic way to grab the API url, the template directory, and the current nonce.
HTML Template Directory
The localized object stores template_directory. This isn’t for our normal php templates but rather, it’s for the HTML templates that we will use in our JavaScript Application. If you don’t have a template directory in your plugin’s base directory, create one now. We will add .html files here as we need them.
Step 2: Customize The API For Your Data
Before you create your app, you need to make sure the API has the necessary endpoints to easily grab and manipulate the data.
If you are just creating an app for default post types (post and page), then feel free to skip this step.
Step 2a: Custom Endpoints Vs. Default Endpoints
When I first started writing this article and creating the code for it, I created a custom endpoint to showcase how it works, however, I was quickly corrected by Josh Pollock. This is because if you have a simple CPT and your actions are just to edit, delete, and create, then you should be using default functionality with default built-in endpoints. Adding custom fields isn’t supported in WordPress 4.4, so you will need download the REST API v2 beta.
Adding REST Capability To A CPT
Adding RESTful support to an existing CPT is easy. If you have access to the CPT creation, just set the show_in_rest parameter to true, if you don’t have access to the CPT creation code, just follow my example here.
Using this example, you can add REST support for multiple CPTs by adding your CPT slug into the $post_type_names array.
Custom API Fields
By default, the REST API does not include meta data from a post into the response, so we will need to add custom fields. First, you will need to register the fields, then create two callback functions for handling GET and POST requests. These callback functions handle retrieving and updating the post meta.
First, you will need to register the fields, then create two callback functions for handling GET and POST requests. These callback functions handle retrieving and updating the post meta.
In my example, I specifically grab two post meta fields, and, on save, I loop through. This is an easy approach, however, for a production application, I recommend creating a custom field for every custom post meta. That way each one is modular and you can do any logic to the field (checking for type, evaluate the data, etc.) independent of any other custom meta field.
[php]function register_custom_fields() {
register_api_field( ‘book’,
‘meta’,
array(
‘get_callback’ => array( $this, ‘get_book_meta’ ),
‘update_callback’ => array( $this, ‘update_book_meta’ ),
‘schema’ => null,
)
);
}
function get_book_meta( $object, $field_name, $request ) {
$return = array(
‘isbn’ => get_post_meta( $object[‘id’], ‘isbn’, true ),
‘price’ => get_post_meta( $object[‘id’], ‘price’, true )
);
return $return;
}
function update_book_meta( $value, $object, $field_name ) {
foreach( $value as $key => $new_value ) {
update_post_meta( $object->ID, $key, $new_value );
}
return;
}[/php]
Custom Endpoints
If you want more functionality other than simply saving a post and some post meta, you may want to create a full custom endpoint for everything. You can create custom endpoints with this quick two-step process. First, register the routes with a namespace, and then create callback functions based on the HTTP method. I have left the code in for my basic example of this here.
Step 2b: Testing Your API
Once you have completed the first part, you should be able to easily test if it works. The fastest way to do so is to go to yourdomain.com/wp-json/ to see if you can either find the namespace you have declared your custom endpoint or find your CPT slug. In my code example, I find “book” in the “wp/v2” namespace, so I know I can go to domain.com/wp-json/wp/v2/book to see a list of the latest “book” posts. It works — huzzah!
If you want to test everything, you can use something like the app for Chrome, Postman REST API, which allows you to check calls like POST directly without writing any code. Remember, you may need to use that nonce saved in the localized object.
Step 3: Create The App, UI States, Factory, And HTML Filter
Now that you have everything in place (HTML, JavaScript, and the API), you are ready to start coding your admin interface. I have structured my sample app to have four pages:
- main page (general info page)
- listing page (to list posts from our CPT)
- detail page
- and edit page.
Declaring The App And Dependencies
Go back to the menu page you defined in php. You will notice in my example that the container div has ng-app — this needs to match the name of your Angular app. Since you are going to use the UI router and ng-resource, you will need to include these external dependencies in your app. Your final code should look something like this:
[js]admin_js_app.app = angular.module( ‘admin-js-app’, [‘ui.router’, ‘ngResource’] );[/js]
Setting Up The Routes And Templates
Now it’s time to set up the routes. Using the UI router, you will define a few states and their associated controllers and templates:
[js]admin_js_app.app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise(‘/’);
$stateProvider
.state(‘main’, {
url: ‘/’,
templateUrl: admin_app_local.template_directory + ‘/admin-js-app-main.html’,
})
…[/js]
You can view the full code here.
You can see the four states listed above defined using the UI router and their corresponding HTML template. Make sure that you have the HTML files (or create them) in the right place so you don’t get any 404 errors while trying to view your app.
Setting Up The Filter And Factory
You will only need one filter and factory for the app. If you want to work with more than one endpoint, then you will need to create a new factory. The filter uses an injectable $sce to trust HTML text and display rendered HTML rather than just the HTML as a string.
You can see that I injected the nonce into the factory as well so I don’t have to do it on a call-by-call basis. Make sure to alter the route in the factory based on your endpoint, /book. It will not work for you if you do not have a ‘book’ CPT.
You can view the full code here.
Step 4: Create The Controllers
Now that you have everything in place, the last piece of the puzzle is to get the controllers going. If you want to test your app to see if it is working, remove any controller calls in the Angular UI state, similar to how my example for “main” state doesn’t have a controller. If you change the HTML in each of the HTML templates and maneuver to the URL, like /book, you should see the correct HTML template.
Controllers set up the data for each template or view. Starting with the list controller, create the controller and make sure to inject your factory.
You can view an example of my list controller here.
The list controller is pretty basic. All it does is query the endpoint for the latest posts, which I then store to $scope.books. In the template, I have a ng-repeat that repeats through all the books to display a list of all of the posts.
The other part of the list controller is the delete button. You don’t need this, however, I thought it might be a nice thing to have on this page. The delete command is just as easy to do as the edit or get comment, just Books.delete({id:book.id}) with a callback function to splice out the post from the JavaScript array.
That’s it! Your list controller is done!
Repeat the process for the remaining controllers and you should end up with the four states as defined in the UI router.
Here is my full JavaScript app code.
AngularJS Isn’t The Only JS Framework
I personally love AngularJS, and built this application using it. This JS framework, however, may not work best for you.
You can check out the organization of the repo located here for other examples.
I am working on a few other versions — the next is ReactJS. Feel free to create your own version using whatever framework you enjoy working in, and submit a pull request.
The Future Is Near
As the REST API injects itself more into core, the way we use WordPress will change. While I have taken my time to build JavaScript application themes and even a plugin to help leverage AngularJS, another crucial way the REST API will influence WordPress is how plugin and theme developers leverage it to create a new experience for their users in the WordPress Dashboard.
Why Is This Good?
I spend a lot of time reading content by a smart man named Morten Rand-Hendriksen. He talks extensively about UX. One thing I learned from him is that the more usable your app is for the user it is built for, the more they will use it. Seems pretty obvious, doesn’t it? This same advice can be applied to the WordPress dashboard. With the REST API and JavaScript, the possibilities are endless. You can create a customized user interface so that your users can better enjoy your product and recommend your plugin or theme to others.
5 Comments