Building Reactive WordPress Plugins – Part 1 – Backbone.js

This is the beginning of a short series of articles showing how to create a simple WordPress Dashboard Widget with reactive style front end development. We’ll build the same plugin in each article, swapping out the front end development technology in each one with minimal changes to the backend PHP.

In this article we will be using Backbone.js for the front end, but as it’s also the first article in the series there will also be a little more to show regarding how the backend is set up too.

Reactive?

Reactive programming is nothing new, but recently technologies such as React and Vue.js have brought it into the limelight.

We’ve already written about some reactive technologies a few times here. Jeff started his WP REST API series with React Native, and Gilbert has written about React and Laravel Lumen and using Vue.js with the WP REST API for a theme.

The beauty of reactive style development is it leads to greater separation of concerns with simpler data flow through your application. This means code is easier to understand, reason about, refactor and debug.

Why Backbone.js?

Backbone.js isn’t normally talked about in the same way that React and Vue.js are in terms of being reactive, that’s probably because it’s a little older, not so fashionable, and doesn’t use some of the “new hotness” under its hood like the new kids on the block do, e.g. DOM patching.

Backbone.js is very much a reactive style JavaScript framework though; you update a piece of data in a model and a view component magically updates to show the changes while a request to the server fires off to persist the change in the background.

There’s a very good Getting Started section on the Backbone.js site that succinctly introduces the reactive flow with their Model and View separation, and Collections.

However, the main reason for using Backbone.js for the first article in this series is I wanted the first iteration of the plugin to be built without any external dependencies other than what already comes with WordPress!

Backbone.js has been officially included in WordPress since version 3.5, and is being used in the core for things like the Media Library’s grid view. So, I figured it would be very nice to use Backbone.js as it makes for a simpler plugin setup.

The Plugin

We’re going to build a very small WordPress plugin that creates a dashboard widget to display what’s in WordPress’s cron schedule. It’s called WP Cron Pixie, and here’s what it looks like…

wp-cron-pixie-demo

In the demo you can see that the cron schedules such as “Once Hourly”, “Twice Daily” etc. each have a list of cron events that are waiting to be run. Each event has a due date, and alongside it (in brackets) is how long it is until that event should be run. If you look closely, those times until due are changing every 5 seconds or so.

To the left of each cron event is a “run now” icon (looks like “fast forward” double arrows). When that icon is clicked the due date is instantly changed to the current date and time and the time until due is “now”. Another 5 seconds or so later and the cron event that was set to run now, rather than later, has a new time assigned somewhere in the future, based on its schedule. That means the WordPress cron has processed the event and scheduled the next time that job should be run.

You’ll likely have noticed three “cron_pixie_*” events in the schedule list. These cron jobs are being created by WP Cron Pixie with specific due times in order to test the way they are handled and displayed. They don’t do anything when the WordPress cron fires off their hooks, and the plugin will create them again if they are removed.

So how is this reactive? We’ll get into the gritty details later, but it’s probably easy to imagine that clicking the “run now” button just updated the due date for the event to the current date and time, and from that a chain of events happened. The important part was a simple function in the view that gets called when a “click” event is fired for the run now icon:

When the runNow() function is run:

  1. A couple of values are retrieved from the model attached to the view.
  2. There’s a quick check to see whether it’s even worth setting the due date if it’s already really close.
  3. The timestamp (seconds since 1970-01-01 00:00:00 on server) is set to “now” by decreasing it by the current seconds until due (both values from server for consistency).
  4. The model attached to the view is saved with a new timestamp.

There’s no finding elements in the DOM to be updated with new values. No specific building of and running AJAX calls to the server to save the updated model. We just save the model with new values and behind the scenes the view (DOM) is updated to reflect the changed model and a call to the server with the updated model data is made.

Another way that the display of cron schedules is reactive is how it updates every five seconds.

We generally think of a reactive interface in terms of buttons and other input controls that can be interacted with by the user, but we often forget that time is an input too. I wanted the interface to not only be reactive to user input, but also be alive and react to changes that happen over time without user intervention.

