Feel like a geek and get yourself Ema Personal Wiki for Android and Windows

01 September 2009

How to get a working Back-button for an AJAX app in Internet Explorer

In an AJAX application, the back-button will take you to the wrong place because in AJAX applications the actions don't update the browser location. There are some solutions that change the hash of the Location (the part behind the #), but those solutions do not work in Internet Explorer.

The problem with IE is that a change in the hash will not lead to an entry in the history, so the back button will take you back to the previous page anyway. IE does react if you change the src attribute of an IFRAME, so most solutions for IE are in the direction of using a hidden IFRAME.

Since I did not find any actually working code, I post it here for future reference. The code works in IE, FF and Chrome.

The main page contains an iframe. The source of the iframe is an actual document on the server (iframe.html). The only thing the iframe document does is echo it's source url back into the parent page location hash:
<html>
 <body>
  <script type="text/javascript">
   function parseLocation() {
    var src = location.href;
    var pos = src.indexOf('href=');
    if (pos === -1)
     src = '' //home
    else
     src = src.substring(pos + 5);
    top.location.href = top.location.pathname + '#' + src;
   }

   parseLocation();
  </script>
 </body>
</html>

The parent page redirects anchor clicks to update the src attribute of the iframe. Because the iframe will be reloaded, IE will update the history of the browser. The iframe writes the src url back into the parent page location. The parent page monitors the location and loads the appropriate url in the appropriate div:
<html>
  <head>
    <title>Ajax test</title>
  </head>
  <body>
    <script type="text/javascript" 
      src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js">
    </script>
    <script type="text/javascript">
      var baseHref = location.protocol + '//' + location.host;
      
      $(document).ready(function() {
        createAjaxLinks();
        setInterval(checkLocationHash, 50);

        $('#trackingframe').get(0).src = iframeSrc + '?href='
          + location.hash.substring(1);
      });
      
      function createAjaxLinks() {
        $('a.' + ajaxLinkClass)
          .removeClass(ajaxLinkClass)
          .click(function(){
            var href = this.href;
            if (href.indexOf(baseHref) > -1)
              href = href.slice(baseHref.length);

            $('#trackingframe').get(0).src = iframeSrc 
              + '?href=' + href;
            return false;
          });
      }
      
      function checkLocationHash() {
        if (location.hash !== this.lastHash) {
          var url = null;
          if (location.hash === '' || location.hash === '#') { 
            //home
            url = initialDocument;
          }
          else {
            var url = location.hash.substring(1);
          }
          if (url !== null) {
            url = baseHref + url;
             $('#siteContainer').load(url, null, createAjaxLinks);
          }
          this.lastHash = location.hash;
        }
      }
      
      var initialDocument = '/1.html'; //absolute url
      var iframeSrc = '/iframe.html';  //absolute url
      var ajaxLinkClass = 'ajaxLink';  //class for ajax links
    </script>
  
    <div id="siteContainer">
    </div>
    <iframe id="trackingframe" style="display:none">
    </iframe>
    
  </body>
</html>
For this example I created three test documents, named 1.html, 2.html and 3.html, which will in turn be loaded in the siteContainer div.

1.html:
<a href="2.html" class="ajaxLink">Load the second document</a>

2.html:
<div>
 This is the second document. 
 <a href="3.html" class="ajaxLink">Load the third</a>
</div>

3.html:
<div>Third document</div>

This example loads all links with class "ajaxLink" into the "siteContainer" div. In real life, you would probably want more control over in which div the link will be loaded. This can be accomplished by including the "target" in the class somehow and parse the class of the link to find this target.

No comments: