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.