When I push a new URL to Backbone.history, the query params stays?

TIMEX picture TIMEX · Mar 1, 2013 · Viewed 10k times · Source

Let's say I'm using Backbone pushstate and I navigate to a page with query params:

domain.com/user/111?hello=123

When I execute this:

Backbone.history.navigate('/settings', true);

My settings page loads perfectly, but the ?hello=123 stays in the URL...

domain.com/settings?hello=123

And that query param stays in the URL everywhere I navigate the site...

Answer

jevakallio picture jevakallio · Mar 4, 2013

Backbone routing and query parameters are an unhappy marriage. Problems are well documented in this GitHub issue.

The core problem is that Backbone.Router is designed to work with URL hash fragments as well as the pushState API. When using hash URLs the query string precedes the hash, and is never matched in the route. With pushState the query string is part of the URL fragment, and requires a different route expression.

Let's say you'd have a route search, and that route would optionally take parameters q, sort and type. As a query string that would look something like:

search?q=kittens&sort=asc&type=images

The problem is, that for users of older browsers, Backbone will revert to hashchange based routing, and the route will become:

?q=kittens&sort=asc&type=images#search

The plugin you use tries to work around this problem, but doesn't resolve the core issue.

If possible, you should consider not using query strings, and pass any state information using optional fragments in your route expressions. The previous example routes would then become:

//pushState
search/q/kittens/sort/asc/type/images

//hash fragment
#search/q/kittens/sort/asc/type/images

Using (optional) route parts and :captures (docs), you could represent this URL with the following expression:

var Router = Backbone.Router.extend({
  routes: {
    "search(/q/:query)(/sort/:sort)(/type/:type)": "search"
  },

  search: function(query, sort, type) {
    console.log(query, sort, type); //-> "kittens", "asc", "images" 
  }
});

As long as the route fragments are in the specified order, this will match urls with none, any and all parameters, for example:

search                       //->  undefined,  undefined, undefined
search/q/kittens/type/images //->  "kittens",  undefined, "images"
search/sort/asc/type/images  //->  undefined,  "asc",     "images"

This way you don't have to worry about third-party query string libraries or browser compatibility. And if you ask me, the latter type of URL looks cleaner as well.