How to keep a clean browser history in a backbone.js app?

OlliM picture OlliM · Mar 28, 2012 · Viewed 9.4k times · Source

My backbone.js has three views:

  1. List of categories
  2. List of items in category
  3. Form for individual item

I'm using backbone.js router to navigate between these views. The user flows in the app go 1<-->2, 2<-->3 and 3 --> 1. The user can navigate back and forth using browser back and forward buttons, which is wanted behavior. Deep linking to any item works also.

The problem is that I want to keep the history clean. Here's an example usage flow:

  • User opens list of categories. History: "Category list" (correct)
  • User select "My category". History: "My category" < "Category list" (correct)
  • User select "My item". History: "My item" < "My category" < "Category list" (correct)
  • User fills form and saves, is redirected to "Category list". History: "Category list" < "My item" < "My category" < "Category list" ( should be just "Category list" )

Another example:

  • User opens url of "My category"
  • User presses home button. History: "Category list" < "My category", should be "Category list"

Any ideas on how to implement this in a clean way?

Answer

shanewwarren picture shanewwarren · Mar 30, 2012

There is no way to implement the first example that you provided. You cannot implement the first example because there is no way to "delete" the browser history using Javascript.

There is no way to clear the session history or to disable the back/forward navigation from unprivileged code. The closest available solution is the location.replace() method, which replaces the current item of the session history with the provided URL.

- https://developer.mozilla.org/en/DOM/window.history

At best you can prevent the current page from being added to the browser history by using window.location.replace or window.history.replaceState. Backbone.js provides a convenient way of doing this by calling router.navigate(fragment, [options]) on your router object and specifying {replace: true} in the options.

Instead of relying on different routes to determine which view to display, I would try instead writing a master view which could handle showing/hiding the specific views.

EDIT

Ok, entering hacky territory...

Since it seems that the "Category List" page is the page where history should be "reset", the solution I have posted attempts to solve both use cases you have mentioned. The code keeps track of a historyState variable, which represents when the "category-list" page is visitied as well as the other pages visited after it.

// in your application init...
$(function(){
    window.historyState = -1;
    router = new Backbone.Router({ 
        routes: {
            "category-list": category-list,
            "category": category,
            "item": item
        }
        
        category-list: function(){
            historyState = 0;
        },
        
        category: function(){
            if(historyState >= 0)
                historyState++;
        },
        
        item: function(){
            if(historyState >= 0)
                historyState++;
        }
    });
});
  • If historyState is -1 - we have not yet visited the "category-list" page.
  • If historyState is 0 - we are currently on the "category-list" page.
  • If historyState is greater 0 - number of pages visited since viewed "category-list" page.

Now anytime a link is used to navigate to the "category-list" page, make sure it calls the following method to handle the appropriate navigation.

function routeToCategoryList(){
  if( historyState > 0 ){ 
    // 'category-list' already exists in our history route to that page.
    window.history.go((historyState * -1));
  } else {  
    // otherwise, don't store an entry for the current page we are on.
    router.navigate("/category-list", {trigger: true, replace: true});  
  } 
}

If the 'category-list' page has already been visited go back in history the appropriate number of entries (this unfortunately keeps the other pages in the history, so you can still go forward to them). Otherwise if the 'category-list' page hasn't been visited yet navigate to it and be sure not to add the current page to the history.