Ajax and javascript callbacks

Posted:

Here’s another boring programming note to myself. Please note that I had to break lines of code for formatting reasons. Please use this syntax as a guideline. Don’t try to cut and paste it.

Ajax is all the rage now. To me, it’s just web services done with Javascript and Javascript blows bigger chucks than Java (and we all know how I feel about that).

In playing with the simple code on Mozilla’s site, I realized that the browser complicates using Ajax as a simple RPC mechanism. The reason?
Browsers are inherently multithreaded applications and so all the RPC Ajax calls must be asynchronous. That means that you can’t directly port something like XML-RPC or SOAP (both of which are more or less synchronous) to Ajax. Browsers run javascript on events. When the Ajax response finally comes back to the browser, a callback function is needed to continue processing the response.

That’s not so bad, but what if you really want to do this:

<script>
var uid = ↵
  RPCRequest('server.php?action=getUID?username=jjohn');
</script>

The problem is that RPCRequest returns as soon as the request is made, not when the response comes back.

Here’s the typical Ajax code (via the Mozilla article) that makes the request and parses the response:

// file: ajax.js
// make the request, register the callback
function makeRequest(url, cb) {
  var http_request = false;
  if (window.XMLHttpRequest) { // Mozilla, Safari, ...
    http_request = new XMLHttpRequest();
    if (http_request.overrideMimeType) {
        http_request.overrideMimeType('text/xml');
    }
  } else if (window.ActiveXObject) { // IE
      try {
        http_request = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e) {

      }
  }

  if (!http_request) {
    alert("No HTTP object!");
    return false;
  }

  http_request.onreadystatechange = function() { 
      alertContents(http_request, cb); };
  http_request.open("GET", url, true);
  http_request.send(null);
  return http_request;
}

// the callback to handle the response from the server
function alertContents(http_request, cb) {
  try {
    if (http_request.readyState == 4) {
      if (http_request.status == 200) {
        // parse out response
        var resp = http_request.responseXML;
        var val = ↵
           resp.getElementsByTagName("response"). ↵
             item(0).firstChild.data;
        cb(val);
      } else {
        alert('There was a problem with the request.');
      }
    }
  } catch (e) {
      alert("Exception: " + e.message);
  }
}

In this code, I expect a rather meager XML response that has only a response tag.

I got the notion that what I should do with the response handler is send it a callback from the caller to handle the parsed response. Something like the following:

<!-- file: test.html -->
<html>
<head>
<script type="text/javascript" 
   language="JavaScript" src="ajax.js">
</head>
<body>

<span
    style="cursor: pointer; text-decoration: underline"
    onclick="makeRequest('server.php', ↵
        function(x){ alert(x) })">Make a request</span>
</body>
</html>

Again, this works fine when the anonymous function has a globablly defined function like alert(). But what if you want to define a function in the caller’s HTML page and have it called from code in the ajax.js page? I haven’t figured that out yet (but see below).

The problem is that the scope of functions defined on the calling page is not visible (it seems) to the functions in the ajax.js page. I think I could get around this using fully-qualified DOM names ore something nutty like that.

I want to use objects, but of course, Javascript has classless objects which don’t help here.
I guess I could define the callback behavior for each call, but that seems dumb.

I need to sleep on this a bit.


UPDATE: What a difference eight hours of sleep makes!

After digging around the javascript books I have, I came up with this more object-oriented version of the ajax code that removes the callback function in favor of a user-overridden method. That nicely divides that generic RPC tasks from the caller-specific ones. The code appears to run in both Firefox and Internet Explorer on my XP box, but I’m sure it will break in Opera and Safari.

Let’s look at the new ajax.js file. This defines an “class” call RPCClient.

/* Much of this is from
 * http://developer.mozilla.org/en/docs/AJAX:Getting_Started
 */
function makeRPCRequest(url) {

  if (window.XMLHttpRequest) { // Mozilla, Safari, ...
    this.http_request = new XMLHttpRequest();
    if (this.http_request.overrideMimeType) {
        this.http_request.overrideMimeType('text/xml');
    }
  } else if (window.ActiveXObject) { // IE
      try {
        this.http_request = ↵
          new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e) {
        this.errstr = e.message;
      }
  }

  if (!this.http_request) {
    alert("No HTTP object!");
    return false;
  }

  // Can't use 'this' in callback.  
  // Gets confuses with http_request.
  var thisObj = this;

  this.http_request.onreadystatechange = function() {
        thisObj.watchResponse();
  };

  this.http_request.open("GET", url, true);
  this.http_request.send(null);
}