Behind the scenes a timer simply asks the server for the current cron schedule data, and updates the collection of cron schedules. You’ll see this in detail later, but that really is it, nothing is specifically done by the timer to update the displayed data. Simply updating the data tells the views that care about the schedule data to update themselves to reflect the changes.

A more fancy implementation might use websockets to keep a channel open to the server and receive updates as they happen, rather than poll for data with a timer. However, that’s way beyond the scope of the little plugin we’re developing here! It may have been possible to use WordPress’s Heartbeat API instead of a timer, but I didn’t want to mess with its default frequency of once per minute.

OK, enough of the introductory chit chat, let’s get to building a WordPress plugin…

The Backend

As with any WordPress plugin, it all starts with a small PHP file with some specific header comments:

All that really happens in wp-cron-pixie.php is that it hooks into the admin_init action so that the plugin only really does anything on admin pages, whereby the Cron_Pixie class comes to life.

The Cron_Pixie Class

This is the meat and potatoes of the backend, and starts off by checking that the current user has enough privileges to play with the cron schedule, and if so registers all the action and filter hook functions that it’s interested in:

You’ll notice that we’re using ye olde admin-ajax.php supplied wp_ajax_* actions rather than the newer WP REST API. In the same way that we’re using Backbone.js for this version of the plugin, I wanted to use something tried and tested and compatible with many previous versions of WordPress. The core infrastructure for the WP REST API was only added in the current (v4.4.x) stable release, whereas admin-ajax.php has been available for virtually forever in comparison. There’s a good chance in a future article we’ll switch to the REST API, but for this version we’re keeping it “old skool“.

There’s a tiny function that is called when the dashboard is starting up, it’s where the widget comes to life as far as WordPress is concerned:

/**
 * Registers the widget and content callback.
 */
public function add_dashboard_widget() {

    wp_add_dashboard_widget(
        $this->plugin_meta['slug'],
        $this->plugin_meta['name'],
        array( $this, 'dashboard_widget_content' )
    );
}

wp_add_dashboard_widget registers the dashboard_widget_content() function in the class to provide the content for the widget. It’s in the wp_add_dashboard_widget that we get our first glimpse of Backbone.js code.

Well, actually, it’s more Underscore.js code, as what is there are a couple of view templates that can be converted to html with variable replacement and a little bit of built in logic.

The <script type="text/template" id="..."> sections are templates that will not be processed by the web browser, and simply hang around in the DOM. We can then reference them via their ids to use them as the templates for a Backbone.View. You’ll see how they are used when we come to the JavaScript.

One important thing to note though is the <%= ... %> sections in the templates. That’s the template’s way of letting you poke data from your Backbone.Model that is feeding the view into the output. There are also <%- ... %> tags for html escaped output, and <% ... %> is used for plain old JavaScript, like the if ... else in the above code.

Having seen the WP Cron Pixie demo above hopefully you recognise the cron-pixie-schedule-item-tmpl template as where the display name for a cron schedule is output followed by an ul full of cron events.

Similarly the cron-pixie-event-item-tmpl template outputs the contents for each of those cron events, with a little bit of logic for when a schedule has an empty list of events. Otherwise showing the “run now” icon, hook name, timestamp for when it’s due and the seconds_due value processed by a displayInterval() function.

The div with id cron-pixie-main is where all the action happens on the frontend. It’s where our primary Backbone.View will attach itself to the DOM and stuff in all its pre-processed template goodness. More on that later, promise.

For an easier to maintain setup on a production plugin, I’d strongly recommend moving these templates out into their own files and including them instead. It might even be better to output the Underscore.js templates from admin_footer.

The enqueue_scripts function does the usual stuff of telling WordPress what stylesheets and JavaScript files the plugin wants included in the output:

It also uses WordPress’s wp_localize_script function to make available a CronPixie JavaScript variable that is full of useful stuff like: strings to be used in the output that can be translated, how often we want the timer to refresh the data (could made configurable in the future), and our initial data for display so that there is no waiting for the frontend JavaScript to fetch it from the server.

The _get_schedules() function that is used to get the schedule data is a bit messy, but I tried to write it in as simple to follow way as I could:

