It’s very common to use backbone’s router to drive application state with the window’s url. The basic idea is that popstate or hashchange events are matched to a hash of routes and invoke a handler function. This approach works well for changing views, but not so well for setting application state. For example, consider these routes:
var routes = {
'(/)' : 'index',
'posts' : 'showPostList',
'posts/:postId' : 'showPost'
}
This works great if you want to show a list of posts and an individual posts. But what if on the posts list page you want to filter by author. You might be tempted to write a route like this:
var routes = {
'posts?author=:authorName'
}
This wont work. Backbone’s router doesn’t read query string parameters. To get these values, I suggest making a backbone model that is binds to the query string and automatically updates its attributes on popstate changes. Going a step further, updating the model should update the query string.
While it would be great to use the HTML5 pushState api, its actually “disabled” on iOS. As far as I can tell, this essentially means its not supported at all. So instead of get into the nitty-gritty of hacking a solution for iOS I will just use this library for reading and writing to the url.
The basic model looks like this:
var createHhistory = require( 'history' ).createHistory;
var useQueries = require( 'history' ).useQueries;
var history = useQueries( createHistory )();
var QueryStringModel = Backbone.Model.extend( {
initialize: function ( opts ) {
// only track attributes configured
this.track = opts.track;
// flag for prevent infinite loops from occurring
this.isPopstate = false;
// setup a listener to url changes
history.listen( this._pop );
// setup a listener for model changes
this.listenTo( this, 'change', this._push );
},
/**
* Handles history change events
*/
_pop: function ( location ) {
// history will invoke this callback on POP, REPLACE, and PUSH, s
// so short-circuit on all but POP events
if ( location.action !== 'POP' ) {
return;
}
// compile an object of attributes to update
var params = location.query;
var toUpdate = _.pick( params, this.track );
// set isPopstate flag to true
this.isPopstate = true;
// update the model
this.set( toUpdate );
// reset isPopstate flag to false
this.isPopstate = false;
},
_push: function () {
// short circuit if flag is true (ie, this model change event was
// triggered by this._pop)
if ( this.isPopstate ) {
return;
}
// pick the tracked attributes
var params = _.pick( this.attributes, this.track );
// push them up
history.pushState(null, window.location.pathname, params );
}
} );
Using the model would like this:
var myAppModel = new QueryStringModel( {
page: 1,
sort: 'date'
}, {
track: [ 'page', 'date' ]
} );
Or as a base model to extend From
var AppModel = QueryStringModel.extend( {
defaults: {
page: 1,
sort: 'date'
}
} );
var myAppModel = new AppModel( {}, {
track: [ 'page', 'date' ]
} );
Now you can listen to changes on this model and render your views accordingly.