Tracking File-Download Events Using JavaScript And ColdFusion
In my ColdFusion applications, I either use the CFContent tag or the X-SendFile module to stream binary data back to the client as a download. Then, in the browser, I simply link to the ColdFusion page that delivers the binary response. This works perfectly well; but, it's not great for client-side events because "Content-Disposition: attachment" doesn't trigger a "load" event on the client. As such, it's very difficult to know, in the JavaScript, when the download event has executed successfully. Luckily, this morning, I stumbled across a Stack Overflow answer that had a great suggestion - track the download event using cookies!
The concept here is super simple:
- Have the server-side download page set a Cookie.
- Check for that cookie on the client-side.
Since the cookie headers will only arrive on the client when the download response has arrived, it means that the mere existence of the cookie will indicate that the user has completed the download request. Brilliant in its simplicity!
To test this, I set up a page with a download link. Then, using jQuery, I intercept the navigation event (on the link) and append the cookie value that the server will echo. Working with cookies on the client is, in my experience, extremely frustrating; as such, my demo does only the bare minimum needed to perform the test in this context.
Index.cfm - Our Client-Side Code
<!doctype>
<html>
<head>
<title>
Tracking File-Download Events Using JavaScript And ColdFusion
</title>
</head>
<body>
<h1>
Tracking File-Download Events Using JavaScript And ColdFusion
</h1>
<p>
<a href="./download.cfm" class="download">Download the file</a>
</p>
<!---
Hide the Thank You until the user actually executes the
target download.
--->
<p class="thanks" style="display: none ;">
<strong style="background-color: yellow ;">
Thank you for your download!
</strong>
</p>
<script
type="text/javascript"
src="//codeorigin.jquery.com/jquery-2.0.3.min.js">
</script>
<script type="text/javascript">
$( "a.download" ).click(
function( event ) {
var target = event.target;
// When tracking the download, we're going to have
// the server echo back a cookie that will be set
// when the download Response has been received.
var downloadID = ( new Date() ).getTime();
// Update the URL that is *currently being requested*
// to contain the downloadID. This will then be response
// cookie header.
target.href += ( "?downloadID=" + downloadID );
// The local cookie cache is defined in the browser
// as one large string; we need to search for the
// name-value pattern with the above ID.
var cookiePattern = new RegExp( ( "downloadID=" + downloadID ), "i" );
// Now, we need to start watching the local Cookies to
// see when the download ID has been updated by the
// response headers.
var cookieTimer = setInterval( checkCookies, 500 );
// I check the local cookies for an update.
function checkCookies() {
// If the local cookies have been updated, clear
// the timer and say thanks!
if ( document.cookie.search( cookiePattern ) >= 0 ) {
clearInterval( cookieTimer );
$( "p.thanks" ).show();
return(
console.log( "Download complete!!" )
);
}
console.log(
"File still downloading...",
new Date().getTime()
);
}
}
);
</script>
</body>
</html>
As you can see, the JavaScript appends a "downloadID" value to the outgoing request URL. Then, it starts a timer, waiting for that downloadID to show up in the local, client-side cookies. This downloadID cookies gets set using the CFCookie tag on the server:
Download.cfm - Our Server-Side Code
<!---
This is the download-tracker value that we will need to set
in the response so that the client-side knows when the download
has been executed.
--->
<cfparam name="url.downloadID" type="string" default="" />
<!--- Pause here to simulate download preperation. --->
<cfset sleep( 5 * 1000 ) />
<!--- Set the tracking cookie. --->
<cfcookie
name="downloadID"
value="#url.downloadID#"
/>
<!--- Define the download content. --->
<cfheader
name="content-disposition"
value="attachment; filename=download.txt"
/>
<cfcontent
type="application/octetstream; charset=utf-8"
variable="#charsetDecode( 'Download file content.', 'utf-8' )#"
/>
That's all there is to it! If I make a request to the download page, which has a sleep() command for several seconds, I get the following console output:
File still downloading... 1379695746067
File still downloading... 1379695746565
File still downloading... 1379695747066
File still downloading... 1379695747565
File still downloading... 1379695748066
File still downloading... 1379695748568
File still downloading... 1379695749066
File still downloading... 1379695749585
File still downloading... 1379695750065
File still downloading... 1379695750567
Download complete!!
How sweet is that! This is so simple, I can't believe I've never seen this before! I'll definitely be incorporating this approach in my ColdFusion applications.
Want to use code from this post? Check out the license.
Reader Comments
I like. It makes it easy to respond gracefully to the download activity and success.
<requested crack comment upload complete>
Have you had a chance to test this with X-Send yet?
@Tim,
Yes, I just tested this and can confirm that it works the same way with X-SendFile.
@Gary,
Thanks my man!