3
votes

I've been developing large Backbone Marionette applications for about a year now. One thing that has always been challenging is passing around options for view states in routes. Examples of options for view states would be active tabs, temporarily selected items, or sorting options on the page that need to be linkable.

Before updating to Backbone 0.9.9+ the best way that I found to deal with these cases was to add query parameters to the end of my routes. My router would look something like this:

"/questions/:id/"         : "showQuestions"
"/questions/:id/?*params" : "showQuestionsWithFilters"

Which would match something like:

"/questions/1/?search=help&sort=name"

The real advantage to this that I found is that the router will match different routes based on the presence of url parameters. Clearing all url parameters and then triggering navigation will actually cause a route change.

After Backbone 0.9.2 routers no longer recognize url parameters. In the above example, the "showQuestions" method would get fired regardless of the presence of a url parameter. The general consensus in this GH issue (https://github.com/documentcloud/backbone/issues/891) and the opinion of the Backbone contributors seems to be that url parameters should NOT be used on the client side at all and instead all information that needs to be passed on to a view should be stored in the main url path (https://github.com/documentcloud/backbone/issues/2440).

A router using this method might look something like:

"/questions/:id/(search/:term)(sort/:type/)"

The problem with this method is that every optional parameter needs to be explicitly added to the router and that all parameters must be ordered accordingly else they will not match. Because there is no delineator between the route and its options and the order is determined by the router, it seems unnecessarily difficult to add or edit options on the fly.

At this point I'm stuck between keeping my current url structure and trying to figure out a way to make it work or migrating over to the latter approach. Before I go too far in either direction I'm wondering if there are other opinions on best practices for similar use cases. What would you recommend?

1

1 Answers

2
votes

There's another piece of a route called a splat. From http://backbonejs.org/#Router:

Routes can contain parameter parts, :param, which match a single URL component between slashes; and splat parts *splat, which can match any number of URL components.

In my app, I'm using one required param, and then any number of optional "filters":

var BrowseRouter = Marionette.AppRouter.extend({
  appRoutes: {
    'browse/:page(/*filters)': 'browse'
  }
});

My URL is then formatted with a series of key/value pairs separated by slashes: #/browse/3/type:image/sort:date/count:24.

In my controller, I'm passing the function two arguments: page and filters. page is a simple value ("3"). filters is optional, and is a longer string that contains everything after the page value ("type:image/sort:date/count:2").

I have an "explode" underscore mixin to take that string and convert it to an object.

_.mixin({
  /*
   * Take a formatted string (from the URL) and convert it into an object of
   * key/val pairs. If the val looks like an array, make it so.
   * _.explode("count:105/sort:date/type:image,video")
   * => { count: 105, sort: date, type: ['image','video']}
   */
  explode: function(str) {
    var result = {};
    if(!str){
        return result;
    }
    _.each(str.split('/'), function(element, index, list){
      if(element){
        var param = element.split(':');
        var key = param[0];
        var val = param[1];
        if (val.indexOf(",") !== -1) {
          val = val.split(',');
        }
        result[key] = val;
      }
    });
    return result;
  }
});