Cyber Monday Sale! Save up to 50% now.

Backbone.js and Collections: Routers

By on November 26, 2013

The biggest challenge with building a Backbone.js powered theme for WordPress was handling routing. This post will explore those struggles and outline the solution used in Collections.

The Backbone.js router

Backbone.js routers connect permalinks with actions and events in a JavaScript (JS) application. When defining a router, individual “routes” are defined with the purpose of matching a permalink pattern and associating it with a JS function. As an example, for an app that employs a standard RESTful API, a typical permalink for a post might look like http://mysite.com/post/45 for the post with ID 45. In Backbone.js, this permalink can be matched by a route and sent to a function with the following code:

routes : {
    'post/:id' : 'getPost'
},

getPost: function( id ) {
    alert( id );
}

The getPost function will be called with 45 as the first argument when a visitor requests http://mysite.com/post/45.

Backbone.js is geared toward applications that utilize a RESTful API. With such applications, routes are straightforward and easy to implement. In the world of WordPress, this is where things get really tricky.

Routes in WordPress

WordPress supports custom rewrite rules, which allow 3rd party code to hijack the request routing and create unique URL patterns to return specific assets. This means that there are an infinite number of possibilities for a WordPress site’s permalink structure (or it could even have none!). A theme should work with a user’s permalink structure, no matter what that structure may be. This was a goal for Collections from the outset and it became a mammoth challenge.

Bolting a SPA onto a WordPress theme requires that the app can handle these permalink permutations. When dealing with routers in WordPress, it is extremely difficult to write routes for all of these possibilities as it would require porting WordPress’ regex based custom rewrite rules to Backbone.js’ token based routes. As such, Collections cannot predict what permalink structure will be used.

Even if one could write routes based on WordPress’ custom rewrite rules, the results would not be useful. Only two of the five default permalink structures involve a post ID. In other words, only two of these permalinks would allow the route to extract an ID, which can be used to query the server. As such, it is not that feasible to build routes for a WordPress theme.

As a last option, the theme could define its own permalink structure to something more RESTful; however, this is rife with compatibility issues, SEO problems, and is a poor decision overall. What to do?

The Collections solution

With Collections, we ended up avoiding the problem altogether. We used a “catch all” router that will match any URL and pass it on to the defined function.


routes: {
    '*notFound' : 'default',
    ''          : 'default'
},

default: function( pathname ) {
    alert( pathname );
}

'*notFound' will match any permalink and '' (a blank string) matches any requests to the root domain or any URLs with no paths after the root domain (e.g., http://mysite.com/?id=45).

Collectively, these routes will match all URLs, then call the default function in the Backbone.Router object. Instead of passing along nicely parsed arguments from the URL, the function just receives the pathname in the URL (i.e., everything but the root domain). We are effectively sidestepping the RESTful assumptions in Backbone.js.

Turning a pathname into JSON

With WordPress, it is quite easy to display a JSON representation of a normal page. You can just think of the JSON view as a different template for the data. To achieve this, we used the following code in Collections:


function collections_add_json_query_var( $vars ) {
    $vars[] = 'cspa-json';
    return $vars;
}

add_filter( 'query_vars', 'collections_add_json_query_var' );

function collections_json_template_redirect() {
    global $wp_query;

    // If this is not a request for json then bail
    if ( ! isset( $wp_query->query_vars['cspa-json'] ) || '1' !== $wp_query->query_vars['cspa-json'] ) {
        return;
    }

    // Set the appropriate header
    header( 'Content-Type: application/json; charset=utf-8' );

    // Help prevent MIME-type confusion attacks in IE8+
    send_nosniff_header();
    get_template_part( 'json', 'posts' );
    exit;
}

add_action( 'template_redirect', 'collections_json_template_redirect' );

First, a new query variable, cspa-json, is defined. Next, on template redirect, presence of that query variable is detected. If present, a JSON template is used to display the data. This allows the theme to tightly control the exact data that it returns when a new page is requested. This data is then used to render new views.

Another reason that we chose the template based method for the JSON data is that it will work seamlessly with popular page caching solutions used in the WordPress community. If we were to use the method of pinging the ajaxurl for every request, this would significantly slow the application response.

Putting it together

When the Single Page Application (SPA) in Collections is initiated and a new user browses to a new URL, the catch all route sends the pathname from the URL to the default function. The default function appends cspa-json=1 to the pathname passed to the function. An AJAX request, using Backbone.js internals, is made to the server using this compiled URL. Due to the presence of cspa-json=1, the JSON representation of the URL is returned. The JSON data is then used with an Underscore.js template to render to new view.

This approach allows us to avoid worrying about the permalink structure of the current site because it just uses the URLs that exist. Additionally, it removes the need for a fully featured RESTful API, which is currently a tough problem in WordPress. Finally, tight control over the JSON template allows us to better control what data is returned.

Mo’ SPA, mo’ problems

Of course, this wasn’t the only challenge with building a SPA in WordPress. In the next post in this series, I will discuss the issue of plugin compatibility.

Enjoy this post? Read more like it in From the workshop.

Post a comment

Basic HTML is allowed. Your email address will not be published.

Subscribe to this comment feed via RSS

“My website is absolutely gorgeous, and the support service is literally unlike any other I’ve experienced, probably in my whole life, in any industry!”

Jacqueline Smith

“I’m blown away by the generous and effective support provided. Super accessible, laid back, and knowledgeable!”

Micah Davisyes
Discover our WordPress Page Builder Now

(Over 600 small businesses got started last week)