Collective Idea

Collective Idea Logo

Tim Bugai

Standalone Javascript Routing

By Tim Bugai on January 25, 2012 in coffeescript, javascript, routing, and url

A recent project has us using spine.js as well as a few other JavaScript libraries. Though spine.js comes with its own routing, it conflicts with pjax. The solution was to roll our own.

The requirements are simple, we should be able to give it a set of routes and their corresponding callbacks and we should be able to process the browsers url upon request. The CoffeeScript for that looks like this:

class Router
  @add: (path, callback) ->
    @routes ||= []
    @routes.push { 
      path: path, 
      callback: callback 
    }
  @process: ->
    for route in @routes
      params = window.location.pathname.match(route.path)
      if params?
        route.callback(params)
        return

This code works exactly as we want it too, but there are a few things I’d like to make better. The first of which is that we have to provide a regex as the path so our routes would look something like this:

Route.add /\/blog\/(\w*)/, blogCallback

It would be much easier to read and maintain if it followed more of a rails-routing-style semantic like this:

Route.add “/blog/:id”, blogCallback

We can make this possible by processing the path string into its regex counterpart inside Route.add()

The first thing we need to do is escape the forward slashes ( / )

path.replace(/\//g, “\\/”)

Then we need to change everything starting with a colon into a capture field

path.replace(/:(\w*)(?!(\w))/g,“(\\w*)”)

And then we convert the fancied up string into a RegExp and save it as the path for our route. I’ve chained all of the replaces() together for simplicity.

path = new RegExp(path.replace(/\//g, “\\/”).replace(/:(\w*)/g,“(\\w*)”))

(thanks to Jonathan Castello for pointing out we were being too cautious with the last regex)

The updated class looks like this:

class Router
  @add: (path, callback) ->
    @routes ||= []
    @routes.push { 
      path: new RegExp(path.replace(/\//g, "\\/").replace(/:(\w*)/g,"(\\w*)")), 
      callback: callback 
    }  
  @process: ->
    for route in @routes
      params = window.location.pathname.match(route.path)
      if params?
        route.callback(params)
        return

Then in our project, we have the following setup code.

$ ->
  Route.add "/blog/:id", blogCallback
  $(document).on "ready end.pjax", ->
    Route.process()

Whenever our document is ready or a pjax link has completed, our routes get matched against the url and the appropriate callback gets fired.

By Tim Bugai on January 25, 2012 in coffeescript, javascript, routing, and url

10 Comments

  1. Zee

    Zee January 25, 2012 http://zee@zacharyspencer.com

    I ran into some issues with backbone routing in regards to having get variables at the end of a url (i.e. for sorting, etc)

    It appears your regex also struggles with that?

  2. Tim Bugai

    Tim Bugai January 25, 2012 http://collectiveidea.com

    @Zee:

    In the project we are working on, we have a separate class that handles the query string. So this code ignores it. I may extend it in the coming days to provide them as well.

  3. Jonathan Castello

    Jonathan Castello January 25, 2012 http://jonathan.com

    Is the negative lookahead in /:(\w*)(?!(\w))/g necessary? \w* should be greedy by default, so it will always consume as many characters as it can.

  4. jipiboily

    jipiboily January 25, 2012

    There’s a mistake in the link “own routing”. Missing “:” after http…;)

  5. Chris Gaffney

    Chris Gaffney January 25, 2012 http://collectiveidea.com

    @jibiboily: Thanks for the head’s up, fixed it.

  6. Tim Bugai

    Tim Bugai January 25, 2012 http://collectiveidea.com

    @Jonathan Castello:

    You are correct! I was being overly protective against / and ?. I’ll update it.

  7. EJ

    EJ January 25, 2012

    Rather than escape the / in your original example, you should use a different start character like | or !. Also, doesn’t your code drop the string "id"’unlike ruby. It seems confusing to label if it’s not used.

  8. Charlie Robbins

    Charlie Robbins January 25, 2012 http://www.nodejitsu.com

    Rolling your own routing is a great exercise in programming. Flatiron has it’s own stand-alone Router which supports pushState or hash-based routing:

    http://flatironjs.org/#routing
    http://github.com/flatiron/director

  9. webdesign porto

    webdesign porto January 27, 2012 http://webdesignporto@gmail.com

    I din’t thought of making it rails-style. You could also use this to your advantage in order to make a fake named groups system. You should really put some default arguments and override arguments. They are quite handy.
    Anyway thanks for the good tip/idea. :D

  10. chad

    chad February 18, 2012 http://chadwik.us

    One modification I made to my implementation of this is to do a secondary check to see if the route actually equals location before calling the callback.  I did this because I was having problems with using the location for a route that was ‘/’.  ’/’ is qualified therefore those scripts will shot gun through the app and be called on every route.

Post a Comment

Contact Us

Find us on Google Maps
Collective Idea
44 East 8th Street, Suite 410
Holland, Michigan 49423 USA 42.790334-86.105251

Follow us on the Interwebs

We are currently available for medium and long term projects. Please get in touch if we can be of service.