<script> tagIt's here: bidynodes.js and formprocessor.php.
If you've tried doing any cross-domain Ajax programming, you'll have noticed by now that XMLHttpRequest doesn't work very well across domains. The three workarounds described in that article (and also here, in the case of the second) assume that we want something that will sit on our own server and mediate XMLHttpRequests to a remote server. But what if I'm interested in doing the opposite -- what if I want to hand off some code to someone else, with the understanding that it will live on their server and connect to mine? For instance, let's say that Clara Client wants to put a form on her website which will accept user input, pass it along to my server (where I can do whatever is needed with it), and then show a customized confirmation message to the user, all without a page reload on the user's part. I shouldn't have to care where Clara puts the form, and Clara shouldn't have to do any special proxy configuration on her end -- all she needs is one shrinkwrapped chunk of code which she can get from me. It sounds like a job for XMLHttpRequest, except for the pesky cross-domain part.
XMLHttpRequestEnter the third technique described in the article, the so-called "script tag hack", which doesn't actually use XMLHttpRequest at all (meaning that that's the last time I'll have to type it here...whew). Instead, it involves dynamically creating a <script> tag via the DOM, which points to a remote file which itself is dynamically generated. As noted in the Jason Levitt article linked above, Darryl Lyons was doing something like this a year ago, albeit with ColdFusion- and IE-specific code. Over at Mindsack, Kent Brewster is calling the technique Dynodes, short for dynamic node creation. Not only does his example work in more than one browser, but he's also using JSON to pass back the data (which happens to be X(HT)ML). JSON is nice for our purposes since it's so lightweight and easy to use.
In the Dynodes example, the data that comes back is dynamically generated each time, but all we're doing is getting a random number -- not, say, a random number in a range that the user gets to specify herself. In the comments here, Jeff suggests that two-way communication is possible if user input can be passed to the remote server via the src attribute of the <script> tag (e.g., src="the_user_just_typed_account_number_1234.js"). Passing data via the file name like that would be pretty ugly, but it would work, and it got me thinking. First of all, there's no need for the file name to end in .js -- we can have it be whatever we want (.php, for instance) and just fake a Content-Type header, as is done in the Levitt article. And so long as we're doing that, why not just throw in a query string too? When the user completes the form and triggers the node creation (in this case, by hovering over the Send button), the local script (source) generates a tag that looks something like <script type="text/javascript" src="http://shoeboxfulloftapes.org/sandbox/bidynodes/formprocessor.php?first_name=Lindsey&favorite_cheese=Mozzarella&">. (The extra & at the end of the string could easily be stripped out, but it's not hurting anything, so I just left it there.) Hooray! Now we have bidirectional communication -- bidynodes, if you will.
On the remote server, the formprocessor.php script (source) processes the query string and sends back a JSON object containing the data that doStuffWith() will need, which you can see by looking at some example output. I took a few ideas from the code in the Levitt article; the getStuff() function comes from Dynodes.
The language could be anything, but I threw in some PHP-specific things (like $_SERVER['SERVER_NAME']) just to demonstrate that the processing does, in fact, occur on the remote server. In a real-world application, you'd most likely want to skip that part and put a friendlier confirmation message here instead; you'd probably also want to do something useful, like save the request data to a database or the like.
Brewster notes in the Dynodes article that a "loading" indicator would be helpful "in case the remote script is on a slow server". The indicator would give visual feedback that something was happening, but by itself it's not enough; if the remote script hadn't been downloaded yet when the user clicked the element that triggered getStuff(), the browser would throw a "getStuff is not defined"
or similar JavaScript error. The Ajax Patterns article about "On-Demand Javascript" suggests running a check to see if the script is there yet:
In most cases, the test will actually fail the first time, because the download function will return before the script's been downloaded. But in this particular application, that doesn't actually matter much - the whole synchronisation sequence is run every five seconds anyway. If the upload function isn't there yet, it should be there in five seconds, and that's fine for our purposes. If desired, we could be more aggressive - for instance, the script could be downloaded as soon as an edit begins.
In some cases we might want to grab the remote script as soon as the user begins to complete the form. Here, though, it doesn't make sense to generate and download the script until the user is all finished and ready to submit the form, since the script needs all of the form data to do its thing anyway. I addressed the problem by wrapping getStuff() in a tryGetStuff() function. If getStuff() doesn't work, we just show the loading indicator, pause for a second, and then call the functions that would be called if the user were to click again. After trying lots of different techniques (setTimeout(), setInterval(), try...catch blocks, recursion) for dealing with a missing getStuff(), I settled on this as the one that seemed to make the most browsers happy, but I'm still not sure it's the best way to go about it. Suggestions are welcome!
It should go without saying that this technique is only appropriate if you trust the remote server to run scripts on your page. Actually, I only intended it for situations in which you are the trustworthy remote server and you want to give others access to a service you're providing...but you know what they always say about good intentions. Please see Douglas Crockford's "Words of Warning" in the Dynodes article for more about security.