Using a GIF as a Data Pipe

Authored September 2000

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.

Submitted Links (Updated Feb 9, 2002)

  • Joseph Thompson has made some of this easier for you by encapsulating a session keep-alive system into an easy to use custom tag. Check it out at CFHub.com.

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, offers 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:

Using a GIF as a Data Pipe: Code Listing 1

Open this Code in a larger Window:
With Line Numbers or Without Line Numbers

Starting from the bottom, note that line 41 adds a 1x1 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.

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 preventing "object not found" errors. However, depending on your need, you may use a user triggered event such as a button click or an image mouse-over to launch your communication.

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 one exists 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 into name=value pairs so 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. If you change the name of the cookie this routine will have to be modified accordingly.

One problem here: ColdFusion encoding replaces the space character with a plus ("+") sign (note that later versions use "%20"). The JavaScript escape() function converts spaces to the "%20" string. However the JavaScript 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 unescape() function. The last thing UpdateForm() does is update the form with the value of the cookie.

Understand that with this code it actually takes two "cycles" for the new information to show up. This is because the form is updated immediately after the graphic's source is changed. The first cycle sends the data to the server and immediately (before the server is able to respond) the value of the cookie is displayed. Then the same thing happens again but the cookie has the new value.

Now let's look at (much simpler) ColdFusion code that replaces our graphic:

Using a GIF as a Data Pipe: Code Listing 2

Open this Code in a larger Window:
With Line Numbers or Without Line Numbers

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. Remember that character 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 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 that you like.

New and Improved Script

Additional Material Added April 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 when used 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:

Using a GIF as a Data Pipe: Code Listing 3

Open this Code in a larger Window:
With Line Numbers or Without Line Numbers

The only change to the ColdFusion file is the addition of the DateSeed to the returned cookie:

Using a GIF as a Data Pipe: Code Listing 4

Open this Code in a larger Window:
With Line Numbers or Without Line Numbers

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 session after 20 minutes of "inactivity". However, inactivity in this case is limited to the retrieval of a CFML template which is 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 with a call to a CFM template. As long as the template called is a member of the application, the user's session will be properly maintained.

Another option is to present a JavaScript pop-up window to the user informing them that their session is about to timeout. Their choice determines whether you modify the graphic's source or not.

Manage Forms Better

The uses for this technique and forms are endless. Although limited by the amount of data possible as a URL variable or Cookie there are still endless options. One possibility is building adaptive forms that change as the user fills them out. The option chosen from a select box could be sent to the server immediately 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 before populating other options.

Another option is proactive data processing. For example, you may validate a credit card number via a 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 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 track user activity 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 PengoWorks excellent Client/Server Gateway JSAPI is an easy to use and full-featured API for passing JavaScript objects seamlessly between the server and the client.
  • The WDDX SDK available from www.openwddx.org 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 Remote Scripting Toolkit. 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. Just drop us a line at webmaster@depressedpress.com.

53 Current Sessions; Time: 16:21:30 23-07-2008; Tick: 937