Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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...

like image 462
TIMEX Avatar asked Mar 01 '13 19:03

TIMEX


2 Answers

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.

like image 186
jevakallio Avatar answered Oct 24 '22 03:10

jevakallio


You don't have to use the backbone-query-parameters plugin to deal with this anymore, just make sure that you have the latest version of Backbone and then overwrite the loadUrl method inside History.prototype.

// Regex to match search query strings
var searchStripper = /\?.*$/g;

// Attempt to load the current URL fragment. If a route succeeds with a
// match, returns `true`. If no defined routes matches the fragment,
// returns `false`.
loadUrl: function(fragmentOverride) {
  var fragment = this.fragment = this.getFragment(fragmentOverride);
  fragment = fragment.replace(searchStripper, ''); // remove the search query parameter
  var matched = _.any(this.handlers, function(handler) {
    if (handler.route.test(fragment)) {
      handler.callback(fragment);
      return true;
    }
  });
  return matched;
},
like image 30
mateusmaso Avatar answered Oct 24 '22 05:10

mateusmaso