Jim Driscoll's Blog

Notes on Technology and the Web

Thin Server the Hard Way (Routing)

leave a comment »

This post is part of a series examining the Modern Web. Currently, I’m trying to assess pain points in creating a Single Page application, and to do that, I’ve created a simple application which does inventory management. You can find the (somewhat sloppy) code up on github, if you want to follow along.

Previously, I covered the basic architecture. Today I’d like to examine how to handle routing via a front controller. As I’ve mentioned before, this proved to be one of the easier tasks.

As background, recall that the anchor is the last portion of every URL, and is set off from it via the # character, which is also called a hash (or octothorpe, if you’re a serious geek). All of our href values will just have these anchor tags, such as href="#home". When a user clicks on that link, we want to rerender the page to make it look to the user like they’ve gone to a new place – but without having to roundtrip to the server to get all of the new HTML. Possibly, we may not have to go to the server at all, if we’ve cached values. This will give the user a much snappier experience, and is pretty much how most modern web sites work nowadays (though some also use the newish history.pushState function, which lets you avoid all this anchor stuff on compatible browsers).

The pattern to follow here is a simple one: For any action which you want the user to be able to bookmark, use an anchored URL. For any action which is inappropriate to bookmark (such as deleting a record), use a click handler.

First, we create a module (jqcontroller) which will handle all the routing. Inside it, we’ll create a hardcoded routing table, which will associate names with the route to take:

// routing table
var routingTable = {
    home: jqproduct.displayHome,
    products: jqproduct.displayProductsPage,
    categories: jqproduct.displayCategoriesPage,
    productAdd: jqproduct.displayProductAddPage,
    defaultPage: jqproduct.displayHome
},
defaultPage = jqproduct.displayHome;

So, when we receive a URL that looks like #home, we’ll call the function jqproduct.displayHome.

We also need to create an onhashchange handler:

function hashChangeHanderSetup() {
    // when the hash changes, go through the router
    $(window).on('hashchange', function router() {
        jqcontroller.route(location.hash);
    });
}

Here, we’re using jQuery to add a new handler on the window object. When the URL’s hash changes, call the jqcontroller.route function, passing in the new hash value.

Of course, we have to call that setup message during initialization for it to work. While we’re at it, let’s allow for routing to an initial location, so that when users bookmark the location, navigating back to it functions correctly:

initializeModule: function initializeModule() {
    hashChangeHanderSetup();
    // initial route
    jqcontroller.route(location.hash);
}

The actual router code called by these functions couldn’t really be much simpler, though it’s complicated by one additional requirement – we also want the hash to contain parameters, so that if you search, for instance, by product name, the hash may look like #products&search=Time – we’ll need to strip that out, and so we’ve created an additional utility method to do that called getPage:

route: function route(url) {
    try {
        var page = pub.getPage();
        var hash = url.split('#')[1];
        if (!!hash) {
            location.hash = hash;
        }
        if (!page || !routingTable[page]) {
            defaultPage();
        } else {
            routingTable[page]();
        }
    } catch (error) {
        // in production, this could write to console, 
        // or do something else useful with error reporting
        window.alert(error);
    }
}

Here, the meat of the code is simply calling routingTable[page](), which means “look up the value of page in the routing table, and execute that as a function”.

So, that’s it in a nutshell. As I mentioned, there’s additional code to handle parameter passing in a hash, but otherwise, there’s not much else.

As pain points go, this isn’t so bad. It’d be nice to have all this code encapsulated in a reusable library, but doing it myself wouldn’t be a terribly difficult task. Of more concern is that there isn’t any support in my code for history.pushState() and related APIs. Though as I mentioned, there needs to be server side support for that as well.

So, any MV* framework would need to support such a simple front controller pattern, as well as (optional) pushState. But since that’s a rather low bar, I don’t expect that to be an issue.

Next up, I’ll talk about implementing the Model layer, which was another fairly easy task.

Advertisements

Written by jamesgdriscoll

January 22, 2014 at 6:54 PM

Posted in JavaScript, web

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: