Friday, December 19, 2008

How to implement RSH (Really Simple History) for Ajax History Back Button

UPDATE (Mar 2010): I've been looking for more easy to implement browser history management solutions and I think that YUI is a better candidate than RSH in terms of the ease of implementation. Here is my new post on overview of history management solutions and a sample YUI implementation - Implementing YUI Browser History Manager

How to use RSH Really Simple History - perhaps one of the best solutions to the Ajax history back button problem. For those who don't know about the ajax browser history and back button problem - browser's back button doesn't work for Ajax asynchronous calls because Ajax results in partial rendering of pages and therefore browser history is not recorded/saved for all Ajax asynchronous calls. Why - again because all Ajax calls result in partial web-page rendering and for browser history to work (for browser to know when to save the current state in history), a full page refresh has to happen. Browser's are just built that way and we can't really control the history management right out of the box. Another problem is that browser's don't have any events for back and forward buttons. So even if we use something like URL Fragments to get the browser's back button to work for Ajax applications, still we wouldn't know if the user is going back or forward without any system that knows how to store the browser history in a stack and pop it when user hits browser's back button (specifically for ajax ofcourse).

After looking at Gmail, Facebook and Orkut, I figured that a good Ajax browser history back button solution will use iframes and URL fragments in some way. Yeah, Gmail, Facebook and Orkut have flawless ajax history (back button) management. Yeah, gmail has the most awesome ajax back button solution in place. Kudos google coders and designers! Facebook is also not lagging behind, you can see the URL fragments in Facebook's URL but it doesn't always seem to work properly (some times the page redirects!). Orkut too, is using some solution that relies on URL fragments and possibly iFrames. So looking at the ajax browser history (back button) managements in these social networking sites, I got some idea about what I'm looking for and found this thing called RSH (Really Simple History) that seems like the perfect solution (so far).

RSH (Really Simple History) uses iFrames (and more for that matter!) to maintain the stack of browser's history. RSH (Really Simple History) also uses URL fragments to store the browser history. Why - because remember that for an ajax asynchronous call browser doesn't even "activate" the back button. This URL fragments technique used by RSH (Really Simple History) is a hack to "activate" the back button because remember that the browser's back button works for all URL fragment clicks. So, by updating the URL fragments Really Simple History (or RSH) create a "state" in browser's history. Yeah, Really Simple History is using iFrames to actually store the browser history stack for ajax calls and it's using URL fragments to make the browser's back button work for each of those ajax calls (tricking the browser into thinking that user navigated to some link! Neat RSH trick!).

