Showing posts with label Ajax History. Show all posts
Showing posts with label Ajax History. Show all posts

Wednesday, March 10, 2010

YUI Ajax Browser History Back Button Implementation

I've been looking for some better solutions to the whole Ajax browser back button issue and I think I've found the ideal candidate - YUI. For those who don't know much of this problem, I'll do a little recap. If you already understand the problem, feel free to jump right to the YUI implementation.

Detailed theoretical explanation of the problem and solutions - basically, browser's back (and forward) button is meant to take the user to either the last page the user was on or the last location in the same page the user is currently at. Ajax updates though, just "change" a part of the page - in other words, they don't take the user to a new "location". Consequently, since the location is still the same (only a part of the page changed, but addressbar still has the same location) browser doesn't register it as a "change" and the back button is never updated. So effectively, browser's back button doesn't work for the Ajax updates. This is a big problem since more and more websites are using Ajax updates for streamlined user interaction and without a functional back button, things like navigation menus and wizards become unusable.

There is a nifty solution to this problem though - Since the browsers rely on the changes in location to make the back button functional, we can change the URL fragments and fool the browser into thinking that the page has changed. URL fragments are stuff that comes after "#" in the location and generally they represent internal links. So, by changing the URL fragments with each Ajax update, we can make the back button functional. But since the browsers see the changes in the URL fragments as internal links and not as Ajax updates, they still don't know exactly what changed! As far as the browsers are concerned, changes in URL fragments mean that the user just moved to a different part of the page but the contents of the page are exactly the same. Since they don't track what changed, they can't go back to what was before - meaning, clicking the back button doesn't take the user to the last state, it doesn't do anything. This means that we need to keep track of whenever the user clicks back and forward buttons. We can actually put the state information associated with the Ajax calls in the URL fragments and use it to update the page as back/forward buttons are clicked.

But there is another problem - browsers don't expose any events for back/forward button clicks! So we can't tell when the user is going back or going forward and all that we can do is to poll and assume that the user clicked back/forward based on the current URL fragment. Further, we can use an iframe to store any data because an iframe won't be affected by navigation. This is basically the approach that is used by the popular browser history management solutions. Below is my experience with some of them -

1) ASP.NET Ajax History - This is very easy to use and just requires setting the EnableHistory attribute of ScriptManager and implementing the Navigate event. The problem with this though is that ASP.NET posts the whole page back anyway (whole page is posted and received) - so if the viewstate is even moderately big, it can be very slow. This pretty much defeats part of the purpose of doing Ajax updates where we want to post only the essential data and receive only the updated parts of the page, to make it as quick as we can. This is also the problem with ASP.NET's whole Ajax management system.

2) Really Simple History (RSH) - I've used with ASP.NET Ajax history and as a sole history management solution....and it works.....more or less. The problem is that its somehow hard to get right at the very first attempt. As you'd see in the comments here, pretty much every one had problems trying to set it up. But that's not really a 'problem', per say. The problem is that it hasn't worked for me in some versions of Internet Explorer (IE) and as we all know, IE is the most popular browser out there. It could just be my implementation but the way I look at it - it's just a means to an end, if something else exists that can solve the same problem with less effort then just use that other thing.....

Which brings us to....

3) YUI Browser History Manager - I very recently tried to implement it and I was suprised that it "just worked" in the very first try and without any problems....or may be I've just got smarter...whatever the case may be, I've successfully used it for many of my projects now and would recommend it. Below is some information on how to implement it -

Implementing YUI Browser History Manager:

Step 1: Download the following javascript files and include them:
<script type="text/javascript" src="/jquery.min.js"></script> (jquery needed for YUI)
<script type="text/javascript" src="/yahoo-dom-event.js"></script>
<script type="text/javascript" src="/connection.js"></script>
<script type="text/javascript" src="/history.js"></script> (from YUI - http://developer.yahoo.com/yui/history/)


Step 2: Add the below code right after the body tag
<iframe id="yui-history-iframe" src="assets/blank.html" width="1px" height="1px">
</iframe>
<input id="yui-history-field" type="hidden" />

(Note: iframe is needed only for IE)

Step 3: Add the below code right before the end of the body tag (or wherever you put your javascript)
<script type="text/javascript">
var bookmarked = YAHOO.util.History.getBookmarkedState("state");
var init = bookmarked || query || "page1.aspx";
YAHOO.util.History.register("state", init, OnStateChanged);
try {
YAHOO.util.History.initialize("yui-history-field", "yui-history-iframe");
} catch (e) {
}
YAHOO.util.History.onReady(function () {});
</script>


Now, if all you care about are back/forward buttons for one set of Ajax calls, then you don't need to change anything in the above code. But if you also want page refreshes and bookmarking support, then some more code will go into the onReady method above (I'll be writing another post for bookmarking support).

Step 4: Add the below code to all your Ajax calls.

YAHOO.util.History.navigate("state", param);

Note the "param" argument above - this is where you store the state information that you'll be using when back/forward buttons are clicked. Below is a sample code:

//Ajax call that needs browser's back button to work
function AjaxShowAppleImage()
{
// Ajax code that shows an apple's image
$('#img1').attr('src', 'apple.jpg');
//YUI code
YAHOO.util.History.navigate("state", 'apple');
}
//Another ajax call that needs browser's back button to work
function AjaxShowOrangeImage()
{
// Ajax code that shows an apple's image
$('#img1').attr('src', 'orange.jpg');
//YUI code
YAHOO.util.History.navigate("state", 'orange');
}

Step 5: Implement a OnStateChanged function. This OnStateChanged is called whenever the back/forward buttons are clicked and you use the "state" parameter (from above) to update the page (in our example - show the correct fruit image). Below is a sample explanation -

function OnStateChanged(state)
{
if(state == 'apple')
$('#img1').attr('src', 'apple.jpg');
else if(state == 'orange')
$('#img1').attr('src', 'orange.jpg');
}

And that's all you need to do. Pretty simple, isn't it?

This ends this post. For those who would like to grab some working code as a starting point, you can take the below code. It has been tested on most popular browsers and works perfectly. It basically contains a couple of links that load the content from two pages (page1.aspx and page2.aspx). It assumes that both these pages have a div with the id as "content" and loads their content in a div with an id "loadedContent". Have fun ajaxing! :)