It’s worth taking a look at WordPress’s wp_get_schedules and related cron functions documentation to get an idea of how it hangs together, but I’m not going to go any deeper into it as I can feel you’re getting impatient and want to get to the frontend stuff.

As such, I’ll just briefly mention the two functions that will handle the wp_ajax_cron_pixie_schedules and wp_ajax_cron_pixie_events actions coming from the frontend, respectively ajax_schedules and ajax_events.

/**
 * Send a response to ajax request, as JSON.
 *
 * @param mixed $response
 */
private function _ajax_return( $response = true ) {
    echo json_encode( $response );
    exit;
}

/**
 * Displays a JSON encoded list of cron schedules.
 *
 * @return mixed|string|void
 */
public function ajax_schedules() {
    $this->_ajax_return( $this->_get_schedules() );
}

ajax_schedules just calls the previously shown _get_schedules and passes the result onto the _ajax_return function that sends out the response as JSON and then exits the script so that no further output is sent. We’re not allowing any changes to cron schedules from the front end, so regardless of whether it’s a GET or POST we’re just going to treat it like a GET. We’re also not handling requests for particular cron schedules either.

The ajax_events function checks the POSTed cron event data and updates the cron schedule to reflect the changes in the event and then tells cron to process any due events. It’s a bit convoluted due to being careful not to clobber an existing run-once cron event with a recurring one, and vice versa. We don’t currently have any reason to request cron event data from the frontend as it’s all supplied as part of the cron schedules data, so we’re basically only handling POST requests, it’ll just fall over if a GET request is tried. This function is a bit raw and not production ready. Maybe we’ll harden it in the future.

You can see ajax_events as well as the other couple of functions that set up the test cron schedule and events in the full source for class-cron-pixie.php:

https://github.com/ianmjones/wp-cron-pixie/blob/backbonejs/src/includes/class-cron-pixie.php

The Frontend

If you did your homework as suggested above, then you probably already have a good idea as to what is going to be included in the JavaScript file for the frontend. A couple of Models to define what our Schedule or Event data is like, a couple of Collections to hold the Schedules and Events, and few discreet Views to define how to output a Schedule, Event and a list of each (from their Collections).

If you take a look at the above code from main.js the first thing that probably jumps out is that we’re not directly using Backbone.Model or Backbone.Collection to define EventModel, EventsCollection, ScheduleModel and SchedulesCollection. This is because by default Backbone.js expects to use a REST API for its chit-chat with the backend server, but we’re currently using WordPress’s admin-ajax.php which is decidedly not REST based. Therefore at the top of the file we define an AdminAjaxSyncableMixin object that knows how to override the normal Backbone.sync function to POST to admin-ajax.php as defined in the global ajaxurl variable that WordPress supplies.

Normally when you use admin-ajax.php you tell it which hook you want it to fire within WordPress by supplying an action string within the data being sent in the AJAX request. So our little override object handles taking an action set on models or collections that extend from it, or it uses its own default action (set to “cron_pixie_request” in this case, which goes nowhere).

After defining our AdminAjaxSyncableMixin, a BaseModel and BaseCollection are set up that mix in its functionality, and extend from the real Backbone.Model and Backbone.Collection classes.

Then we come to the cron event’s model and collection definitions:

/**
 * Single cron event.
 */
CronPixie.EventModel = BaseModel.extend( {
    action: 'cron_pixie_events',
    defaults: {
        schedule: null,
        interval: null,
        hook: null,
        args: null,
        timestamp: null,
        seconds_due: null
    }
} );

/**
 * Collection of cron events.
 */
CronPixie.EventsCollection = BaseCollection.extend( {
    action: 'cron_pixie_events',
    model: CronPixie.EventModel
} );

Here you see where we define the WordPress action we want to be called whenever the model or collection data is synchronised with the server.

The EventModel also includes the set of fields we’d expect to see in the data, initialized to nulls.

The EventsCollection just specifies that it is a collection of EventModels.

When looking at the main.js code you’ll notice that the ScheduleModel and SchedulesCollection classes are very similarly defined.

Then comes the views that will show our EventModel/Collection and ScheduleModel/Collection data in the web page.

