Thursday, May 11, 2006

AJAX: Back Button Fix with PHP and HTML Frames

UPDATED: 2008-10-02 Working Demo, and refined solution to the AJAX Back Button Fix is now available at zedwood.com.


So your back button is broken in your AJAX/PHP web app. I made a fix using PHP and HTML frames.

Flash developers have had to deal with this issue for a while, and so my fix is a variant. View Robert Penner's flash fix.

Lets say you are at a regular website with 5 pages total. After a user navigates from page1 to page2 he may want to click BACK to change the state of the website from page2 back to page1. Well in AJAX your website is no longer divided up into pages, but you must still use the idea of states that intuitively appears to the user as a different page.

So each AJAX page state is arrived at by executing a javascript function. When you click back, you'll want to execute a previous AJAX function which returns the site to the previous page state. This is accomplished with frames, but this means that if a browser is not enabled to use frames (lynx), the back button will not work. This is okay because no one would use lynx for your website anyway.

No what we have a frame overlay page, with two frames, one sized at 100%,100% and the other as invisible. When we hit a link that we want to store in the page history, we make change the page of the invisible frame to a new page, and pass it a parameter of the real page we wanted to go to. Then in our invisible frame we use that parameter to determine which ajax state to go to in the main page, which we arrive at by executing a javascript function. The effect is that when you hit the back button, it jumps to a previous page in the invisible frame and executes the javascript function associated with it, which changes the page state of the main ajax page.

Allowed Links. There are 3 kinds of links available to use on your site:
1. A HREF with _target='parent', used for external links to jump out of the framed ajax page
2. A HREF with onclick="pagenav('ajaxpagestate')" use this to navigate to a new ajax page state
3. Regular A HREF, do not use, because it will boot up the link within your frame overlay

Files needed for my PROOF-OF-CONCEPT(included below):
index.html- contains 2 frames index.php and redir_ph.php(invisible)
redir_ph.php- invisible frame used for storing page history
index.php- your main ajax app page
ajax.js- contains javascript code for ajax
ajaxfunc.php- ajax access this to returns famous quotes in xml


index.html
<html><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<
head>
<
title></title>

<
meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<
/head>

<
frameset rows="*,0" frameborder="NO" border="0" framespacing="0">
<
frame src="index.php?useframes=true" name="realframe" frameborder="NO">
<
frame src="redir_ph.php?p=main" name="historyframe" frameborder="NO">
<
/frameset>

<
noframes>
<
body bgcolor="#FFFFFF">
<
script language="JavaScript">
<!---------
window.location.href='index.php';//index.php defaults to useframes=false
//-------->
<
/script>
<
/body>
<
/noframes>

<
/html>


ajax.js
//--ajax
function createRequestObject() {
if(navigator.appName == "Microsoft Internet Explorer")
return new ActiveXObject("Microsoft.XMLHTTP");
return new XMLHttpRequest();
}

var httpreq = createRequestObject();
function ajaxreq(quote) {
httpreq.open('get', 'ajaxfunc.php?t='+quote);
httpreq.onreadystatechange = ajaxresponse;
httpreq.send(null);
}
function ajaxresponse()
{
if(httpreq.readyState == 4)
{
var xml=httpreq.responseXML;
var node=xml.getElementsByTagName("quote")[0];
var quot=node.firstChild.nodeValue;
document.getElementById('maindiv').innerHTML=quot;
}
}


redir_ph.php
<?php
if ( isset($_REQUEST['p']) )
$pagename= rtrim($_REQUEST['p']);
else
$pagename="";

$jscript="if (parent.realframe.pagenavdone) parent.realframe.pagenavdone('$pagename');";
?>
<
html><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<
head>
<
title><?php echo $pagename;?></title>
<
meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</
head>
<
body bgcolor="#000000" text="#FFFFFF">
<?
php echo $pagename;?>
<
script language="JavaScript">
<!---------
<?
php echo $jscript;?>
//-------->
</
script>
</
body>
</
html>


ajaxfunc.php
<?php
if ( isset($_REQUEST['t']) )
$t= rtrim($_REQUEST['t']);
else
$t="";

header('Content-type: text/xml');
echo "<quote>";
if ($t=="money")
echo "I spent 90% of my money on women and drink. The rest I wasted - George Best";
else if ($t=="fire")
echo "Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life. - Terry Pratchett.";
else if ($t=="love")
echo "Love is temporary insanity curable by marriage. - Ambrose Bierce";
else if ($t=="success")
echo "If at first you don't succeed... So much for skydiving. - Henry Youngman.";
else if ($t=="hate")
echo "I am free of all prejudices. I hate everyone equally. - WC Fields";
else
echo
"The first ninety minutes of a football match are the most important. - Bobby Robson";
echo "</quote>";
?>


