[Looking for Charlie's main web site?]

Stopping multiple form submissions with CF 7/8 and "submitonce" validation

Note: This blog post is from 2008. Some content may be outdated--though not necessarily. Same with links and subsequent comments from myself or others. Corrections are welcome, in the comments. And I may revise the content as necessary.
What happens in your app if a user hits the submit button more than once before the form is processed, such as when the form didn't come back quickly enough, so the user submitted the form again? Would you be interested to know that CF has a feature to prevent them doing that? It's an often-missed hidden gem of CF7.

Have you ever considered this prospect of multiple form submissions? What could happen? It could cause multiple inserts to a database, or multiple charges to a card, or unexpected increases in some session variable counter, etc., which could be real trouble for you or your users.

It's a subject that comes up often in developer circles (even outside of CF). The good news is that there's a very simple solution in the available validate="submitonce" option of CFINPUT, which as of CF7 can be used for submit buttons. You'd use CFINPUT TYPE="submit" VALIDATE="submitonce" NAME="somename", as demonstrated in a complete code example below.

In this entry, I offer more info on the feature. I've not found too many other resources discussing it, so I hope this will help folks. There are some interesting challenges you should understand as well.

Problem already solved for you in Firefox--but do all your users use it?

For instance, if you've read about the feature and tried it on Firefox, you may have been surprised that you couldn't see what it did differently than a normal submit button. That's because it turns out Firefox already solves the problem itself, by preventing double-submission of a given form. It doesn't hurt to use the CF-based feature on FF. It just isn't needed. But you certainly still need the CF-based feature, though, if you may have users visiting your site with other browsers. (I can confirm that even IE 7 does not prevent double form submissions. Haven't tested Safari yet. Any takers want to report in the comments?)

Demonstrating the problem of multiple form submissions

It's not too difficult to demonstrate the problem of multiple form submissions. All you need is a means to track how many times the server-side form processing takes place for a given form's submission(s). In your real application, again, this may lead to multiple inserts in a database or multiple charges to a customer's credit card. To keep things simple, I'll demonstrate it using a session variable that's incremented on each form submission.

But then more important (to demonstrate the effect), we need the form processing to simulate "taking too long" so that a series of rapid submissions of a form will be able to be sent before the processing of the page "completes". We can do that in CF6+ by calling the Java thread sleep method, or in CF8 we can use the new sleep built-in function.

The example code is offered at the bottom here. Before you take a look at that, or try it out, note a few things.

Applies to form, not submit button

Besides the point above about Firefox being immune automatically to the multiple form submission problem, note that this multiple submission protection is enabled on the form itself, not on the submit button, so it does apply just as readily to forms submitted by pressing enter (when permitted by the browser) as by forms submitted by pressing the submit button.

The fact that it's enabled for the entire form (when the CFINPUT above is used) means also that you can't test this by having a "regular" submit button and a "protected" one in the same form. Again, it's not the submit button that gets the protection, but the form in which the CFINPUT type="submit" appears. You'll notice, therefore, that my demo code above uses 2 forms. (It's not at all important whether I use or don't use CFFORM in the "unprotected" form example. It just makes no difference.)

Does require use of CFFORM And Javascript

That makes a point though: the CFINPUT tag does indeed need to be used within a CFFORM tag, as shown in the second form. And further the feature is indeed relying on Javascript (generated by CF) to perform the multiple submit prevention. Neither should be a showstopper for most. You don't need to convert any other HTML tags within a form to their CFML CFFORM equivalents (like CFSELECT or CFTEXTAREA) just to enable the CFINPUT for form submission.

Some may want to point out that you don't need to use this particular approach to solve the problem: there are other Javascript-based approaches, as well as some that assert to work without JS. I'll leave it to commenters to mention them here, if interested.

My goal was to demonstrate the submitonce functionality, since it's not been discussed much. Do let me know if this was interesting to you.

