Hello children.

Today I'm going to attempt to teach you a (hopefully) simple way to implement Ajax, that crazy buzzword-happy compilation of technologies that's all the rage on the net.

Ajax is an acronym, standing for "Asynchronous JavaScript And XML"... sweet. While it's been around for a while, it's recently become popularized by popular sites like Google Maps and Google Gmail.

The basic goal behind this technology is to create a more friendly user experience by eliminating the "web-iness" of website applications. Ajax does this by removing the Refresh (also known as the "flicker") when requesting information. This is done by using the XMLHttpRequest object (an ActiveX object, now supported by all *major* browsers).

The thing is, I'm not an ActiveX programmer... I'm not even sure if I have the terminology down right... is ActiveX a language? Can you be an ActiveX programmer? These are questions I'm not here to answer.

I'm also not a great JavaScript programmer. I know just about enough to get Ajax working smoothly, but only with a little help... I'll get to that.

What I can do is PHP. It's a beautiful language - nearly everyone who's used it prefers it over alternatives such as Perl or ASP (note: I didn't say ASP.Net... that's a different beast all together). It's well supported, Open Source, and runs on multiple platforms. Before you all start thinking, "Who does he think he is? I like Perl just fine!", bear in mind: I said "nearly everyone"... and I said people "prefer" it, not that it's better - they all have their places.

So if you're like me, a bit weak on the JavaScript and ActiveX, but healthy on PHP, then I think we're on the right page. If you're on a different page, fake it and try not to get caught sleeping.

The tool I started with was Sajax, available at http://www.modernmethod.com/sajax/ . It got the job done fairly well... it's basically a helper utility which generates the code needed to get Ajax up and running with minimal effort.

The basic how-to is available on the site, but here's a quick overview. Let's pretend you have a script called FooBar.php. This script contains both PHP code and HTML:

PHP Code:
<?php
require_once("Sajax.php");
sajax_init();
sajax_handle_client_request();

function 
getText()
{
    return 
"Success!";
}

sajax_export("getText");
?&
gt;
&
lt;html>
&
lt;head>
&
lt;title>Hello Mother</title>
&
lt;script language="JavaScript" type="text/javascript">
&
lt;?php sajax_show_javascript(); ?>
&
lt;/script>
&
lt;script language="JavaScript" type="text/javascript">
function 
showText()
{
    
x_getText(showText_cb);
}
function 
showText_cb(result)
{
    
document.getElementById("debug").innerHTML result;
}
&
lt;/script>
&
lt;/head>
&
lt;body>
&
lt;a href="javascript:showText();">Show Text</a>
&
lt;div id="debug"></div>
&
lt;/body>
&
lt;/html&gt
This is relatively simple. When you click on that link, it calls "showText()", which in turn calls "x_getText([arg])", a wrapper class which is automatically generated from the Sajax script. This takes the whatever arguments your original function took, plus a callback function (the function that is called when you return from the PHP script). So, showText_cb() is called on return, and it's argument is the returned value from the PHP script.

Follow so far? Yay.

We then take that value, and stick it in the div tag. We're on a roll.

I played around with this for a while... but there some immediate problems. First of all, almost all of your logic is mixed in with your design. Bad. You can "fake" separation by putting all of your PHP functions in a separate file, and including it at the top of your main PHP file... still no good.

I thought, "Heck, why can't we make the Ajax stuff a class and then just extend off of it?" If we did this, we could just extend off that main class and add functionality inside some kind of... SiteClass object. I'll explain in a bit.

Here's how it would work:

We'd have our main Sajax Class (SajaxClass), which would provide all of the functionality of the normal Sajax library, and then we'd have a second class that would extend that (in practical terms, the Sajax class is worthless alone; you must extend it - it's abstract), and provide the overall functionality for the site.

This was a fun project to work on, only because I had nearly no idea what I was doing. I managed to turn the Sajax library into a SajaxClass by making the "globals" instance variables and by changing the functions into object methods. I then had to dump most of the "top-down" code into the constructor.

Where do we go from there? Well, here's the SajaxClass code (which I've thoughtfully named AjaxMonkey):
PHP Code:
<?





class 
AjaxMonkey

