Restore backbone state after navigating away and then back - backbone.js

I have a Backbone Marionette app which allows users to search using various criteria. From the search results they can click on a link which navigates them away from the backbone app to a standard static page.
How can I set things up so that when they click back in their browser, the backbone search page is restored back to their previous state (with search criteria and results intact)?
Thanks

You could use backbone router for your search page and save state in location.hash, so all population/rendering will be managed by router or views which will listen to router events:
"route:[name]" (params) — Fired by the router when a specific route is matched.
"route" (route, params) — Fired by the router when any route has been matched.
For example #search/query/nuggets will trigger this route:
'search/query/:query-string': function(query) {
yourCollection.fetch({data: {query: query}})
}
and in your view
initialize: function() {
this.listenTo(yourCollection, 'sync', this.render)
}
so then user click/or hit enter in your search field you just should trigger route change: yourRouter.navigate("search/query/"+yourQuery, {trigger: true})

Related

Backbone.history is not updating in IE 9. Back button broken

In our app, we actually have two Backbone SPA applications. The first one is for login, registration and other features for unauthenticated users. The URL for this would be something like http://www.example.com/registration#signin. Once you login, you are redirected to our main Backbone app at http://www.example.com/ui#home.
In my main UI app, I am using Backbone.history without pushState. The App file looks something like:
define(function (require) {
var App = new Marionette.Application();
App.addInitializer(function (options) {
...
});
...
App.on('initialize:after', function () {
$(function(){
if (Backbone.history) {
Backbone.history.start({ root: '/ui' });
}
});
$.log("**WebApp**: Marionette app started.");
});
return App;
});
Of course, everything works flawlessly in any browser except IE 9 (and maybe 10, I need to check). In IE 9, all the routing works fine. Clicking links such as http://www.example.com/ui#anotherpage works. However, when the user clicks the Back button in their browser, they are not sent back to the last route fired. Instead, they are sent to http://www.example.com/registration#signin, which is the last page served by Node, our web server. As I click through links, I can see that history.length and Backbone.history.history.length are not updating.
All routes are fired from links/URL's. I'm not using router.navigate() within the code. Here are examples of our Router:
define(function (require) {
var Backbone = require('backbone'),
Marionette = require('marionette');
return Backbone.Marionette.AppRouter.extend({
appRoutes: {
"": "showHome",
"home": "showHome",
"foo": "showFoo"
}
});
});
And Controller:
define(function (require) {
var Backbone = require('backbone'),
Marionette = require('marionette');
return Backbone.Marionette.Controller.extend({
showHome: function () {
require(['webapp','modules/home'], function (WebApp) {
WebApp.module("Home").start();
WebApp.module("Home").controller.showModule();
});
},
showFoo: function () {
require(['webapp', 'modules/foo'], function (WebApp) {
WebApp.module("Foo").start();
WebApp.module("Foo").controller.showModule();
});
}
});
});
UPDATE:
On further research, it turns out the problem is that older versions of IE don't record hash changes in their history. See - Change location.hash and then press Back button - IE behaves differently from other browsers. But I'm still not sure what the workaround for this would be. I'm guessing it would somehow involve manually handling hash change events with a plugin such as jQuery Hashchange and doing... something? Manually setting IE's history? Or crafting a custom history object and using it when we detect a Back button in IE?
I was having the same problem in one of our apps for IE.
Starting backbone history like below works.
Backbone.history.start({
pushState: true,
hashChange: false
});
Update: As mentioned By T Nguyen,
When you set pushState to true, hash URL's no longer trigger routes. Unless you add server-side support for all your Backbone routes, you need to add an event handler on the client side which captures appropriate links and calls .navigate() on the route

Backbone View injected links reload Site

After rendering a Backbone View I inject HTML generated with jQuery. This HTML also includes links within the application. If you click those links they reload the site.
How can I bind those links so they will trigger the router and don't reload the site?
You have to bind a click event to those links and call Router.navigate. It's important that you return false from your event handler as this will prevent the borwser to actually follow the link. Another important thing is to pass trigger: true to actually have your router execute (otherwise it will only change the url displayed in the address bar).
events : {
'click a.changeView' : 'changeView'
},
changeView : function(e) {
Router.navigate(e.target.href, { trigger: true });
return false;
}
Also, you might have to tweak your href a bit if it contains protocol, domain, etc... for instance if your href is http://mydomain.com/mypage you might need to pass only mypage to the router.

