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>

INSTALL: PHP5+APACHE2+WIN32

Installing php 5, apache 2 in a win32 environment is easy. Sometimes enabling extensions can be difficult, but we will be enabling php_curl.dll, php_mysql.dll, php_mcrypt.dll, and php_mssql.dll.

Download PHP5 zip package
(the .zip with all the extensions, not the .exe installer)
Download Apache2 from http://http.apache.org
(link to 2.2.2 /w no SSL)
Unzip PHP5 to C:\PHP
Run the apache2 installer

if you have a folder named C:\PHP\extensions rename it to C:\PHP\ext
rename C:\PHP\php.ini-recommmended to C:\PHP\php.ini
edit C:\PHP\php.ini,
change the extension_dir to "C:\php\ext" (include quotation marks)
remove the ; from line: ;extension=php_curl.dll
remove the ; from line: ;extension=php_mysql.dll
remove the ; from line: ;extension=php_mcrypt.dll
remove the ; from line: ;extension=php_mssql.dll

edit http.conf, add the following lines to the bottom (default location-
c:\program files\apache group\apache2\conf\http.conf)
LoadModule php5_module "c:/php/php5apache2.dll"
AddType application/x-http-php .php
# configure the path to php.ini
PHPIniDir "C:\php"

Now, right-click on "My Computer", go to "Properties", go to the "Advanced" tab, click on the "Environment Variables" button. Under "System" add ";C:\php;C:\php\ext" to your PATH variable.

create a file called
c:\program files\apache group\apache2\htdocs\phpinfo.php
containing the text
<?php phpinfo(); ?>


When you reboot next, your PATH settings will be available to all programs, your apache2 will load as a service, it will see the new version of the http.conf file with the modifications you made, and will see the new php.ini file with the modifications you made, and when you hit "http://localhost/phpinfo.php" you will see an html version of your php.ini file with all of the settings, and you will see sections for each of the php extensions you have enabled.

C++: How to get the Temp Directory

For the currently logged-in user, you can edit the TEMP and TMP folders. In Windows XP, if your right-click on My Computer, go to Properties, go to the Advanced tab, click on Environment Variables, and you can set TEMP and TMP from here.

But what if you are writing a program that needs to create a temporary file? windows.h provides us with the GetTempPath() function used below. The following program was taken from an MSDN coding example.

code to fetch it: [revised]
#include <windows.h>
#include <stdio.h>
#define BUFSIZE 4096

int main(int argc, char* argv[])
{
DWORD dwRetVal;
DWORD dwBufSize=BUFSIZE; // length of the buffer
char lpPathBuffer[BUFSIZE]; // buffer for path

// Get the temp path.
dwRetVal = GetTempPath(dwBufSize, lpPathBuffer);

if (dwRetVal > dwBufSize)
{
printf ("GetTempPath failed with error %d.\n",
GetLastError());
return (2);
}
printf("GetTempPath returned: %s", lpPathBuffer);
return (0);
}
Originally I had included code for Borland C++ Builder code which went and did a manual fetch from the registry, but a reader posted about GetTempPath. I'm more familiar with borland C++ than I am with ms visual studio and windows API commands, so thanks to kicheck for that.

kichik said...
GetTempPath works "on Windows 9x as well and requires a lot less code. It'll also fallback from TMP to TEMP to USERPROFILE to WINDIR in case any of those don't exist."

Thursday, May 04, 2006

PHP: No input file specified error

Sometimes people get this error when installing php as a CGI Binary, when setting up PHP5 IIS6.0 on Windows Server 2003.

Solution:
Edit php.ini, comment out doc_root, there is a problem with virtual servers.

Helpful Links:
http://www.visualwin.com/PHP/
http://www.peterguy.com/php/install_IIS6.html
http://wordpress.org/support/topic/4243