Implementing History Support using jQuery for AJAX websites built on asp.net AJAX
- by anil.kasalanati
Problem Statement:
Most modern day website use AJAX for page navigation and
gone are the days of complete HTTP redirection so it is imperative that we
support back and forward buttons on the browser so that end users navigation is
not broken. In this article we discuss about solutions which are already
available and problems with them.
Microsoft History
Support:
Post .Net 3.5 sp1 Microsoft’s Script manager supports history
for websites using Update panels. This is achieved by enabling the ENABLE
HISTORY property for the script manager and then the event “Page_Browser_Navigate”
needs to be handled. So whenever the browser buttons are clicked the event is
fired and the application can write code to do the navigation.
The following articles provide good tutorials on how to do
that
http://www.asp.net/aspnet-in-net-35-sp1/videos/introduction-to-aspnet-ajax-history
http://www.codeproject.com/KB/aspnet/ajaxhistorymanagement.aspx
And Microsoft api internally creates an IFrame and changes
the bookmark of the url. Unfortunately this has a bug and it does not work in
Ie6 and 7 which are the major browsers but it works in ie8 and Firefox. And
Microsoft has apparently fixed this bug in .Net 4.0. Following is the blog
http://weblogs.asp.net/joshclose/archive/2008/11/11/asp-net-ajax-addhistorypoint-bug.aspx
For solutions which are still running on .net 3.5 sp1 there
is no solution which Microsoft offers so there is are two way to solve this
o
Disable the back button.
o
Develop custom solution.
Disable back button
Even though this might look like a very simple thing to do
there are issues around doing this
because there is no event which can be manipulated from the javascript.
The browser does not provide an api to do this. So most of the technical
solution on internet offer work arounds like doing a history.forward(1) so that
even if the user clicks a back button the destination page redirects the user
to the original page. This is not a good customer experience and does not work
for asp.net website where there are different views in the same page.
There are other ways around detecting the window unload
events and writing code there. So there are 2 events onbeforeUnload and
onUnload and we can write code to show a confirmation message to the user. If
we write code in onUnLoad then we can only show a message but it is too late to
stop the navigation. And if we write on onBeforeUnLoad we can stop the
navigation if the user clicks cancel but this event would be triggered for all
AJAX calls and hyperlinks where the href is anything other than #. We can do
this but the website has to be checked properly to ensure there are no links
where href is not # otherwise the user would see a popup message saying “you
are leaving the website”.
Believe me after
doing a lot of research on the back button disable I found it easier to support
it rather than disabling the button. So I am going to discuss a solution which
work using jQuery with some tweaking.
Custom Solution
JQuery already
provides an api to manage the history of a AJAX website -
http://plugins.jquery.com/project/history
We need to integrate this with Microsoft Page request
manager so that both of them work in tandem.
The page state is maintained in the cookie so that it can be
passed to the server and I used jQuery cookie plug in for that –
http://plugins.jquery.com/node/1386/release
Firstly when the page loads there is a need to hook up all
the events on the page which needs to cause browser history and following is
the code to that.
jQuery(document).ready(function()
{
// Initialize history plugin.
// The callback is called at once by present location.hash.
jQuery.history.init(pageload);
// set onlick event for buttons
jQuery("a[@rel='history']").click(function() {
//
var hash = this.page;
hash =
hash.replace(/^.*#/, '');
isAsyncPostBack = true;
// moves to a new page.
// pageload is called at once.
jQuery.history.load(hash);
return true;
});
});
The above scripts basically gets all the DOM objects which
have the attribute rel=”history” and add the event. In our test page we have
the link button which has the attribute rel set to history.
<asp:LinkButton ID="Previous" rel="history" runat="server" onclick="PreviousOnClick">Previous</asp:LinkButton>
<asp:LinkButton ID="AsyncPostBack" rel="history" runat="server" onclick="NextOnClick">Next</asp:LinkButton>
<asp:LinkButton ID="HistoryLinkButton" runat="server"
style="display:none" onclick="HistoryOnClick"></asp:LinkButton>
And you can see that there is an hidden HistoryLinkButton
which used to send a sever side postback in case of browser back or previous
buttons. And note that we need to use display:none and not visible= false
because asp.net AJAX would disallow any post backs if visible=false.
And in general the pageload event get executed on the client
side when a back or forward is pressed and the function is shown below
function pageload(hash) {
if
(hash) {
if
(!isAsyncPostBack) {
jQuery.cookie("page", hash);
__doPostBack("HistoryLinkButton", "");
}
isAsyncPostBack = false;
} else
{
//
start page
jQuery("#load").empty();
}
}
As you can see in case there is an hash in the url we are
basically do an asp.net AJAX post back using the following statement
__doPostBack("HistoryLinkButton", "");
So whenever the user clicks back or forward the post back
happens using the event statement we provide and Previous event code is invoked
in the code behind. We need to have the
code to use the pageId present in the url to change the page content.
And there is an important thing to note – because the hash
is worked out using the pageId’s there is a need to recalculate the hash after
every AJAX post back so following code is plugged in
function ReWorkHash() {
jQuery("a[@rel='history']").unbind("click");
jQuery("a[@rel='history']").click(function() {
//
var
hash = jQuery(this).attr("page");
hash = hash.replace(/^.*#/, '');
jQuery.cookie("page", hash);
isAsyncPostBack = true;
//
moves to a new page.
//
pageload is called at once.
jQuery.history.load(hash);
return
true;
});
}
This code is executed from the code behind using
ScriptManager RegisterClientScriptBlock as shown below –
ScriptManager.RegisterClientScriptBlock(this, typeof(_Default), "Recalculater",
"ReWorkHash();", true);
A sample application is available to be downloaded at the
following location –
http://techconsulting.vpscustomer.com/Source/HistoryTest.zip
And a working sample is available at –
http://techconsulting.vpscustomer.com/Samples/Default.aspx