The rumours are true! The WordPress Admin Panel is now using Underscore and Backbone! This means that with minimal effort, we can begin to utilise these fantastic JavaScript libraries in our own plugins. This tutorial will show you exactly how you can do that. We'll create the Admin part of a Quiz plugin. We'll use a simple Custom Post Type to save Questions, and then within each question we'll add a meta box that will allow us to enter up to 4 answers and select which is the correct one. We'll be going through how to use templates, how to hook into click and key-up events, how to save data back to the WordPress database and most importantly, how to 'get your truth out of the Dom', as the creator Jeremy Ashkenas likes to put it.
I will say up front, that the plugin we are building in this tutorial may seem overly verbose for what it accomplishes. It will however, give you an excellent peek into the world of using Backbone and should you come across a project in the future that requires a complex user interface with a lot of JavaScript, you will be well armed and ready to bring much needed organisation to the party.
What We'll Do
In this first part, we'll set up the back end of our plugin. This will involve setting up the files and folders as well as implementing all of the features our plugin requires in PHP. We'll need to:
- Register a Custom Post Type - for our Questions
- Add a meta box that will allow us to enter answers on the same page
- Save information from the meta boxes when the post is saved
- Save information from our ajax requests (via Backbone)
Then in the Second Part...
Once we have our back end set up, we'll then proceed to output the required HTML for our meta box along with the data for each answer in JSON format. We'll also write the JavaScript that ties everything together. We'll cover:
- Outputting base HTML for the meta box
- Outputting a client-side template along with the answers in JSON
- The JavaScript needed to tie it all together
I hope this small series sounds interesting to you, and I'm looking forward to helping you get up and running with using Backbone.js within a WordPress plugin.
What We'll Build
This small plugin will use a Custom Post Type to save Questions. Then in a meta box, we'll create four inputs that will allow users to enter possible answers to the current question and select which of those is the correct answer. When an answer is changed, the corresponding save button will become active. When clicked, we'll use Backbone's built in model.save()
method to save the data back to the WordPress database. Also, when the answers are being written in the inputs, the select box below it will automatically update its values as it will be looking out for changes to the models. All of these things are relatively trivial to do with Backbone and after reading this tutorial, you'll be able to start taking your plugins to the next level by using them within WordPress.
There's a lot to cover, so let's get started!
1. Create the Plugin
We need to do all the usual first steps involved with any plugin - create the files folders.
- Create a folder called wp_quiz
- Create a PHP file inside with the same name
- Create a folder called js
- Create a folder called src
Your folder structure should look like this.
2. Add the Plugin Header
Inside of wp_quiz.php.
/* Plugin Name: WP Quiz Plugin URI: http://wp.tutsplus.com/author/shaneosbourne/ Description: An example of using Backbone within a plugin. Author: Shane Osbourne Version: 0.1 Author URI: http://wp.tutsplus.com/author/shaneosbourne/ */
3. Add Hooks to Instantiate the Plugin
Still inside of wp_quiz.php, we need to do the following things:
- Include our main plugin class
- Create a function that will create an instance of the class
- Add a hook to only call the function when the user is an admin
/** wp_quiz.php **/ include 'src/WpQuiz.php'; // Class File // Create an instance of the Plugin Class function call_wp_quiz() { return new WpQuiz( 'admin' ); } // Only when the current user is an Admin if ( is_admin ) add_action( 'init', 'call_wp_quiz' ); // Helper function if ( ! function_exists( 'pp' ) ) { function pp() { return plugin_dir_url( __FILE__ ); } }
Putting the helper function pp()
within this file will allow us to reference other files relative to the root of the plugin folder (you'll see that in action shortly).
4. Create the Plugin Class
Inside of the src folder, create a file called WpQuiz.php.
In this plugin class, we'll be needing a few different methods to accomplish all of the following:
- Register the Custom Post Type
- Add a meta box
- Retrieve the content for the meta box and output both HTML and some JSON data into it
- Listen for PUT requests and save data to the database
- Save our meta box data upon regular 'save' actions
Before we write the methods though, we're going to be storing some information as class properties. We store this information at the top of our class file so that modifications are easier to make later on. The answerIds
array contains the keys that we'll be using throughout this plugin to save data using the built-in add_post_meta()
.
Add the Properties
/** src/WpQuiz.php **/ class WpQuiz { // Names of Custom Post Type public $postTypeNameSingle = 'Question'; public $postTypeNamePlural = 'Questions'; // Meta Box Stuff public $metaBoxTitle = 'Answers'; public $metaBoxTempl = 'templates/metabox.templ.php'; // Question Id's public $answerIds = array( 'quiz-a-1', 'quiz-a-2', 'quiz-a-3', 'quiz-a-4' ); // Javascript public $jsAdmin = '/js/admin.js'; }
Add the Constructor
- First we register the Custom Post Type using another helper method (which will be seen later on)
- Then we are registering a hook to load our meta box
- We also need a separate method for accepting our ajax requests
- Finally, when a page is loaded we'll want to save info from our meta box
/** src/WpQuiz.php **/ public function __construct( $type ) { switch ( $type ) { case 'admin' : // Register the Post Type $this->registerPostType( $this->postTypeNameSingle, $this->postTypeNamePlural ); // Add the Meta Box add_action( 'add_meta_boxes', array( $this, 'addMetaBox' ) ); // Accept an Ajax Request add_action( 'wp_ajax_save_answer', array( $this, 'saveAnswers' ) ); // Watch for Post being saved add_action( 'save_post', array( $this, 'savePost' ) ); } }
Add the Meta Box
- Add the JavaScript files needed for this plugin - again using a helper method (seen later)
- Create a unique ID for this plugin based on the name of the post type
- Add the meta box using the properties we set earlier
/** src/WpQuiz.php **/ public function addMetaBox() { // Load the Javascript needed on this admin page. $this->addScripts(); // Create an id based on Post-type name $id = $this->postTypeNameSingle . '_metabox'; // Add the meta box add_meta_box( $id, $this->metaBoxTitle, array( $this, 'getMetaBox' ), // Get the markup needed $this->postTypeNameSingle ); }
Get the Meta Box Content
Here we are looping through our Answer IDs and constructing an array that contains post meta fetched with our helper method getOneAnswer
. We make this new array so that we can encode it and send it to our template in JSON format - just the way Backbone likes it. We send data to our template using the $viewData
array seen below. This keeps all of the HTML out of harm's way and allows us to work on it in a separate file. We'll take a quick look at the getTemplatePart
method later on, but if you want an in-depth explanation about why I use it, please check out Improving Your Work-Flow – Separate Your Mark-Up From Your Logic!
/** src/WpQuiz.php **/ public function getMetaBox( $post ) { // Get the current values for the questions $json = array(); foreach ( $this->answerIds as $id ) { $json[] = $this->getOneAnswer( $post->ID, $id ); } // Set data needed in the template $viewData = array( 'post' => $post, 'answers' => json_encode( $json ), 'correct' => json_encode( get_post_meta( $post->ID, 'correct_answer' ) ) ); echo $this->getTemplatePart( $this->metaBoxTempl, $viewData ); }
Get a Single Answer - Helper
We are just returning an array of the data needed in our template. You can think of this as creating a single model that is needed on the front end.
/** src/WpQuiz.php **/ public function getOneAnswer( $post_id, $answer_id ) { return array( 'answer_id' => $answer_id, 'answer' => get_post_meta( $post_id, $answer_id, true ) ); }
Save Post
When a user clicks to save a post that our meta box is currently in, we need to do a couple of checks to ensure we are saving our Custom Post Type and that the current user has the correct permissions - if both checks are ok then we save the four answers from the meta box and the correct answer.
/** src/WpQuiz.php **/ public function savePost( $post_id ) { // Check that we are saving our Custom Post type if ( $_POST['post_type'] !== strtolower( $this->postTypeNameSingle ) ) { return; } // Check that the user has correct permissions if ( ! $this->canSaveData( $post_id ) ) { return; } // Access the data from the $_POST global and create a new array containing // the info needed to make the save $fields = array(); foreach ( $this->answerIds as $id ) { $fields[$id] = $_POST[$id]; } // Loop through the new array and save/update each one foreach ( $fields as $id => $field ) { add_post_meta( $post_id, $id, $field, true ); // or update_post_meta( $post_id, $id, $field ); } // Save/update the correct answer add_post_meta( $post_id, 'correct_answer', $_POST['correct_answer'], true ); // or update_post_meta( $post_id, 'correct_answer', $_POST['correct_answer'] ); }
Save Answers From Ajax Requests
This is where we will receive data passed to the server from Backbone. We need to:
- Access data sent as a PUT request. As it will be in JSON format, we need to decode it
- Again check that the current user has relevant permissions
- Go ahead and attempt the save
- If either Add or Update was successful, we can simply return the newly saved data and Backbone will view this as a successful save
- If neither were successful, we just return 0 to indicate a failure
/** src/WpQuiz.php **/ public function saveAnswers() { // Get PUT data and decode it $model = json_decode( file_get_contents( "php://input" ) ); // Ensure that this user has the correct permissions if ( ! $this->canSaveData( $model->post_id ) ) { return; } // Attempt an insert/update $update = add_post_meta( $model->post_id, $model->answer_id, $model->answer, true ); // or $update = update_post_meta( $model->post_id, $model->answer_id, $model->answer ); // If a save or update was successful, return the model in JSON format if ( $update ) { echo json_encode( $this->getOneAnswer( $model->post_id, $model->answer_id ) ); } else { echo 0; } die(); }
The Helper Methods
Here are the four helpers mentioned in the above snippets.
-
canSaveData()
- This just ensures that the current user has the relevant permissions to edit / update this post. -
addScripts()
- Note that here we are ensuring that we pass the 5th param to thewp_register_script()
function. This will load our custom JavaScript into the footer and will ensure that our JSON data is available. Also, if you are using the WordPress editor in your plugin, you do not need to specify Backbone as a dependency as it will already be available to you. I'm including it here for the sake of the example though. -
registerPostType()
- This is something I use often in plugins. It just makes life easier when adding a new Custom Post Type. It accepts both singular and plural versions of the name because it's not always as easy as just adding an 's'. -
getTemplatePart()
- I've never been fond of having mark-up inside of my methods. This little helper will allow the use of a separate template file.
/** src/WpQuiz.php **/ /** * Determine if the current user has the relevant permissions * * @param $post_id * @return bool */ private function canSaveData( $post_id ) { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return false; if ( 'page' == $_POST['post_type'] ) { if ( ! current_user_can( 'edit_page', $post_id ) ) return false; } else { if ( ! current_user_can( 'edit_post', $post_id ) ) return false; } return true; } private function addScripts() { wp_register_script( 'wp_quiz_main_js', pp() . $this->jsAdmin , array( 'backbone' ), null, true ); wp_enqueue_script( 'wp_quiz_main_js' ); } /** * Register a Custom Post Type * * @param $single * @param $plural * @param null $supports */ private function registerPostType( $single, $plural, $supports = null ) { $labels = array( 'name' => _x( $plural, 'post type general name' ), 'singular_name' => _x( "$single", 'post type singular name' ), 'add_new' => _x( "Add New $single", "$single" ), 'add_new_item' => __( "Add New $single" ), 'edit_item' => __( "Edit $single" ), 'new_item' => __( "New $single" ), 'all_items' => __( "All $plural" ), 'view_item' => __( "View $single" ), 'search_items' => __( "Search $plural" ), 'not_found' => __( "No $plural found" ), 'not_found_in_trash' => __( "No $single found in Trash" ), 'parent_item_colon' => '', 'menu_name' => $plural ); $args = array( 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'query_var' => true, 'rewrite' => true, 'capability_type' => 'post', 'has_archive' => true, 'hierarchical' => false, 'menu_position' => null, 'supports' => ( $supports ) ? $supports : array( 'title', 'editor', 'page-attributes' ) ); register_post_type( $single, $args ); } /** * Render a Template File * * @param $filePath * @param null $viewData * @return string */ public function getTemplatePart( $filePath, $viewData = null ) { ( $viewData ) ? extract( $viewData ) : null; ob_start(); include ( "$filePath" ); $template = ob_get_contents(); ob_end_clean(); return $template; }
5. On to the Front End
At this point, we've set up everything needed for our back end. It's time to take a break and prepare for the next part where we'll be getting down and dirty with client-side templates, JavaScript and Backbone.js. I hope to see you there - it's going to be a good one.
Comments