/**
 * The main view for listing cron schedules.
 */
CronPixie.SchedulesListView = Backbone.View.extend( {
    el: '#cron-pixie-main',

    initialize: function() {
        this.listenTo( this.collection, 'sync', this.render );
    },

    render: function() {
        var $list = this.$( 'ul.cron-pixie-schedules' ).empty();

        this.collection.each( function( model ) {
            var item = new CronPixie.SchedulesListItemView( { model: model } );
            $list.append( item.render().$el );
        }, this );

        return this;
    }
} );

The SchedulesListView is the main view that is attached to the #cron-pixie-main div that we saw in the HTML being output as the content of the plugin’s widget.

SchedulesListView has an initialize function that is used to tell itself to render its content whenever its collection of data is synchronized. In our case, whenever we tell the SchedulesCollection that we later set on the view to fetch data from the server. This view will run its render function after the data is updated.

In the render function we first clear out the existing list of schedules from the unordered list with class “cron-pixie-schedules” that is sitting inside the “cron-pixie-main” div that is the root of the view. Then for each schedule in the collection it brings to life a SchedulesListItemView with its data and tells it to render itself and then appends that output to the unordered list.

/**
 * A single cron schedule's view.
 */
CronPixie.SchedulesListItemView = Backbone.View.extend( {
    tagName: 'li',
    className: 'cron-pixie-schedule',
    template: _.template( $( '#cron-pixie-schedule-item-tmpl' ).html() ),

    initialize: function() {
        this.listenTo( this.model, 'change', this.render );
        this.listenTo( this.model, 'destroy', this.remove );
    },

    render: function() {
        var html = this.template( this.model.toJSON() );
        this.$el.html( html );

        // Need to render the cron schedule's events.
        var $list = this.$( 'ul.cron-pixie-events' ).empty();

        var events = new CronPixie.EventsCollection( this.model.get( 'events' ) );

        events.each( function( model ) {
            var item = new CronPixie.EventsListItemView( { model: model } );
            $list.append( item.render().$el );
        }, this );

        return this;
    }
} );

Every time a SchedulesListItemView is instantiated, it sets itself up a li element with the class “cron-pixie-schedule”. That’s what the tagName: li and className: 'cron-pixie-schedule' properties do.

As discussed earlier when talking about the widget’s initial content output, the SchedulesListItemView uses the Undercore.js template functionality to process the “#cron-pixie-schedule-item-tmpl” DOM element as a template for its output. The render function applies the current model’s data to the template and then sets the contents of the view to its html output. The template function works with JSON encoded data rather than plain old JavaScript objects, hence the use of this.model.toJSON().

This schedule item view is slightly complicated by the need to show all the schedule’s child cron events. But you’ll notice that exactly the same pattern is used to render the cron events collection nested in the schedule model as was used to render the schedules collection into the primary SchedulesListView view above. It just instantiates an EventsListItemView view for each event instead.

That only leaves one more view to discuss:

/**
 * A single cron event's view.
 */
CronPixie.EventsListItemView = Backbone.View.extend( {
    tagName: 'li',
    className: 'cron-pixie-event',
    template: _.template( $( '#cron-pixie-event-item-tmpl' ).html() ),

    initialize: function() {
        this.listenTo( this.model, 'change', this.render );
        this.listenTo( this.model, 'destroy', this.remove );
    },

    events: {
        'click .cron-pixie-event-run': 'runNow'
    },

    render: function() {
        var html = this.template( this.model.toJSON() );
        this.$el.html( html );

        return this;
    },

    runNow: function() {
        CronPixie.pauseTimer();

        // Only bother to run update if not due before next refresh.
        var seconds_due = this.model.get( 'seconds_due' );

        if ( seconds_due > CronPixie.timer_period ) {
            var timestamp = this.model.get( 'timestamp' ) - seconds_due;
            this.model.save(
                { timestamp: timestamp, seconds_due: 0 },
                {
                    success: function( model, response, options ) {
                        /*
                         console.log( options );
                         console.log( response );
                         */
                        CronPixie.runTimer();
                    },
                    error: function( model, response, options ) {
                        /*
                         console.log( options );
                         console.log( response );
                         */
                        CronPixie.runTimer();
                    }
                }
            );
        }
    }
} );