function parseRPCResponse() {
  try {
    if (this.http_request.readyState == 4) {
      if (this.http_request.status == 200) {
         // parse out response
         var resp = this.http_request.responseXML;
         var val = ↵
                   resp.getElementsByTagName("response") ↵
                        .item(0).firstChild.data;
         this.handler(val);
      } else {
          alert('There was a problem with the request.');
      }
    }
  } catch (e) {
      alert("Exception: " + e.message);
  }
}

// define the RPCClient object
function RPCClient () {
   this.errstr         = false;
   this.http_request   = false;
}

// navigator 3 bug workaround for prototype
new RPCClient();
RPCClient.prototype.send = makeRPCRequest;
RPCClient.prototype.watchResponse = parseRPCResponse;
RPCClient.prototype.handler = function (x) ↵
     { alert("Default handler.n" + x) };

The idea of this code is that the user overrides the handler method to do something useful with the value that comes back from server.

Most of this code just replaces procedural elements with OO mechanisms.
However, notice this stupid looking assignment:

  var thisObj = this;

I had to do this because of scoping behavior that, as a Perl coder, I find nutty and wrong. The trouble comes when I want to use the current object in the http_request callback/method onreadystatechange. Like most method declarations, I use an anonymous function to override the default action. When I try to use the this in the callback/method, it then refers to http_request object, not the RPCClient object.

Ugh.

I guess JavaScript has to work this way, since it doesn’t have proper namespaces or Perl closure rules. How can I tell JavaScript which object this refers to? The answer is not to use this, but a reference to the current RPCClient object.

This is a pretty subtle point and it’s easy to get forget. Of course, if you only use JavaScript, it probably makes perfect sense to you.

Time for the calling HTML page.

<!-- file: test.html -->
<html><head>
<script type="text/javascript" language="JavaScript1.2" 
src="ajax.js"></script>
<style type="text/css">
.fakeURL { text-decoration: underline; cursor: pointer;}
</style>
<script type="text/javascript" language="JavaScript1.2">
var rpc = new RPCClient();
rpc.handler = function(x) { alert("From the caller: " + x)};

function dump (obj) {
  document.write("<div><p><b>Object properties</b></p><dl>n");
  for (var p in rpc) {
    document.write("<dt>property: <code>" + p 
                   + "</code></dt>" + "<dd>type: <code>" 
                   + typeof(rpc[p]) + "</code></dd>n");
  }
  document.write("</dl></div>n");
}
</script>

</head>
<body>
<script type="text/javascript" language="JavaScript1.2">
//dump(rpc);
</script>
<span class="fakeURL" 
   onClick="rpc.send('ajax.php')">Make a request</span>
</body>
</html>

There’s a bit of debugging code in there that dumps the properties of the RPCClient object. I left that code here for future debugging needs. A new RPCClient object is instantiated as you’d expect. The default handler is overridden with a trival method.

Now the RPC mechanism looks a little cleaner! It’s still asynchronous, but all the caller side code can be stored with the caller.

For the record, here’s the PHP server, which is just echoes the values passed to it:

<?php
  header("Content-Type: application/xml");
  header("Cache-Control: no-cache");
  print "<?xml version='1.0'?>";
?>

<response>
<? foreach($_GET as $k => $v) {
     print "$k => $vn";
   }
?>
</response>

This PHP is pretty trivial, but there are a few gotchas.

You have to set the MIME type correctly and disable browser caching.

You also have to emit XML, which is no big thing, except that the XML declaration looks like PHP code to PHP, so I had to be a little crafty in emitting it.

Finally, I just echo the key-value pairs I receive. Simple!

The next iteration of the code will pass the response back as a base64 encoded string. I don’t need to pass complex data to the browser, but I may want to pass HTML. From my years of XML-RPC hacking, the best way to do this is by encoding the HTML. Otherwise, you’ll go mad.

Have I mentioned that XML sucks too? It does.

Happy hackin’.