(UPDATE 06-07-2010: Fixed a minor bug related to missing comma in the YAHOO.util.History.register method after "init" in the below code - YAHOO.util.History.register("state", init, function(state)... - Thanks to Mike in the comments for pointing it out)


<iframe id="yui-history-iframe" src="assets/blank.html" width="1px" height="1px">
</iframe>
<input id="yui-history-field" type="hidden" />
<script type="text/javascript" src="/jquery.min.js"></script>
<script type="text/javascript" src="/yahoo-dom-event.js"></script>
<script type="text/javascript" src="/connection.js"></script>
<script type="text/javascript" src="/history.js"></script>
<form id="form1" runat="server">
<div>
<a href="page1.aspx" onclick="$('#loadedContent').load('page1.aspx #content');YAHOO.util.History.navigate('state', 'page1.aspx');return false;">
page1</a> <a href="page2.aspx" onclick="$('#loadedContent').load('page2.aspx #content');YAHOO.util.History.navigate('state','page2.aspx');return false;">
page2</a>
</div>
<div id="loadedContent">
</div>
</form>

<script type="text/javascript">
var bookmarked = YAHOO.util.History.getBookmarkedState("state");
var query = YAHOO.util.History.getQueryStringParameter("divs");
var init = bookmarked || query || "page1.aspx";
YAHOO.util.History.register("state", init, function(state) {$('#loadedContent').load(state + ' #content');});
try {
YAHOO.util.History.initialize("yui-history-field", "yui-history-iframe");
} catch (e) {
}
YAHOO.util.History.onReady(function () {});
</script>

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!

Monday, December 15, 2008

Browser History Back Button - History/Remote Plugin

This particular solution applies to the cases when we want to stop any visible page refreshes as the user browses through the website. What we are trying to do basically is to load a new page as the user clicks on a link (navigates via a link) without refreshing the browser - That is, loading the part of the new page in our current page via Ajax by partially rendering the new content in the current page. But because of partial rendering of the page (the fact that the page is never refreshed), the back button gets lost. This plugin solves the back button browser history problem for this scenario using URL fragments.

Step 1: Download the latest jquery library from http://jquery.com/and include it in your project as follows -

<script type="text/javascript" src="<%= ResolveUrl("~/js/jquery-1.2.6.min.js") %>"></script>
(Please remember that this is just an example and the method/filenames etc will change depending on the names used in your web-development language / platform and jquery version etc.

Step 2: Download history/remote plugin js files from http://stilbuero.de/jquery/history/ (this is the website of Mr. Klaus Hartl, the gentleman who came up with this solution) and include it in your project as follows -

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

Step 3: Add a class named "seolink" to all your links (anchor tags) that needs history management. Please note that this class doesn't exist in your css file and doesn't have any display attributes, it's solely for the purpose of history management.

Step 4: Add the below code (jquery script) in the head tag of the webpage (or the master template if you are using asp.net masterpages) -

$(document).ready(function() {
  createSeoLinks($("#navigation a").add("a.seolink"));
  $.ajaxHistory.initialize(function (){$("#main-content").load(location.href " #main-content > *")});
  });

function createSeoLinks(links) {
  links.unbind('click').remote('#main-content', function() {
 window.createSeoLinks($("#main-content a.seolink"));
  });
}

Step 5 (optional): Ensure that all the links (anchor tags) have title attributes properly set

Step 6 (optional): Add the below code to the $(document).ready(function() section -

document.title = convertLinkToTitle(window.location);

If you don't know what Step 6 means, the final code in Step 4 will look like this -

$(document).ready(function() {
  createSeoLinks($("#navigation a").add("a.seolink"));
  $.ajaxHistory.initialize(function (){$("#main-content").load(location.href " #main-content > *")});
  });

function createSeoLinks(links) {
  links.unbind('click').remote('#main-content', function() {
  window.createSeoLinks($("#main-content a.seolink"));
  document.title = convertLinkToTitle(window.location);
  });
}

And you are done! Yes, it was that easy!

Now, let explain a few things in the above code -

1) main-content : Refers to the div that contains the content that you want to load whenever a link is clicked. Remember that only the contents of main-content div will change as the user navigates through the website, so you will need to organize your content so that the static elements are kept out of main-content div. It doesn't matter a hell lot though but this organization is a good idea in general.

2) What is in addressbar - Remember that, since the page i snever refreshed, the only thing in addressbar that will change is the url fragment. This is picked from the title of anchor tags and that's why Step 5 is needed. It's not an essential component of the history management process though and if you leave it out then it will be "#remote" followed by some number - (like #remote25 for 25th anchor tag etc)

3) convertLinkToTitle - Again since the page is never refreshed, the page title never really changes. So, I figured that I should update the page title programmatically using the information in the browser's addressbar. So this is a javascript / jquery function that will take the link (in the browser's addressbar) and figure out what the title should be, you will be writing the code yourself depending on what you want in url fragment (or the url itself) and how to translate it into page titles. The way, I wrote it is below -

function convertLinkToTitle(linkStr)
{
  var title = new String();
  title = String(linkStr);
  title = title.substr(title.indexOf('#', 0) 1);
  title = title.replace(/_/g, ' ');
  return title;
}

So this is how you implement the History/Remote Plugin.....feel free to suggest improvements, ask questions or comment on the code....And click - to read about another (and in my opinion) better Ajax Browser History Back Button solution called RSH (Really Simple History)