Advertisement
Creative Coding

Using Backbone Within the WordPress Admin: The Back End

by

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:

  1. Register a Custom Post Type - for our Questions
  2. Add a meta box that will allow us to enter answers on the same page
  3. Save information from the meta boxes when the post is saved
  4. 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:

  1. Outputting base HTML for the meta box
  2. Outputting a client-side template along with the answers in JSON
  3. 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.

answers01

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.

  1. Create a folder called wp_quiz
  2. Create a PHP file inside with the same name
  3. Create a folder called js
  4. Create a folder called src

Your folder structure should look like this.

files01

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:

  1. Include our main plugin class
  2. Create a function that will create an instance of the class
  3. 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:

  1. Register the Custom Post Type
  2. Add a meta box
  3. Retrieve the content for the meta box and output both HTML and some JSON data into it
  4. Listen for PUT requests and save data to the database
  5. 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

  1. First we register the Custom Post Type using another helper method (which will be seen later on)
  2. Then we are registering a hook to load our meta box
  3. We also need a separate method for accepting our ajax requests
  4. 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

  1. Add the JavaScript files needed for this plugin - again using a helper method (seen later)
  2. Create a unique ID for this plugin based on the name of the post type
  3. 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:

  1. Access data sent as a PUT request. As it will be in JSON format, we need to decode it
  2. Again check that the current user has relevant permissions
  3. Go ahead and attempt the save
  4. If either Add or Update was successful, we can simply return the newly saved data and Backbone will view this as a successful save
  5. 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.

  1. canSaveData() - This just ensures that the current user has the relevant permissions to edit / update this post.
  2. addScripts() - Note that here we are ensuring that we pass the 5th param to the wp_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.
  3. 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'.
  4. 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.

Related Posts
  • Code
    Web Development
    Laravel Unwrapped: Session, Auth and CacheLaravel wide retina preview
    Join me as we learn how to use Laravel's component-based system, Illuminate. Additionally, we'll see how to utilize service providers, Laravel's manager system, the Session, Auth, and Cache components, and the Store, Guard, and Repository libraries.Read More…
  • Code
    WordPress
    Mastering WordPress Meta Data: Understanding and Using ArraysMetadata
    In the first part of this series, we covered what WordPress meta data is, how it can be retrieved, and the various data types (such as objects or arrays) in which it can be returned. Now it's time to learn about the different types of arrays. When you write an array manually you know what its structure is an what the name of each index is. But when you are building arrays by querying a database, you are going to need to do some detective work to determine the structure of the data returned and the names of the indexes.Read More…
  • Code
    JavaScript & AJAX
    Combining Laravel 4 and BackboneLaravel plus backbone 400
    For this tutorial, we're going to be building a single page app using Laravel 4 and Backbone.js. Both frameworks make it very easy to use a different templating engine other than their respective default, so we're going to use Mustache, which is an engine that is common to both. By using the same templating language on both sides of our application, we'll be able to share our views betweem them, saving us from having to repeat our work multiple times.Read More…
  • Code
    Plugins
    Integrating Multiple Choice Quizzes in WordPress - Creating the BackendIntegrating multiple choice quizzes in wordpress
    Multiple choice questions are something that most of us have faced at least once in our life. We love them because we can provide correct answers by logically thinking about provided possibilities, even if we don't exactly know the correct answer. Also answering takes less time which makes it so popular. Creating a multiple choice quiz in WordPress can be a very exciting and profitable task. You can use it in your personal blog to attract more visitors, or you can create a premium section with advanced quizzes, or you can create quizzes focusing on popular certification exams. There are numerous possibilities for making it profitable.Read More…
  • Code
    Creative Coding
    Using Backbone Within the WordPress Admin: The Front EndThumbnai02l
    Welcome to the second part of Using Backbone Within the WordPress Admin. In the first part, we set up the 'back-end' of our plugin and now in the second part we'll finish off by adding our 'client-side' or 'front end' functionality. For an overview of what we're building in this tutorial along with our folder structure and files, please review the first part.Read More…
  • Code
    Plugins
    Incorporating the jQuery Date Picker Into the Post Editor: Preparing the PluginDatepicker
    We cover a lot of topics on this blog - anything ranging from something as simple as how to include and require template files in WordPress projects to something such as an entire series on the Settings API, but I think there's always room to cover a straightforward How-To that covers a single, specific task within the context of WordPress. So, in this two-part series, we're going to take a look at how to introduce a jQuery date picker into our post editor so that we can associate a date with a given post.Read More…