It's the hash, baby!    


AJAX In Action (2): Fixing The Broken Bookmark


I
N
T
R
O


his article gives a solution to a common problem of AJAX: when the content of a page changes without page load, the URL does not necessarily reflect the state of the page.



Historical Review

AJAX is a technique that enables us to create web pages with changing segments. The need for such pages brought up the concept of framesets in the early years of Internet. One can easily change a segment of a page by changing the content of one frame and leaving the others intact. A major drawback of this technique is that the URL usually refers to the default opening state of the frameset. When you are not aware of browsing in a frameset, find something interesting, bookmark it or send the URL to your friend, you or your friend will not get back the same content what you saw at the time of copying the URL. In an article, Got Lost on the Web I gave a solution to that problem. The main idea has been that a manipulated search string of the URL can reflect the state of a page. (For sake of clarity, search string is also called query string in the literature.)

The same exact technique does not work in our case. Remember, we want to eliminate unnecessary round trips to the server. One of the reasons of changing partial content with AJAX is that we don't want to request a new page or to reload the present page. However, changing the search string invokes a page reload. This has not been a problem with framesets because there we always wanted to change the content by requesting and loading a new HTML in a frame. So, what is the solution in case of single-document pages? What part of the URL can we change without the consequences of requesting a load? It's the hash!

  What Is Hash and How To Use It?

No, it's not what you think. This hash you cannot smoke. Hash is the part of URL after a # hash mark. It's original use is to navigate inside a page. The

href="www.mypage.html#middle"

attribute links to the said page and scrolls to the section starting with the anchor <a name="middle"> or with <a id="middle">. (The latter is better: the name attribute has already been deprecated in HTML 4 and future XHTML versions will completely eliminate it.) The good news is that one can set the hash property without invoking a load or navigating inside the page. If there is no anchor named "middle", the browser shows the top of the page.

With the above features of hash we can keep track of the page's status. Every time we change the page content dynamically, we can change the hash as well. In the Photo Album application the actually displayed photo corresponds to an album number and a picture number. The snippet that writes these numbers in the hash is as follows:

function SetPresentState(albumNo, picNo) {
  if (isNull(picNo)) picNo = 0;
  location.hash = albumNo + ";" + picNo;
}

The picNo = 0 state refers to the thumbnail view of an album. SetPresentState should be called every time the content changes, i.e., when we set the image source and when we switch to a thumbnail view. Then the URL corresponds with the page state. When the page shows the 3d album's pictures in thumbnail view, the URL ends with index.html#3;0 and when it shows the 4th picture of the 1st album, it ends with index.html#1;4. We can bookmark or send these URL's to a friend.

How To Retrieve The Page?

The remaining problem is that how we can reconstruct the page from the above URL. By default it makes index.html load and tries to find an anchor inside with the name or id of "3;0" or "1;4". These anchors don't exist, so, the page stays on its top. However, the following code in the HEAD enables the page to appropriately set the content both in thumbnail view and in single view:

//Constants:
var ALBUMNO = 1;//default album number
var PICNO = 1;//default picture number
//Global vars:
var g_albumNo = GetPresentState(1);
var g_picNo = GetPresentState(2);
if (g_albumNo + g_picNo < 1) {//hash is empty
  g_albumNo = ALBUMNO;
  g_picNo = PICNO;
}

function GetPresentState(paramNo) {
  return Number(parseString(paramNo));
}

function parseString(n){//N>=n>0;
//Form of str: param1;param2;...;paramN
// Returns nth param of the string.
  var delim = ';'
  var str = getHash();//hash string w/o the '#'
  if (str.charAt(str.length-1)!=delim) str += delim;
  var i = n;
  while(i>0) {
    retVal = str.substring(0,str.indexOf(delim));
    str=str.substring(retVal.length+1,str.length);//cut 1st
    i--;
  }
  return retVal;
}//end function parseString

function getHash() {
  var str = document.location.hash;
  return str.substring(1,str.length);//cut the '#'
}//end function getHash()

parseString is a simplified version of a more general function. The original one can handle various types of strings with various delimiter characters and of various origins. You can find the complete HTML, Javascript and CSS lists of the Photo Album Application in the last article of this series.

The next article discusses an asynchronous call of a function that makes the slide show happen in our Photo Album Application.