Some final observations about my code example

I've provided comments in my code example code, about things that aren't related to the multiple submissions feature, but which may raise questions for some.

First, note that because I'm using sessions to demonstrate the feature, I've just gone ahead and put the CFAPPLICATION tag right into this template, so it doesn't matter where you put the code (won't be helped or hurt by an existing application.cfm or application.cfc, with respect to the session var created.)

As for the forms, note that I'm using self-posting forms. It doesn't matter if you do or don't for this multiple submission prevention feature to work.

And note that I don't use any means to force a filename into the form ACTION attribute, to make it post back to itself. If you leave the action empty, that makes the form self-posting. This is a legitimate HTML-specified use, if not widely known. No need to force the current file into it using #cgi.script_name# or the like.

And I determine if the form is submitted using a test for cgi.request_method being "post", which it will be on a form submission (versus it being a "get" when the page is first loaded). I don't use a test for whether the submit button is defined (a technique that is taught by some but will fail when the page is run on IE, if the user presses enter rather than the submit button, at least when there's only a single input field). Again, these choices have NO effect on the validity of the tests.

Finally, while I don't *need* to specify the empty action or post method on a CFFORM (since they're the default), I do it to avoid any confusion from those looking at the code and not familiar with that

The example code

You should be able to drop this code, verbatim, into any CFML page in any CF7 or 8 server to see the effect.

<cfapplication sessionmanagement="Yes" name="submitoncedemo">
<cfparam name="session.submitted" default="0">
<cfset interval=2>

<h4>Demonstration of New submitonce validation in CF7+</h4>

Try clicking each of the submit buttons below multiple times, in rapid succession, before the page returns from its #interval# second delay. Wait for that delay before noticing the count indicated after the 2nd form.
<p>
Notice how the "uncontrolled submit" will cause execution of multiple submissions (as tracked by a session variable). (The problem is not apparent in Firefox 1.5 and above, as it includes its own feature to prevent multiple form submissions. Try it on IE, though, even IE 7.)

<form name="test" action="" method="post">
<input type="Submit" value="Uncontrolled Submit">
<input type="button" onclick="location.href='<cfoutput>#cgi.script_name#</cfoutput>'" value="Reset Counter">
</form>
<!--- the use of no value for ACTION, to do a self-post of the form to itself, is legitimate HTML-specified functionality, if not widely known. No need to force the current file into it using #cgi.script_name# or the like. --->

<hr>
The "controlled submit" will not. Regardless of how many times you press it, it will only register a single increment in the session count, demonstrating that CF prevented the form processing from being executed more than once until the form processing page was completed.

<!--- This needs to be in a separate form from above, because the submitonce validation applies to the form, not the button. And while I don't *need* to specify the empty action or post method on a CFFORM (since they're the default), I do it to avoid any confusion from those looking at the code and not familair with that. --->
<cfform name="test2" action="" method="POST">
<cfinput type="Submit" validate="submitonce" name="submit" value="Controlled Submit">
<input type="button" onclick="location.href='<cfoutput>#cgi.script_name#</cfoutput>'" value="Reset Counter">
</cfform>

<cfif cgi.request_method is "get">
   <!--- if the form is requested the first time, or via the HREF below, reset the session variable. --->
   <cfset session.submitted=0>
<cfelseif cgi.request_method is "post">
   <!--- if the form is submitted, process it. (There are other ways to test form submissions, but this is a very good one for many reasons.) --->
   <!--- put thread to sleep for the number of seconds indicated above, to simulate a wait while form submission is being processed --->
   <cfset thread = createObject("java", "java.lang.Thread")>
   <cfset thread.sleep(javaCast("long", 1000*interval))>
   <!--- in CF8, could use sleep function:    <cfset sleep(interval)> --->

   <!--- yes, perhaps we should lock the sesion variable below, but it's not critical to help or hurt this demo--->
   <cfset session.submitted=session.submitted+1>
