[This article originally appeared in August of 2000 when, I assure you, it made more sense. I add it here for posterity as it was, by far, the most popular piece of content I ever developed (as sad as that may be).]
Ever want to send data to the server and return data to the browser without refreshing the page? This essay describes a simple technique that allows you to do that by using an HTML img tag as a conduit. The technique takes advantage of JavaScript’s ability to modify the source of a img tag on the fly and ColdFusion’s ability, via cfcontent, to mimic a graphic.
With this technique you can create adaptive forms, gain detailed control over session time out, better deal with user error, and other things we haven’t thought of.
How it Works
The whole thing begins with the fact that JavaScript can manipulate the src attribute of any img tag on the page. Using JavaScript we can create, populate, and append URL variables to the modified src attribute allowing us to send arbitrary data to the server. This method is often used by advertising companies to capture certain data as part of the server request logs. However, to use the data immediately you can’t call a graphic image but instead must call a program template, such as a ColdFusion file. That template must be able to manipulate the URL variables and finish by presenting a proper graphic to the browser. Communication back to the browser is achieved through the use of cookies set by the template and added to the graphic response header.
ColdFusion, and many other application servers, offer all the tools required to pull this off. Simplified JavaScript calls a template (via an img tag) and appends data to it. The server manipulates the data and can populate a cookie with any required response. Finally the server sends the browser a proper graphic along with any cookies it may have changed.
We’ll explore a simple example. The user will be presented two form fields. Every five seconds whatever is currently entered in the upper field will be sent to the server and the value of a cookie will be output to the second field. The server will then prepend that string to the current date and time and populate our cookie with the result.
Code Listing 1 is the main page containing the JavaScript and the user interface:
<html> <head> <title>Test Page</title> <script language="Javascript1.1"> <!-- function GetServerData() { var DateSeed = new Date(); document.images.HeartBeat.src = "FauxGIF.cfm?Message=" + escape(document.DisForm.Message.value) + "&TimeSeed=" + escape(DateSeed); UpdateForm(); SetTimer(); } function UpdateForm() { var DocCookies = document.cookie; var Pos = DocCookies.indexOf("SERVERMESSAGE="); if (Pos != -1) { var StartPos = Pos + 14; var EndPos = DocCookies.indexOf(";", StartPos); if (EndPos == -1) EndPos = DocCookies.length; var CookieVal = DocCookies.substring(StartPos, EndPos); CookieVal = CookieVal.replace(/+/g, "%20"); CookieVal = unescape(CookieVal); } document.DisForm.ServerMessage.value = CookieVal } function SetTimer() { if (typeof(TimeOutID) == "number") { clearTimeout(TimeOutID); } TimeOutID = setTimeout("GetServerData()", 5000); } //--> </script> </head> <body onload="SetTimer();"> <form name="DisForm"> Your Message: <input type="Text" name="Message" value="" size="100"> <br> Server Response: <input type="Text" name="ServerMessage" value="" size="100"> </form> <img name="HeartBeat" src="FauxGIF.cfm" width="1" height="1" alt="" border="0"> </body> </html>
Starting from the bottom, note that line 41 adds a 1×1 pixel img tag to the page; but instead of calling a GIF or JPG we call “FauxGif.cfm” template. Note that you may also simply call a graphic here and save the CF template call for the JavaScript, but for purposes of example this is how we’ll proceed. Lines 36-40 are the form that will accept user input and JavaScript output.
Note that in this case we use the onLoad event handler in the body tag to kick off the action. The onLoad handler ensures that all the page assets, including our img tag, are loaded before triggering prevent “object not found” errors. However, depending on your need, you may use a user trigger event such as a button click or an image mouse over to launch your communications.
The onLoad handler calls the SetTimer() JavaScript function. All this function does is manage a recurring timeout that calls the GetServerData() function every 5000 milliseconds. The result of a setTimeout() function is a numeric identifier. The if statement at line 26 checks to see if there’s an existing timeout and if there is it destroys it. This ensures that only one timeout will be active at any time.
The GetServerData() function actually sends the data to the server. Line 8 builds the URL with two variables: “Message”, containing the escaped (escape() is equivalent to URLEncodedFormat() in CFML) content of the Message form field. The other variable, “TimeSeed” is simply the current browser time. Something like TimeSeed is needed to ensure that every URL created is unique and not pulled from the browser cache. Of course your seed can be just a simple counter or random number. The function finishes by calling the UpdateForm() function and restarting the timer with the SetTimer() function.
The UpdateForm() function spends most of its time retrieving the value of the “SERVERMESSAGE” cookie. JavaScript doesn’t automatically separate cookies from each request. Lines 13-19 extract the cookie value. Note that in line 16 StartPos is Pos plus the length of the cookie name and the equal sign; in our case “SERVERMESSAGE=” is 14 characters long.
One problem here: ColdFusion encoding replaces the space character with a plus (“+”) sign. The JavaScript escape() function converts spaces to the “%20” string. The problem is that the unescape() function does not convert the plus character to a space. Line 20 uses a simple regular expression replace to change all plus signs to “%20”. The cookie value is then decoded on line 21 using the JavaScript unescape() function. The last thing UpdateForm() does is actually update the form with the value of the cookie.
One thing to understand is that in this code, since the form is updated immediately after the graphic’s source is changed, it actually takes two cycles for the new information to show up. Once the first cycle of the data is sent to the server and immediately before the server is able to respond, the value of the cookie is displayed. Then, on the next cycle the same thing happens, but now the cookie has the new value.
Now let’s look at (much simpler) ColdFusion code that replaces our graphic:
<!--- Default the Message Variable ---> <cfparam name="URL.Message" default="" type="string"> <!--- Set the Time Cookie ---> <cfset RawMessage = "Your Message:(" & #URL.Message# & "); Server Time:(" & #TimeFormat(Now(), "hh:mm:ss")# & "," & #DateFormat(Now(), "mm/dd/yy")# & ")"> <cfcookie name="ServerMessage" value="#RawMessage#"> <!--- Write out the real GIF ---> <cfcontent type="image/GIF" file="d:WWWRootTestSessionTimeSpacer.gif">
This code is very simple. Line 2 defaults the URL variable expected to null. Lines 5 and 6 take the value and the date/time stamps and populate the “ServerMessage” cookie. Note that case doesn’t matter in ColdFusion, but it does in JavaScript. No matter what case you use in the CFML template for the cookie name it will be converted to ALL CAPS format when sent to JavaScript.
The last line is the trick. We use cfcontent to send an actual GIF image to the browser. In this case the gif is a single pixel, transparent graphic that won’t be seen by the user. You can, of course, use any image(s) that you like.
New and Improved Script (Added April 14, 2001)
Although all the above code works just fine and gets the point across there has been one additional feature requested by several people: the ability to “know” when the server response has been received. The code above works perfectly well when the client has no need to examine the server response (for example to maintain session state). A problem arises however when the client needs to react to the server response as quickly as possible. This updated code addresses that problem.
The primary addition is the CheckCookie() function (beginning on line 25) which is called (from within SendServerData() ) immediately after information is sent to the server (line 10). CheckCookie() takes the date/time seed created in SendServerData() as a transaction identifier. At its essence this function simply checks in the cookie for this transaction identifier until it finds it (which means that the cookie has been returned with updated information from the server) or times out.
As written the function recursively calls itself every 50 milliseconds (as defined in line 35) up to 500 times (as defined in line 29). These settings work out to a 25 second timeout. You should modify them to meet your needs. If the cookie changes properly before the timeout the “else” clause at line 38 is executed (which, in the example, runs the UpdateForm() function) if the timeout value is reached the “if” clause at line 30 is executed (which currently does nothing).
Another small modification from the original code is the use of the “valueOf()” method on the DateSeed variable (line 8). This converts the date/time object to a simple, smaller numeric value.
The updated client-side code:
<html> <head> <title>Test Page</title> <script language="Javascript1.1"> <!-- function SendServerData() { var DateSeed = new Date(); DateSeed = DateSeed.valueOf(); document.images.HeartBeat.src = "FauxGIF.cfm?Message=" + escape(document.DisForm.Message.value) + "&DateSeed=" + DateSeed; CheckCookie(DateSeed, 1); } function UpdateForm() { var DocCookies = document.cookie; var Pos = DocCookies.indexOf("SERVERMESSAGE="); if (Pos != -1) { var StartPos = Pos + 14; var EndPos = DocCookies.indexOf(";", StartPos); if (EndPos == -1) EndPos = DocCookies.length; var CookieVal = DocCookies.substring(StartPos, EndPos); CookieVal = unescape(CookieVal); CookieVal = CookieVal.replace(/+/g, " "); } document.DisForm.ServerMessage.value = CookieVal } function CheckCookie(DateSeed, CurCount) { if (typeof(TimeOutID) == "number") { clearTimeout(TimeOutID); } if (CurCount > 500) { return false; } var DocCookies = document.cookie; var Pos = DocCookies.indexOf("DateSeed" + DateSeed); if (Pos == -1) { TimeOutID = setTimeout("CheckCookie('" + DateSeed + "', " + (CurCount + 1) + ")", 50); } else { UpdateForm(); } } //--> </script> </head> <body> <form name="DisForm" method="post"> Your Message: <input type="Text" name="Message" value="" size="100"> <br> Server Response: <input type="Text" name="ServerMessage" value="" size="100"> <br> <input type="Button" value="Update Server Data" onclick="SendServerData();"> </form> <img name="HeartBeat" src="FauxGIF.cfm" width="2" height="2" alt="" border="0"> </body> </html>
The only change to the ColdFusion file is the addition of the DateSeed to the returned cookie:
<!--- Default the Message Variable ---> <cfparam name="URL.Message" default="" type="string"> <!--- Set the Time Cookie ---> <cfset RawMessage = "Your Message:(" & URL.Message & "); Server Time:(" & TimeFormat(Now(), "hh:mm:ss") & "," & DateFormat(Now(), "mm/dd/yy") & "); DateSeed" & URL.DateSeed> <cfcookie name="ServerMessage" value="#RawMessage#"> <!--- Write out the real GIF ---> <cfcontent type="image/GIF" file="d:WWWRootTestSessionTimeSpacer.gif">
Special thanks to James Pecora who collaborated with us on this code.
Ideas for this Technique
The above code may illustrate the technique but it doesn’t offer much in the way of useful functionality. Here are some ideas for using this technique to solve actual problems.
Gain Control over Session Timeouts
One continual problem with session-based CFML applications is the server-centric basis of customer usage. By default ColdFusion times out a user after 20 minutes of “inactivity”. However, inactivity in this case is limited to the retrieval of a CFML page marked as a member of the application in use. Often an application may present a task that requires more than twenty minutes of time between requests. A form could easily take more than twenty minutes to complete and some content could easily take more than twenty minutes to read.
Form fields could be made, using the onChange or onfocus handlers, to populate a variable with the time of the event, a “LatestEvent” tracker. The page could be set to check the value of that variable against the current time every, say, 5 minutes. If an event has occured in the past five minutes the script could launch a function that updates the graphic source. As long as the CFML template called is a member of the application, the user’s session will be properly maintained.
Another option is to present a pop-up window to the user informing them that their session is about to timeout. Their choice determines whether you modify the graphic src or not.
Manage Forms Better
The uses of the technique and forms are nearly endless. Although somewhat limited by the amount of data possible in the URL or Cookie there are still endless options. One of the easiest is adaptive forms that change as the user fills them out. A selection made in a select box could be sent to the server for processing and data to populate a second select box could be returned. Although special care needs to be taken to ensure that the lag in communication doesn’t affect the user experience, this option is very powerful when complex databases or calculations must be consulted to populate other options.
Another option is proactive data processing. For example, you may validate a credit card number via the graphic while the user is entering other information. This is where using visible graphics may come in handy. You can present an animated “checking credit card” graphic, then have the CFML template send either a “Credit Accepted” or “Credit Refused” graphic to the browser. The same idea can be used to the same effect for reverse DNS lookups, logins and other complex validations.
Generally Deal with the User more Intelligently
Using this technique it would be possible to, for example, spell check the values entered into a “search” field at the server before submitting the page. A a pop-up window or dynamic HTML routine could be used to present the corrected options before proceeding. Otherwise you either interrupt the process with another page to present the options or run the search anyway and possibly waste system resources and user time looking for a misspelled term. You could create a FAQ page that sent the internal (named) links that the user selected back to the server. This would allow you to better manage information and present more important items up front. Lacking a technique like this, those internal document links are lost and cannot be used to improve your user experience.
Similar Techniques
There are of course other ways to create in-page browser/server communication. Here are a few:
- The WDDX SDK available from [dead link] contains a free Java Applet, URLPipe, that does everything I’ve described and can deal with larger datasets. However, it does require that Java and scripting of Java applets be enabled.
- A hidden frame or layer could be used to send and retrieve data (and this technique would in fact be much better for larger, more complex data). Brent Ashley has, in fact, authored an entire library to do just this. It’s a great resource and available from Brent’s Remote Scripting Site. Thanks to Patrick Whittingham for pointing us to Brent’s work.
- Another option is Microsoft’s [dead link]. This works in much the same way as the URLPipe applet available in the WDDX SDK, but with many more options and documentation. Thanks to Patrick Whittingham for pointing us to this as well.
Conclusion
We hope that you’ll explore the potential of this technique. If you come up with something really slick you may want to consider doing a write up for it (or allowing us to). We’d be happy to share your idea with other readers!