There’s nothing new in the EventsListItemView class until you get to the following bit:

events: {
    'click .cron-pixie-event-run': 'runNow'
},

Do you remember when we discussed the runNow() function up in the “The Plugin” section? This events property is all that’s required to specify the events to listen out for and what functions to fire in response. In this case we’re looking for click events when the target DOM element has the class “cron-pixie-event-run”, just like our “Run now” icons do.

I’m not going to go over the displayInterval utility function that follows, it’s not anything specific to Backbone.js, and just converts our “seconds until due” values into a more human readable string. So, displayInterval( 12345 ); would output “3h 25m 45s”.

I also hope the refreshData, runTimer, pauseTimer and toggleTimer functions are pretty self explanatory:

/**
 * Retrieves new data from server.
 */
CronPixie.refreshData = function() {
    CronPixie.schedules.fetch();
};

/**
 * Start the recurring display updates if not already running.
 */
CronPixie.runTimer = function() {
    if ( undefined == CronPixie.timer ) {
        CronPixie.timer = setInterval( CronPixie.refreshData, CronPixie.timer_period * 1000 );
    }
};

/**
 * Stop the recurring display updates if running.
 */
CronPixie.pauseTimer = function() {
    if ( undefined !== CronPixie.timer ) {
        clearInterval( CronPixie.timer );
        delete CronPixie.timer;
    }
};

/**
 * Toggle recurring display updates on or off.
 */
CronPixie.toggleTimer = function() {
    if ( undefined !== CronPixie.timer ) {
        CronPixie.pauseTimer();
    } else {
        CronPixie.runTimer();
    }
};

The one thing to notice is how simple refreshData is. Doing schedules.fetch() is all that is needed to grab all the data from the server for the collection of cron schedules defined in the schedules variable. “But where is that schedules variable coming from?”, I hear you ask? Well …

/**
 * Set initial data into view and start recurring display updates.
 */
CronPixie.init = function() {
    // Instantiate the base data and view.
    CronPixie.schedules = new CronPixie.SchedulesCollection();
    CronPixie.schedules.reset( CronPixie.data.schedules );
    CronPixie.schedulesList = new CronPixie.SchedulesListView( { collection: CronPixie.schedules } );
    CronPixie.schedulesList.render();

    // Start a timer for updating the data.
    CronPixie.runTimer();
};

$( document ).ready( function() {
    CronPixie.init();
} );

When the DOM is fully loaded the init function is called to render the data supplied in the CronPixie.data.schedules variable set by the plugin.

The first line creates the schedules variable as a new SchedulesCollection. Its reset function is then called to set its data without firing off any sync events as we don’t want any attempts to send data to the server when nothing has actually changed yet.

We then instantiate a SchedulesListView view, setting its collection to be the schedules collection we just set up. We then tell the new view to render itself for the first time, after that it’ll render itself whenever its collection changes.

To update that collection of schedules every 5 seconds, we then call runTimer(); to start the timer, which as you saw earlier ultimately calls schedules.fetch() each time it fires.

Voila, we now have constant updates coming from the server updating our data and rippling through our views.

Wrap Up

That was a rather long article, but it does introduce a brand new plugin that was written from scratch, hopefully in easy to follow code. We also touched upon the concept of reactive style development, and showed how that can be used with existing technologies in a stock WordPress install.

For the next article in this “Building Reactive WordPress Plugins” series we’ll be swapping out the Backbone.js implementation for something different. We’ll not be discussing the backend anywhere near as much, so hopefully you’ll have a shorter article!

Did you enjoy this article? Did you learn anything new and are you excited to try using Backbone.js in your next WordPress plugin rather than relying on jQuery style DOM manipulation? Please let me know in the comments below.

About the Author

Ian Jones

Ian is always developing software, usually with PHP and JavaScript, loves wrangling SQL to squeeze out as much performance as possible, but also enjoys tinkering with and learning new concepts from new languages.

  • euthelup

    This is like gold or something