</cfif>

<cfoutput>
Submitted: #session.submitted# times.

<hr size="3" color="##000000">

<h4>Discussion</h4>
The form above has 2 submit buttons to demonstrate the new SubmitOnce validation in CF7+.
<p>
The first button, "Uncontrolled Submit", is a normal submit button (<input type="submit">).
<p>
The second button, "Controlled Submit", uses (<cfinput type="submit" validate="submitonce" name="somename">) which causes CF to prevent it being clicked twice to cause submission of a single form.
<p>
To demonstrate the effect, the action processing of this form is set to wait #interval# seconds before completing. It also outputs (using a session variable) how many times the button led to submission of a request to the form processing portion of the page.
<p>
Note that clicking the uncontrolled submit multiple times in rapid succession (on other than Firefox--haven't tested on Safari) will lead to multiple submissions. Even though you don't see the output of each submission (because each new submission starts a new request to the server), the increase in the session variable at a rate higher than just one per the #interval# second interval it takes for the form to complete. This demonstrates that multiple submissions are being caused.
<p>
Do the same with the "controlled submit" button, and that doesn't happen. It only ever increases by one per 3 second period.
<p>
</cfoutput>

For more content like this from Charlie Arehart: Need more help with problems?
  • If you may prefer direct help, rather than digging around here/elsewhere or via comments, he can help via his online consulting services
  • See that page for more on how he can help a) over the web, safely and securely, b) usually very quickly, c) teaching you along the way, and d) with satisfaction guaranteed
Comments
I have used the submitonce attribute on submit buttons before but with coldfusion 8 ajax features I find myself using ColdFusion.Ajax.submitForm on a normal button. I looked briefly into how to prevent the double-submit in javascript but It looked complicated.
# Posted By Michael White | 3/25/08 11:16 AM
Thanks for posting this. Over the years I've crafted various solutions to the multilpe submit problem. I had no idea this even existed.
# Posted By Kevin Slane | 3/27/08 7:18 AM
Sure, Kevin. Happy to help. That's indeed why I share this stuff. I'm sure a poll of most CFers would show that only a tiny fraction of them do know about it.

@Michael, I'm afraid I just don't know how the Ajax-based features will integrate with this feature. If anyone tries it, do let us know.
Where can I find info on the Firefox feature? Never heard of it b4.
# Posted By Derek | 4/13/10 9:03 AM
I'm not sure what feature you're asking about, Derek. Can you clarify?
The Firefox stopping extra form submissions. I did test with your page and it indeed does work. Never knew.
# Posted By Derek | 4/13/10 9:49 AM
But I'm not sure what info you want to find. It's not a feature one can enable/disable, as far as I know. I was merely passing along the observation (2 years ago) that I had found it to work that way. I"m not aware of any particular info on it that I can refer you to. Or am I misunderstanding your question?
ok,np. Just thought it would be mentioned somewhere since it is a pretty cool feature that I never knew about.
I was just doing research on best way to prevent multiple form submission server side and came across this.
# Posted By Derek | 4/13/10 12:33 PM
Thanks Charlie. This solved the issue for me instantly. I was not aware of this attribute for a cf submit button. Love CF!
I'm not a big fan of all of difficult to understand code bloat that using cfForm introduces into a web page. For that reason alone, I would not consider using this approach. Just sayin. :)

What I do, is bind a custom function that I use throughout my web site (where needed) that 1. Changes the button text to "Processing ..." 2. Inserts before the text a small animated ajax loader gif and 3. Disables the button. Then, I use ajaxComplete to reset the button once the form processing is competed.

Just my own little contribution here.
Sorry for the delay in responding to a couple of comments on this entry, folks.

@Grant, thanks so much. Great to hear it helped you. Yours is the very kind of response I was hoping for (and that it solved the problem so easily for you) when I wrote about this back in 2008.

