Building Rich Internet Applications with HTML5, CSS3, JS and KnockoutJS Part 2: History and Deeplinking

Are you migrating from Silverlight to HTML5? After reading this article, you will know how to re-use some silverlight techniques on the web

This Article is part of a three-part series:

Part 2: Add History and deeplinking

In this part we will add History (Back button) and deeplinking by using fragment URLs. For example we should be able to open the Invoice page directly with this fragment URL:

../index.html#invoices

Start with the project you have made in the first part or download this project as a starting point for part 2

Lets start by adding the fragment to the URL when a view is loaded. Change the loadView function in navigation.js like this:

this.loadView = function (view) {
  var contentframe = $("#content");
  contentframe.load("Views/" + view + ".html", function () {
    window.history.pushState(view, null, "#" + view);
  });
}

As you can see, we use the callback of the load function to push a new state to the history object. You can read about the pushState method here.

Run it in the browser and we see that navigating to views will add a fragment:

fragment

But pressing the back button does not work yet. So lets implement some code to make that work. Add two functions to navigation.js

this.back = function () {
  if (history.state) {
    var view = self.getView();
    self.loadView(view);
  }
};

this.getView = function () {
  var fragments = location.href.split('#');
  if (fragments && fragments.length === 2) {
    return fragments[1];
  } else {
    return startupView;
  }
};

The back function checks if there is state available (has history) and uses the getView helper function to determine what view to navigate to. But the getView function determines the view from the current URL. What URL is that? It’s the URL that is popped from the history stack by using the popstate event.

To illustrate this, add this line to the bottom of navigation.js:

$(window).on('popstate', self.back);

When the browser fires the popstate event, our back function will navigate to the view that is determined from the current fragment. When I test this in Chrome, a breakpoint is hit when I click the back button:

back

So that works. The problem now is that pressing the back button calls the loadView function which adds state to the history object. We only want to add state to the history object if we navigate with the Customer and Invoice button. To fix this, we add the parameter pushHistory to the loadView function. The next script block shows the complete navigation.js file.

var Navigation = function (startupView) {
  var self = this;

  this.navigate = function navigate(view) {
    if (view !== this.currentView) {
      self.loadView(view, true);
    }
  }

  this.loadView = function (view, pushHistory) {
    var contentframe = $("#content");
    contentframe.load("Views/" + view + ".html", function () {

      self.currentView = view;

      if (pushHistory) {
        window.history.pushState(view, null, "#" + view);
      }
    });
  }

  this.back = function () {
    if (history.state) {
      var view = self.getView();
      self.loadView(view,false);
    }
  };

  this.getView = function () {
    var fragments = location.href.split('#');
    if (fragments && fragments.length === 2) {
      return fragments[1];
    } else {
      return startupView;
    }
  };

  this.loadView(startupView, true);
  $(window).on('popstate', self.back);
}

I have also introduced the currentView property. This way we only load a view when it’s not the current view.

Deep linking

I’m not sure if deep linking really makes sense in a business application where you probably will need to log in anyway but it is possible and in fact very easy now the hard work is done. Just change this.loadView(startupView, true); in the bottom of the file to:

this.loadView(this.getView(), true);

Open a new tab in your browser and paste a url like ../Javigation/#invoices. This is how it looks like on my machine:

my machine

And that’s all folks! In the next part we will use KnockoutJS for MVVM and databinding and we will learn how to mimic a code behind file.

Here is a zipfile with the project so far.