Backbonejs - should a change in the model cause the route to change, or should all changes be through the route first?

When a model is changed, I update the route (it has a url that contains the application's current state).
When a url is visited (or back is pressed) I update the model from the route.
This creates circular logic problems for me that I can't get my head around. Things are being changed twice for no reason.
Is it normal to base everything on the route, and use that to update the model?
Is it normal to have two models?
What is normal?
Any help or advice would be appreciated. Thanks
I wouldn't advise using Router the way you do. In general, the route action should not change model state. In general, HTTP GET operations should not have side-effects.
Routers should be used for navigation between different pages of a single-page application. Model changes should be triggered directly from the view code that handles user input. Let's say you have a model User, and view UserView, the view could work something like this:
var UserView = Backbone.View.extend({
events: {
"click #save", "save"
},
initialize: function(options) {
this.model = options.model;
},
render: function() {
//your render code here
},
save: function() {
var fields = {
name: this.$("#name").val();
email: this.$("#email").val();
};
this.model.save(fields , {
//after save go back to users page, or whatever
success: function() { window.location.hash = "/users"; },
error: this.displayError
});
}
});
Backbone isn't really an MVC framework, so the Router shouldn't be treated as a pure controller. And even if you did, changing state in a route action would be equivalent to changing state in a MVC controller GET endpoint - bad, bad idea.
If you want to adhere to a pure MVC pattern, you should implement your own controller layer, or look at another layer besides Backbone.

Backbone routing keeping hashtags when navigate()

Hello here is my scenario.
I have these routes
routes: {
"": "show_group_list",
"!/group/:_id/": "show_group",
},
and here is my navigate function:
App.app.navigate('!/group/'+group.get('_id')+'/', { trigger: true });
when the function is triggered, on the address bar it shows localhost/group/1 instead of localhost/#!/group/1. The problem is that when I refresh the page I don't get the initial page anymore (mine is a single page app)
How can I hack navigate() so that it keeps the hashtag?
Ok, This was easy, I had pushState enabled. Disable pushState and you'll have back the hash

Backbone.js history 'on route change' event?

Is there a general event that fires every time we navigate to a different URL?
window.App =
Models: {}
Collections: {}
Views: {}
Routers: {}
init: ->
# Initialize Routers
new App.Routers.Main()
# Initialize History
Backbone.history.start(pushState: true)
# BIND VIEW CHANGE?
$(#).on 'changeOfRoute', ->
console.log "Different Page"
$(document).ready ->
App.init()
Doing this per view is possible, but I'm looking for a general solution.
There is the "route" event on the Router:
http://backbonejs.org/#Events-catalog
"route" (router, route, params) — Fired by history (or router) when any route has been matched.
This allows you to bind to specific routes.
If you want to fire a handler after any route, bind to "route", and the route will be the first argument:
myRouter.on("route", function(route, params) {
console.log("Different Page: " + route);
});
This will only trigger events for your explicitly defined routes. If you want to trigger events for routes that are not explicitly defined, then add a 'splat' route as per How to detect invalid route and trigger function in Backbone.Controller
From the Backbone docs
This method is called internally within the router, whenever a route matches and its corresponding callback is about to be executed. Override it to perform custom parsing or wrapping of your routes, for example, to parse query strings before handing them to your route callback, like so:
var Router = Backbone.Router.extend({
execute: function(callback, args) {
args.push(parseQueryString(args.pop()));
if (callback) callback.apply(this, args);
}
});
This site has some useful code for redefining the Router to support 'before' and 'after' hooks, though it would require updating with each version-change of Backbone.
#TTT: Unfortunately Backbone doesn't give us a before/after event, so you will need to overwrite or extend the Router.route. You can find the way to do that in this answer: https://stackoverflow.com/a/16298966/2330244
May be this extension would be useful for you:
https://github.com/zelibobla/Backbone.RewindableRoute/blob/master/backbone.rewindableRoute.js

Resources