@Mike, well, then there's your perspective. :-) If you read what Grant had said before you, and Kevin's comment several earlier, you'll see that for some people, it is indeed something they'd not only consider but embrace and appreciate. (Kevin even admitted having crafted his own solution but appreciated hearing that this was built-in.)

As for the "code bloat" from using CFFORM which you decry, I find that (frequent) assertion to be overstated personally. If anyone changed an existing form from being a plain form to a CFFORM, and added just this one new CFINPUT for the submitonce, the number of bytes added would be trivial (in size, and in complexity)

That said, most people who would be interested to use this feature wouldn't really care about the "complexity" of the code (if that) anyway, as they're generally not interested in "understanding the [Javascript] code". They just want to solve a problem, and this does that, pretty well, in a single CFML tag.

It's a shame (in my opinion) that people with higher-order requirements and experience seem so often to put down solutions (especially in CF) that do adequately suit folks with simpler needs. It's like someone with a post graduate finance degree coming into a high school and looking down their nose because the students can't appreciate the opportunity of covered interest arbitrage. The high schoolers just don't know what they don't know, but they can apply what they have learned to real world problems.

We're just not all at the "postgrad level" when it comes to implementing solutions, and many people get along fine in life with only a "high school" education. (I'm using these metaphorically, not asserting any connection between CFers and their actual education level.)

To be more specific, Mike, you outline your preferred approach, and I do appreciate your sharing it, for the sake of those who can take that ball and run with it. But I can assure you that many reading this wouldn't know how to "bind a custom function", or where to begin to use an "animated ajax loader gif", let alone how how to "use ajaxComplete to reset the button".

I realize I open a door of ridicule for responding this way. "Come on, Charlie. You're just coddling people. No wonder CFers have such a bad reputation." I just think we should acknowledge that there's a place for each class of CF user:

- those with mad Javascript/Ajax skilz, who want to cut and craft their pages to their own expectations, without CF "getting in the way"
- and those who don't really care to "use" Javascript (don't want to learn it, don't want to consider its vagaries, don't see problems for which it's a solution) who are happy if a single tag solves a problem by implementing that

And there's a range of folks between them, too. Let's just allow the water to find its own level, to truly mangle some metaphors. :-)
@Charlie, regarding your statement that "most people who would be interested to use this feature wouldn't really care about the "complexity" of the code", That may be somewhat true I suppose, yet when it comes time for another developer to modify that code a year later, sometimes that code can be less intuitive. It is generally unformatted, ugly, CFform generated JavaScript.

Regarding your statement "It's a shame (in my opinion) that people with higher-order requirements and experience seem so often to put down solutions (especially in CF) that do adequately suit folks with simpler needs." I stand corrected. I never even turned a computer on until I was 35. I struggled for years to learn even basic programming expertise. I've told myself in the past that I will never again look down on someone with simple questions that otherwise would be considered "stupid" by experienced professionals. Thanks for bringing me back to my senses. May I remain teachable!
# Posted By Mike | 1/17/13 9:00 AM
@Mike, thanks for your thoughts.

As for the first paragraph, I'd just reply that if someone inherited some CFML code which used CFFORM for just a simple feature like this, and that new developer didn't like it, they could easily remove it and replace it with their own code.

So again, I just don't buy the argument that people should be dissuaded from using it just for that future possibility. :-)

Still, I also appreciated your expression of humility regarding my earlier admonition. I didn't necessarily mean it directed at you personally, because you'd only shared a brief paragraph. But I was responding as much to all who I've heard make such (brief) arguments against features like this.

Anyway, if it did strike a chord, then may your humility there serve as a model for all of us to remember when challenged, myself included! :-)
Copyright ©2024 Charlie Arehart
Carehart Logo
BlogCFC was created by Raymond Camden. This blog is running version 5.005.
(Want to validate the html in this page?)

Managed Hosting Services provided by
Managed Dedicated Hosting