index.php
<?php
if ( isset($_REQUEST['useframes']) )
$useframes= rtrim($_REQUEST['useframes']);
else
$useframes="false";
?>
<
html><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<
head>
<
meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
<
title>My Main AJAX Page</title>
<
script language="JavaScript" type="text/JavaScript" src="ajax.js"></script>
<
script language="JavaScript" type="text/JavaScript">
<!---------
//--back button
var useframes=<?php echo $useframes;?>;
function pagenav(str)
{
if (useframes)
top.frames["historyframe"].location.href = "redir_ph.php?p="+str;
else
pagenavdone(str);
}
function pagenavdone(pagename)
{
if (pagename=="money_quote_state")
ajaxreq("money");
else if (pagename=="fire_quote_state")
ajaxreq("fire");
else if (pagename=="love_quote_state")
ajaxreq("love");
else if (pagename=="success_quote_state")
ajaxreq("success");
else if (pagename=="hate_quote_state")
ajaxreq("hate");
}
//-------->
</
script>
</
head>
<
body style='font-family:Arial,Geneva,Sans-Serif;font-size:10pt'>

This is my main ajax page 3.
<br>
<
br><a href='http://www.google.com' target='_parent'>
Use this type of A HREF for links external to you AJAX app
</a>
<
br>
<
br>Ajax Page States:
<
br>
<
a href='javascript:void(0);' onclick="pagenav('money_quote_state');">money</a>
| <
a href='javascript:void(0);' onclick="pagenav('fire_quote_state');">fire</a>
| <
a href='javascript:void(0);' onclick="pagenav('love_quote_state');">love</a>
| <
a href='javascript:void(0);' onclick="pagenav('success_quote_state');">success</a>
| <
a href='javascript:void(0);' onclick="pagenav('hate_quote_state');">hate</a>

<
div id='maindiv' style='height:50px;border:1px #000000 solid'></div>

<
br><a href='http://www.google.com'>Do not use a regular A HREF link like this</a>
<
br>If you do , you it display your http://www.mysite.com/index.html in the location bar
but show the content of the site you were trying to reach.
</
body>
</
html>

12 comments:

Conn Buckley said...

I've been struggling with the back button issue for over a week now and I'm not any closer to a solution. The site I am building is still at it's early stages and I haven't yet added even support for non-IE browsers. I do not know anything about PHP and I can't tell if I can even implement the solutions discussed here since I already came up with my own bookamrking scheme and I'm using frames besides. If anyone has any suggestions, please let me know. I can post any code that people would want to see or even allow anonymous read access to the folder that contains all of my current source.

marker said...

"conn buckley" I can't look over your code, but I can email you the 5 files discussed here if I can only know your email address.

If i email the files, then you can be sure that there are no copy/paste errors.

marker said...

I included the 5 files you need as inline attachments in the original blog post. If you have webspace I can email you the files and you can host the demo. Blogspot doesn't offer any good webspace.

When you copy and paste it you might have to add spaces on html lines that wrap around. The code is highlighted so if you are familiar with code, you don't have to do any work except copying and pasting.

Anonymous said...

I'm trying to use your code with links to other pages on my site. Can that be done and if so, how?

Anonymous said...

Very nicely done, just had to give you some kudis for such a simple yet powerful idea. To me these are the best kind, have a good one.

Anonymous said...

it works very good for Back button but it has error when use refresh button

it's not return a page when click refresh button? pls fix it.
http://phpbasic.com

marker said...

This back button fix is not a refresh button fix. Like gmail, when you hit the refresh button it goes to the url in your address bar, and loses the state of the page.

I also assume that you have an understanding of php and html and ajax the only thing you lack is a hack of how to fix the back button.

riper said...

hi, i found a full ajax website, and the back button works : http://itbreaks.net

Pramod said...

I am fetch data on change event of a page but when i am back using exploler then page not reload and gave error page expired. & using refresh button data again show please help me how can i show the privious data by using back button.
Please send me code if you have it.

Anonymous said...

I am fetch data on change event of a page but when i am back using exploler then page not reload and gave error page expired. & using refresh button data again show please help me how can i show the privious data when use back button.
Please send me code if you have it.

marker said...

Don't forget to check out
zedwood.com's solution to the problem.

It includes a working demo in their ajax back button fix article.

lloyd said...

Life Saver. After having horrible problems with RSH (Really Simple History) and prototype, I was close to giving up. With some adaption of your code to ajax multiple divs at the same time, I am back on track.