A few years ago I wrote a quick blog entry on working with CAPTCHA and ColdFusion. (Quick and dirty CAPTCHA Guide - for ColdFusion 8) A reader on that entry asked if it was possible to add a "reload if you can't read feature" to the CAPTCHA. We've all seen CAPTCHAs before that are impossible to read. For many of these forms you have no choice but to submit the form and hope the next one is better. But more and more forms are allowing you to reload just the CAPTCHA. Here is a quick example of how I modified the earlier blog entry to support this.

To start off - I modified my code from the old entry to avoid using a hash of the CAPTCHA for validation. Instead I stored the value in the session scope and verified it there. Here is the modified template. I'm not going to explain it since it's 95% the same as the old blog entry, but as you can see I now don't bother with a hash.

<cffunction name="makeRandomString" returnType="string" output="false"> <cfset var chars = "23456789ABCDEFGHJKMNPQRS"> <cfset var length = randRange(4,7)> <cfset var result = ""> <cfset var i = ""> <cfset var char = "">

<cfscript> for(i=1; i <= length; i++) { char = mid(chars, randRange(1, len(chars)),1); result&=char; } </cfscript>

<cfreturn result> </cffunction>

<cfset showForm = true> <cfparam name="form.name" default=""> <cfparam name="form.comments" default=""> <cfparam name="form.captcha" default="">

<cfif isDefined("form.send")> <cfset errors = "">

<cfif not len(trim(form.name))> <cfset errors = errors & "You must include your name.<br />"> </cfif>

<cfif not len(trim(form.comments))> <cfset errors = errors & "You must include your comments.<br />"> </cfif>

<cfif form.captcha neq session.captcha> <cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />"> </cfif>

<cfif errors is ""> <!--- do something here ---> <cfset showForm = false> </cfif>

</cfif>

<cfif showForm>

<cfset session.captcha = makeRandomString()>

<cfoutput> <p> Please fill the form below. </p>

<cfif isDefined("errors")> <p> <b>Correct these errors:<br />#errors#</b> </p> </cfif>

<form action="#cgi.script_name#" method="post" > <table> <tr> <td>Name:</td> <td><input name="name" type="text" value="#form.name#"></td> </tr> <tr> <td>Comments:</td> <td><textarea name="comments">#form.comments#</textarea></td> </tr> <tr> <td>Enter Text Below:</td> <td><input type="text" name="captcha"></td> </tr> <tr> <td colspan="2"> <cfimage action="captcha" width="300" height="75" text="#session.captcha#"> </td> </tr> <tr> <td> </td> <td><input type="submit" name="send" value="Send Comments"></td> </tr> </table> </form> </cfoutput>

<cfelse>

<cfoutput> <p> Thank you for submitting your information, #form.name#. We really do care about your comments. Seriously. We care a lot. </p> </cfoutput>

</cfif>

If you want to run this, you can do so here. But that isn't the interesting one. Let's look at a nicer version.

In order to support reloading just the CAPTCHA, I moved the CAPTCHA display into a DIV I'll be loading via Ajax. So where before I had the cfimage tag, now I have just this:

<div id="captchaDiv"></div>

I used some jQuery to load it:

$("#captchaDiv").load("showcaptcha.cfm");

showcaptcha.cfm now includes the logic to generate a random CAPTCHA string and to store/render it.

<cffunction name="makeRandomString" returnType="string" output="false"> <cfset var chars = "23456789ABCDEFGHJKMNPQRS"> <cfset var length = randRange(4,7)> <cfset var result = ""> <cfset var i = ""> <cfset var char = "">

<cfscript> for(i=1; i <= length; i++) { char = mid(chars, randRange(1, len(chars)),1); result&=char; } </cfscript>

<cfreturn result> </cffunction>

<cfset session.captcha = makeRandomString()> <cfimage action="captcha" text="#session.captcha#" width="300" height="75">

Ok, so how do we handle reload? After my div, I added a quick link:

Can't read? <a href="" id="reloadLink">Reload</a>

And then wrote a lot of JavaScript to handle clicks there. (OK, maybe not a lot of code - but jQuery saves me such much time I've got to pretend I actually work!)

$("#reloadLink").click(function(e) { $("#captchaDiv").load("showcaptcha.cfm"); e.preventDefault(); });

And that's it. The form page has changed quite a bit so I'll display it completely below. Before that you can demo it here:

<cfset showForm = true> <cfparam name="form.name" default=""> <cfparam name="form.comments" default=""> <cfparam name="form.captcha" default="">

<cfif isDefined("form.send")> <cfset errors = "">

<cfif not len(trim(form.name))> <cfset errors = errors & "You must include your name.<br />"> </cfif>

<cfif not len(trim(form.comments))> <cfset errors = errors & "You must include your comments.<br />"> </cfif>

<cfif form.captcha neq session.captcha> <cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />"> </cfif>

<cfif errors is ""> <!--- do something here ---> <cfset showForm = false> </cfif>

</cfif>

<cfif showForm>

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> <script> $(document).ready(function() { $("#captchaDiv").load("showcaptcha.cfm");

$("#reloadLink").click(function(e) { $("#captchaDiv").load("showcaptcha.cfm"); e.preventDefault(); }); }) </script>

<cfoutput> <p> Please fill the form below. </p>

<cfif isDefined("errors")> <p> <b>Correct these errors:<br />#errors#</b> </p> </cfif>

<form action="#cgi.script_name#" method="post" > <table> <tr> <td>Name:</td> <td><input name="name" type="text" value="#form.name#"></td> </tr> <tr> <td>Comments:</td> <td><textarea name="comments">#form.comments#</textarea></td> </tr> <tr> <td>Enter Text Below:</td> <td><input type="text" name="captcha"></td> </tr> <tr> <td colspan="2"> <div id="captchaDiv"></div> Can't read? <a href="" id="reloadLink">Reload</a> </td> </tr> <tr> <td> </td> <td><input type="submit" name="send" value="Send Comments"></td> </tr> </table> </form> </cfoutput>

<cfelse>

<cfoutput> <p> Thank you for submitting your information, #form.name#. We really do care about your comments. Seriously. We care a lot. </p> </cfoutput>

</cfif>