Ok, enough with the talk, lets get back to how to implement RSH or Really Simple History. (Yes, that's kinda the main point of the blog because very few sites explain how to use or how to implement Really Simple History (RSH). Please remember that the code samples I provide below come from ASP.NET development environment but being Javascript/JQuery (clientside code basically) there's not much difference.

1: Download JQuery and include in your web page (skip if already using them). JQuery can be downloaded from JQuery Download for RSH (Really Simple History) and JQuery version (1.2.6 in the code below) will change as per the latest JQuery version you download.

<script type="text/javascript" src="<%= ResolveUrl("~/js/jquery-1.2.6.min.js") %>"></script>

2: Download RSH (Really Simple History) library from http://code.google.com/p/reallysimplehistory/downloads/list and include the the minified JQuery and JSON files (again the version numbers will change as per your latest RSH and JSON versions download).


<script type="text/javascript" src="<%= ResolveUrl("~/js/json2007.js") %>"></script>

<script type="text/javascript" src="<%= ResolveUrl("~/js/rsh.compressed.js") %>"></script>

(JSON2007.js and JSON2005.js were included in the Really Simple History package so I didn't have to download JSON separately).

3: FTP or Upload blank.html (that is there in RSH package) to your website where you want to use Really Simple History. This is important because Really Simple History uses the blank.html file to store the browser history stack (as an iframe in your main web page).

4: Create a method that handles any changes in history events. Please remember from earlier in this article that browsers don't have any events for back and forward buttons so we can't handle them (the back button navigation specifically). Now, Really Simple History can handle back (and forward) button on your behalf and all you need to do is to provide a function that takes a stored history state (that you store a bit later in this article) and decides what to do. If this doesn't make a lot of sense, hang on and just look at the sample code for that method below. I'll explain it in more detail in a little bit.

function historyChange(newLocation, historyData)
{

ShowOldContent(newLocation, historyData);

// OR to simplify

alert('hey I'm going back to ' + newLocation);

}

5: Execute the dhtmlHistory.initialize and dhtmlHistory.addListener methods to your onload function to initialize Really Simple History and create the appropriate listeners respectively. Sample code follows and please take a look at the comments for more clarification -

window.onload = function() {
//Simply initializes the dhtmlHistory object. No change needed.
dhtmlHistory.initialize();

//Adds a listener for browser's back/forward navigation. Just change "historyChange" to
// your method of choice. Note that "historyChange" comes from step 4 above.
dhtmlHistory.addListener(historyChange);

//This adds a starting point to the whole browser history thingy. Not really optional though.
dhtmlHistory.add('default.aspx','link');

};


Please note that whatever you put in dhtmlHistory.add() function, as the first argument, will show up as a URL fragment in your webpage. So if your webpage is http://www.mywebpage.com/ then it will become http://www.mywebpage.com/#default.aspx if default.aspx was your first argument to dhtmlHistory.add() function.

6: Call dhtmlHistory.add() function wherever you want to store the browser history for ajax calls! For example, in your buttons that do some ajax calls, you can call it in the onclick() method. What the dhtmlHistory.add() function will do is to call your historyChange() method in turn and execute your history management code.

So, the final sample code looks like -

<script type="text/javascript" src="<%= ResolveUrl("~/js/json2007.js") %>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/js/jquery-1.2.6.min.js") %>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/js/rsh.compressed.js") %>"></script>

<script type="text/javascript" language="javascript">
window.onload = function() {
dhtmlHistory.initialize();
dhtmlHistory.addListener(historyChange);
dhtmlHistory.add('default.aspx','link');
};

function historyChange(newLocation, historyData)
{
ShowOldContent(newLocation, historyData);
// OR to simplify
alert('hey I'm going back to ' + newLocation);
}
</script>

Yes! You are done! It's as simple as that!

Sample Implementation: Now let's go over a sample implementation because so far it was all pretty much up in the air and you might not be sure about what to put in historyChange() method and what to pass as arguments in dhtmlHistory.add() method and so on. So, I'll present two sample implementations of RSH (Really Simple History) . One is a a wizard like problem and the other is navigating using links in a fancy ajax way. This is what I had to do myself in various different projects.

How to use RSH (Really Simple History) to implement (anchor tags) navigation - This is a common problem in which the user clicks a link (anchor tag) and the whole page goes back for a second as new page is loaded. This interferes with the user's overall experience and might even drive him away the more impatient ones. No need to say that a seamless link (anchors) navigation can help most e-commerce websites considerably. This was always considered a necessary evil though but with RSH (Really Simple History) has come to our rescue and here I present one easy solution to integrate Ajax with page navigation -

1: Attach dhtmlHistory.Add() to all anchor tags's onclick() event handlers
2: Call $('#').load() in your historyChange() function

And you are done! Yeah, that was it! I'll be writing in more details about it though as I think it's a rather big topic in itself if presented with all the required code etc.

How to use RSH (Really Simple History) to implement Ajax driven wizards - Let's say we have a drop down (select) as step 1 and 2 radio buttons in our body tag -

<h1>Ajax driven Wizard</h1>
<div id="divStep1">
<asp:DropDownList ID="ddlStep1" runat="server">
<asp:ListItem Value="">Please Select</asp:ListItem>
<asp:ListItem Value="itep1">item1</asp:ListItem>
<asp:ListItem Value="itep2">item2</asp:ListItem>
</asp:DropDownList>
</div>

<div id="divStep1">
<asp:RadioButton runat="server" groupName="rbStep2a" ID="rbStep2" />
<asp:RadioButton runat="server" groupName="rbStep2b" ID="rbStep2" />
</div>

We attach the javascript event handlers to above as follows (one of the many ways of attaching event handlers) -

protected void Page_Load(object sender, EventArgs e)
{
ddlStep1.Attributes["onchange"] = "moveNext('step2')";
rbStep2a.Attributes["onclick"] = moveNext('step1')";
}

"moveNext" is just some method here that shows the "next" step of the wizard and and saves the data to database so that you can keep track of the intermediate steps of a wizard in case user leaves in the middle

function moveNext(nextStep)
{
if(nextStep == 'step2')
{
$("#divStep2").show();
$("#divStep1").hide()
}
else if(nextStep == 'step1')
{
$("#divStep1").show();
$("#divStep2").hide()
}
dhtmlHistory.add(nextStep,'WizardStep');
saveToDatabase(nextStep);
}

Yes, the above code will create a cyclic not-so-much-of-a-wizard! Please give special attention to the bolded line. This line actually adds the "next step" to browser history and as soon as it's executed you will see # as a URL Fragment. Yeah, that's RSH at work right there.

Using above as a reference, we can write historyChange method as below. This implementation too just shows the "next" div and looks a lot like moveNext() method above but it can change to do anything as per your requirements.

function historyChange(newLocation, historyData)
{
if(newLocation == 'step2')
{
$("#divStep2").show();
$("#divStep1").hide()
}
else if(newLocation == 'step1')
{
$("#divStep1").show();
$("#divStep2").hide()
}
}

Please note that I haven't use the historyData object at all in the above example because I simply didn't need to store anything else. But if I need to store something then yes, we can put anything we want in the historyData object. So, thats how we can use RSH (Really Simple History) for an Ajax driven wizard that allows the user to use back button to go back to the last question (and saves the data to the database as a bonus) with refreshing the page!

21 comments:

  1. Very descriptive but I still can't quite figure it out. Are there any working examples of this anywhere that we could check out please? Thanks.

    ReplyDelete
  2. Yes, we've implemented it for many clients, they are all business websites though and they'd not prefer the links to be posted. But I can give you some simplified html to work off with. In the below html (that I kinda wrote off the top of my head just now), only the content of div with the id as 'content' changes when links are clicked. You can create 2 sample pages page1 and page 2 and put this code in there and then try clicking links and back button to see what happens -

    <html>
    <head>
    <script type="text/javascript" language="javascript">
    window.onload = function() {
    dhtmlHistory.initialize();
    dhtmlHistory.addListener(historyChange);
    dhtmlHistory.add('default.aspx','link');
    };

    function historyChange(newLocation, historyData)
    {
    $.load((newLocation + ' #content > *');
    }
    </script>
    </head>
    </html>

    <body>
    <form>
    <a href="page1.aspx" onclick="$.load('page1.aspx #content > *');dhtmlHistory.add('page1.aspx','link');">page1</a>
    <a href="page2.aspx" onclick="$.load('page2.aspx #content > *');dhtmlHistory.add('page2.aspx','link');">page2</a>

    <div id='content'></div>

    </form>
    </body>

    ReplyDelete
  3. A slight correction - </html> tag will be at the end

    ReplyDelete
  4. This sucks! While it works fine in FF, just adding the rsh library to my page breaks IE7 css and makes it act all strange - trying to reload the whole page twice when doing an ajax call - even if nothing has been initialized - only the library has been added.

    ReplyDelete
  5. Hello Patrick, I just checked some of my sites that are using this same RSH implementation and verified that they are working in IE7. No problems with CSS either. Perhaps, it's some syntax error in your js declaration? Or may be you can try moving the css and js file declarations a bit (css before js, vice versa) to see if it helps find the bug?

    ReplyDelete
  6. Hi,

    @Manoj Bhatty, can you give me a link of such a page you made?

    ReplyDelete
  7. I also have a question,
    I want to make a website for a band, which include a musicplayer playing continuely. Therefore it shouldn't be loaded again when the visitor click on a link (e.g. on bio). So i decided to use ajax. But in ajax there is no backfunktion, and also the url doesn't change. So I came across this script, but I don't know how to build on the page. So can you show me that on a simple example on a website?

    Best Regards

    ReplyDelete
  8. Kajenthiran: I think RSH or some similar ajax history mechanism is the best solution. But I'm wondering that your requirement is apparently very simple - how about putting the music player component in an iframes for this?

    ReplyDelete
  9. yeah but iframes are not supported by search engines, and to be honest, sometimes iframe just kill the whole design, when the users has different settings on his pc or mac. do you may know another solution?

    ReplyDelete
  10. http://thomasabend.comuf.com/ajax_test/index_ajax.html

    this is what i done for a test. so if someone click on about, i want that the url shows: http://thomasabend.comuf.com/ajax_test/index_ajax.html#about or even http://thomasabend.comuf.com/ajax_test/about.html

    ReplyDelete
  11. Alright, I looked at your page and below are some suggestions -

    1. If only your media player is in an iframe, then it won't hurt the site from an SEO standpoint because the media player part of it will be ignored by crawlers anyway (there seems nothing to index there).

    2. Since your overall layout is reasonably simple, have you considered using the ASP.NET Ajax's history management? I've used it in many implementations and it works very well as long as your content is not way too huge/complex.

    3. If ASP.NET Ajax's history is not an option for you then have you looked at YUI's history? I haven't used it but I plan to analyze the implementation for some of my next projects because I'd think it'll be supported by Yahoo as well.

    Finally, I'd say try RSH (really simple history) for a simple page with just 2-3 links (ajax or no ajax). I had some trouble implementing it the very first time myself but using a few simple examples I was able to get it right. When you do use RSH, do you get any errors? Any specific problems you encounter with it?

    If RSH is definitely not an option, then ASP.NET's Ajax would be the simplest and easiest to implement option for you.

    ReplyDelete
  12. thanks for the answer! Yh have yiu got any simple examples of RSH? Because as I read this tutorial, I dont clear understand, how to put it on a page.

    ReplyDelete
  13. Hello Kajenthiran,

    I recently successfully used YUI Browser History Manager as a replacement for RSH. The implementation was surprisingly easy and I'd recommend it for your project.

    Manoj

    ReplyDelete
  14. Hi,
    I find your solution very 'pretty' compared to whatever else one can find on the Net, so I would like to use it. Despite my best efforts, I was not able to make this happen :(. Maybe you can help me out ?

    Here is my simple test case code (content of page0.aspx):

    [%@ Page Language="VB" %]
    [html xmlns="http://www.w3.org/1999/xhtml"]
    [head runat="server"]
    [title][/title]
    [script src="scripts/json2007.js" type="text/javascript"][/script]
    [script src="scripts/jquery-1.4.2.min.js" type="text/javascript"][/script]
    [script src="scripts/rsh.js" type="text/javascript"][/script]
    [script type="text/javascript" language="javascript"]
    window.onload = function() {
    dhtmlHistory.initialize();
    dhtmlHistory.addListener(historyChange);
    dhtmlHistory.add('page0.aspx', 'link');
    };
    function historyChange(newLocation, historyData) {
    $.load(newLocation + ' #content ] *');
    }
    [/script]
    [/head]
    [body]
    [form id="form1" runat="server"]
    [div]
    [a href="page1.aspx" onclick="$.load('page1.aspx #content > *');dhtmlHistory.add('page1.aspx','link');"]page1[/a]
    [a href="page2.aspx" onclick="$.load('page2.aspx #content > *');dhtmlHistory.add('page2.aspx','link');"]page2[/a]
    [div id='content'][/div]
    [/div]
    [/form]
    [/body]
    [/html]

    When I load the page, I get this error in rsh.js : Microsoft JScript runtime error: 'this.storageField.value' is null or not an object, from function loadHashTable.

    Where did I go wrong ?
    Thanks in advance for any help.

    Jhfelectric (sorry if the code doesn't look good)

    ReplyDelete
  15. Hello Jhfelectric,

    Your code looks good but I might need to implement it to figure out the issue. That being said, since I wrote this article I've noticed more issues with RSH (ie6 at times, implementation issues and what not) and I've been playing around with other browser history frameworks and I think I've found a better solution - YUI. Its implementation is much more bug free and robust and I'd recommend it. I'll implement your example with YUI today in another post and will post the link here for you.

    Manoj

    ReplyDelete
  16. Hello there Jhfelectric, you can find a sample implementation for YUI here - http://web-win-programming-code-tutorial.blogspot.com/2010/03/yui-ajax-browser-history-back-button.html

    I quickly put together this code so there is much room for improvement but I tested to make sure that the history and back button parts work as expected. Also, you can get the javascript files from http://developer.yahoo.com/yui/

    ReplyDelete
  17. Thanks a lot Manoj, your YUI solution made it !
    Jhfelectric

    ReplyDelete
  18. jhfelectric - your code is missing the following:

    window.dhtmlHistory.create();

    put this just above your historyChange function

    ReplyDelete
  19. Hi, i'm trying to implement this on my website (see link below) but i haven't got it to work yet. Can you please show me an example that fits my website?

    /Jonas

    ReplyDelete
  20. It doesn't work at my site. because there is an error this.storagefield is Null.
    I need a Browser History for my Webshop. In the Webshop i change a div Element and not the complete Site. Witch Browser History Works?

    ReplyDelete
  21. Is there any working example? im very confused how to implement this >.<

    ReplyDelete