{

    var 
$sajax_debug_mode 1;
    var 
$sajax_export_list = array();
    var 
$sajax_request_type "GET";
    var 
$sajax_remote_uri "";

    var 
$sajax_js_has_been_shown 0;

    var 
$REQUEST_URI;

    

            

    function 
AjaxMonkey()

    {

        
$this->sajax_init();

        
$this->sajax_remote_uri $this->sajax_get_my_uri();

       
// $this->sajax_remote_uri = $this->sajax_get_my_uri();

        //$this->sajax_handle_client_request();

        

    
}

    

    function 
show_ajax_javascript()

    {

        
$this->sajax_show_javascript();

    

    }

    

    

       function 
sajax_init() {
    }
    
    function 
sajax_get_my_uri() {
        
        
        return 
$this->REQUEST_URI;
    }
    
    

    function 
sajax_handle_client_request() {
        
//global $sajax_export_list;
        
        
$mode "";
        
        if (! empty(
$_GET["rs"])) 
            
$mode "get";
        
        if (!empty(
$_POST["rs"]))
            
$mode "post";
            
        if (empty(
$mode)) 
            return;

        if (
$mode == "get") {
            
// Bust cache in the head
            
header ("Expires: Mon, 26 Jul 1997 05:00:00 GMT");    // Date in the past
            
header ("Last-Modified: " gmdate("D, d M Y H:i:s") . " GMT");
            
// always modified
            
header ("Cache-Control: no-cache, must-revalidate");  // HTTP/1.1
            
header ("Pragma: no-cache");                          // HTTP/1.0
            
$func_name $_GET["rs"];
            if (! empty(
$_GET["rsargs"])) 
                
$args $_GET["rsargs"];
            else
                
$args = array();
        }
        else {
            
$func_name $_POST["rs"];
            if (! empty(
$_POST["rsargs"])) 
                
$args $_POST["rsargs"];
            else
                
$args = array();
        }
        
//echo gettype($this->$sajax_export_list);
        
if (! in_array($func_name$this->sajax_export_list))
            echo 
"-:$func_name not callable";
        else {
            echo 
"+:";
            
$result call_user_func_array(array(&$this$func_name), $args);
            echo 
$result;
        }
        exit;
    }
    
    function 
sajax_get_common_js() {

        
        
$t strtoupper($this->sajax_request_type);
        if (
$t != "GET" && $t != "POST"
            return 
"// Invalid type: $t.. \n\n";
        
        
ob_start();
        ?&
gt;
        
        
// remote scripting library
        // (c) copyright 2005 modernmethod, inc
        
var sajax_debug_mode = <?php echo $this->sajax_debug_mode "true" "false"; ?>;
        var 
sajax_request_type "<?php echo $t; ?>";
        
        function 
sajax_debug(text) {
            if (
sajax_debug_mode)
                
alert("RSD: " text)
        }
         function 
sajax_init_object() {
             
sajax_debug("sajax_init_object() called..")
             
             var 
A;
            try {
                
A=new ActiveXObject("Msxml2.XMLHTTP");
            } catch (
e) {
                try {
                    
A=new ActiveXObject("Microsoft.XMLHTTP");
                } catch (
oc) {
                    
A=null;
                }
            }
            if(!
&& typeof XMLHttpRequest != "undefined")
                
= new XMLHttpRequest();
            if (!
A)
                
sajax_debug("Could not create connection object.");
            return 
A;
        }
        function 
sajax_do_call(func_nameargs) {
            var 
ixn;
            var 
uri;
            var 
post_data;
            
            
uri "<?php echo $this->sajax_remote_uri; ?>";
            if (
sajax_request_type == "GET") {
                if (
uri.indexOf("?") == -1
                    
uri uri "?rs=" escape(func_name);
                else
                    
uri uri "&rs=" escape(func_name);
                for (
0&ltargs.length-1i++) 
                    
uri uri "&rsargs[]=" escape(args[i]);
                
uri uri "&rsrnd=" + new Date().getTime();
                
post_data null;
            } else {
                
post_data "rs=" escape(func_name);
                for (
0&ltargs.length-1i++) 
                    
post_data post_data "&rsargs[]=" escape(args[i]);
            }
            
            
sajax_init_object();
            
x.open(sajax_request_typeuritrue);
            if (
sajax_request_type == "POST") {
                
x.setRequestHeader("Method""POST " uri " HTTP/1.1");
                
x.setRequestHeader("Content-Type""application/x-www-form-urlencoded");
            }
            
x.onreadystatechange = function() {
                if (
x.readyState != 4
                    return;
                
sajax_debug("received " x.responseText);
                
                var 
status;
                var 
data;
                
status x.responseText.charAt(0);
                
data x.responseText.substring(2);
                if (
status == "-"
                    
alert("Error: " data);
                else  
                    
args[args.length-1](data);
            }
            
x.send(post_data);
            
sajax_debug(func_name " uri = " uri "/post = " post_data);
            
sajax_debug(func_name " waiting..");
            
delete x;
        }
        
        &
lt;?php
        $html 
ob_get_contents();
        
ob_end_clean();
        return 
$html;
    }
    
    function 
sajax_show_common_js() {
        echo 
$this->sajax_get_common_js();
    }
    
    
// javascript escape a value
    
function sajax_esc($val)
    {
        return 
str_replace('"''\\\\"'$val);
    }

    function 
sajax_get_one_stub($func_name) {
        
ob_start();    
        ?&
gt;
        
        
// wrapper for <?php echo $func_name; ?>
        
        
function am_<?php echo $func_name; ?>() {
            
sajax_do_call("<?php echo $func_name; ?>",
                
am_<?php echo $func_name; ?>.arguments);
        }
        
        &
lt;?php
        $html 
ob_get_contents();
        
ob_end_clean();
        return 
$html;
    }
    
    function 
sajax_show_one_stub($func_name) {
        echo 
$this->sajax_get_one_stub($func_name);
    }
    

    

    
    function 
sajax_export() {
        
//global $sajax_export_list;
        
        
$n func_num_args();
        for (
$i 0$i &lt$n$i++) {
            
$this->sajax_export_list[] = func_get_arg($i);
        }
    }
    

    
//Identical function as sajax_export ... just trying to escape the sajax name

    //No offense, ModernMethod

    
function convert_to_ajax() {
        
//global $sajax_export_list;
        
        
$n func_num_args();
        for (
$i 0$i &lt$n$i++) {

            
$func func_get_arg($i);
            
array_push($this->sajax_export_list$func);
        }
    }

    
    
    function 
sajax_get_javascript()
    {
        
$this->sajax_js_has_been_shown;
        
//global $sajax_export_list;
        
        
$html "";
        if (! 
$this->sajax_js_has_been_shown) {
            
$html .= $this->sajax_get_common_js();
            
$this->sajax_js_has_been_shown 1;
        }
        foreach (
$this->sajax_export_list as $func) {
            
$html .= $this->sajax_get_one_stub($func);
        }
        return 
$html;
    }
    
    function 
sajax_show_javascript()
    {
        echo 
$this->sajax_get_javascript();
    }





}

?&
gt
As you can see, it's almost a direct copy of the Sajax library, just dumped into an object. Now for the extension (AjaxSiteClass - could be anything):
PHP Code:
<?

 require_once(
'AjaxMonkey.php');

 class 
AjaxSiteClass extends AjaxMonkey

 
{

    function 
AjaxSiteClass()

    {

        
parent::AjaxMonkey();

        
//$this->sajax_handle_client_request();

        

    
parent::convert_to_ajax("testing");

        

    
parent::sajax_handle_client_request();

        

    

    }

 

    function 
testing()

    {

        return 
"hello";

    }

 

 

 }

 ?&
gt
What we have here is an object that extends AjaxMonkey. It calls the parents constructor in its constructor, since I wasn't sure if it did that upon instantiation... some languages (like Ruby) don't, and I haven't looked it up.

We then have a "convert_to_ajax" method, which is almost identical to the "sajax_export" method found in its parent, except it uses the "array_push" to add more values to the function list. I figured I'd do this in case I have manymanymany functions and I wanted to split it up.

We then have a parent call to "sajax_handle_client_request", which takes care of calling the different methods on "postback" (via the XMLHttpRequest).

The only thing left to do now is make a page to test the system out. Here's mine:
PHP Code:
<?

require_once(
'AjaxSiteClass.php');

$sc = new AjaxSiteClass();

$sc->$sajax_debug_mode 1;

?&
gt;

&
lt;html>

&
lt;head>

&
lt;title>Testing</title>

&
lt;script language="JavaScript" type="text/javascript">

function 
callMethod()

{

    
am_testing(callBack);

}



function 
callBack(result)

{

    
document.getElementById("debug").innerHTML result;

}

&
lt;/script>



&
lt;script language="JavaScript" type="text/javascript">

&
lt;? 

$sc->show_ajax_javascript();

?&
gt;

&
lt;/script>

&
lt;body>

&
lt;a href="javascript:callMethod();">GO!</a><br>

&
lt;div id="debug"></div>

&
lt;/body>

&
lt;/html&gt
As you can see at the top, we merely include our AjaxSiteClass.php (which includes AjaxMonkey.php). I set debug mode equal to 1, so I could view the process (this functionality is built into the Sajax library).

In the "script" section, we have a bit more work to do. As you can see, the hyperlink in the body calls the function "callMethod". We have to make this. We also have to call our AjaxSiteClass object's "show_ajax_javascript" method, which spits out the generated code to handle the ActiveX stuff.

Inside this callMethod, we call "am_testing", which is the wrapper class generated by AjaxMonkey (you can change the prefix, as I did, in the AjaxMonkey file). This takes the callback method as it's only argument, since "testing" doesn't take any arguments (So if "testing" took a integer as an argument, you'd call "am_testing(34, callBack)" ).

Technically, you could have called "am_testing" from the hyperlink, but I find it best to keep the wrapper classes out of the body, and only call them from the script section, that way you can keep the method/callback methods together.

After am_testing is called, callBack method is called, with "result' as its only argument. This "result" is the return value of your PHP function. In this case it's a string, so I merely set the div tag's contents to that string.

YAY! We made it! It wasn't so bad, was it? It's not quite MVC, but it's pretty darn close.

You can simplify the process further by putting all of your "callMethod/callBack" type methods in a separate .js file, and include them in the script section.

I'd like to figure out a way (and I think I just have) to stick those methods in the AjaxSiteClass file, and then just have that "show_ajax_javascript" export that code as well. One could then have some kind of "limited export", where only the javascript that is important to this particular page would be exported, rather than every single function in that list.

That'll be my next update, I guess.

Cheers, all.

M