Rails & JavaScript tip: Disable submit button on all forms after submit
(UPDATE: SEE COMMENTS FOR CODE UPDATE)
I'm still testing this, but so far it works. Please let me know if you come across any bugs with it.
If you've ever watched non-tech savy people use a computer you've seen them double-click EVERYTHING. Including submit buttons on forms. Which leads to double-data entry.
Eew.
I didn't find anything built into Rails to deal with this (I could have missed it). I've coded a JavaScript solution that applies itself to every form in my web application, Pudding, without having to do anything to the forms. The exception to this are the Ajax forms. I'm not happy with my solution, but I've got it working. (If anyone sees a better way to do this, please let me know!)
For the code below to work you need to be using Ruby on Rails, the "submit_tag" function to generate all your submit buttons, and have the Prototype JavaScript library included in your pages. (You can adapt this to your own framework, but you've got to have a consistent name for your submit buttons. "submit_tag" gives every submit button the name, "commit". And I'm using Prototype's functions to make things easier for me.)
How does this all work?
Starting from the bottom of the code...
The Event.observe(window, 'load', addListeners); line forces your web browser to call the addListeners(e) function once the page is done loading. The addListeners(e) function is a great place to put all function calls that "setup" event listeners.
The addFormHelperListener() function is then called. It loops over all the forms on your page and ties the submission of the form to the disableSubmitButtonByEvent function up there in that FormHelper object. The disableSubmitButtonByEvent takes a look at the event that fired off (the submission of the form) and pulls out the DOM element that caused it - the form.
Since the Ruby on Rails function, "submit_tag", is used on all my forms they contain the HTML attribute, name="commit". Which allows me to grab the submit button out of the form object. Then it's easy! You just change the value of the submit button and then disable it.
Now the user can't double click the submit button!
As I mentioned earlier, the code works for all the forms on my web application except the Ajax ones. See that function, disableSubmitButton?
When you're using the "form_remote_tag" function you just add this option and you're good to go.
A full example looks like this:
I hate tacking on that ":before" option, but I couldn't figure out why it wasn't working. I'm guessing it's got something to do with the ":complete" option....
FUN FACT:
I had originally written this:
like this:
But it didn't work in Safari.
Safari, you're on notice.
I'm still testing this, but so far it works. Please let me know if you come across any bugs with it.
If you've ever watched non-tech savy people use a computer you've seen them double-click EVERYTHING. Including submit buttons on forms. Which leads to double-data entry.
Eew.
I didn't find anything built into Rails to deal with this (I could have missed it). I've coded a JavaScript solution that applies itself to every form in my web application, Pudding, without having to do anything to the forms. The exception to this are the Ajax forms. I'm not happy with my solution, but I've got it working. (If anyone sees a better way to do this, please let me know!)
For the code below to work you need to be using Ruby on Rails, the "submit_tag" function to generate all your submit buttons, and have the Prototype JavaScript library included in your pages. (You can adapt this to your own framework, but you've got to have a consistent name for your submit buttons. "submit_tag" gives every submit button the name, "commit". And I'm using Prototype's functions to make things easier for me.)
var FormHelper = {
disableSubmitButtonByEvent: function(event) {
var theForm = Event.element(event);
var submitButton = theForm.commit;
this.disable(submitButton);
},
disableSubmitButton: function(theForm) {
var submitButton = theForm.commit;
this.disable(submitButton);
},
disable: function(submitButton) {
submitButton.value = "Processing...";
$(submitButton).disable();
}
};
function addFormHelperListener() {
var formsInPage = document.forms;
for (var i=0; i<=formsInPage.length; i++) {
Event.observe(formsInPage[i],
'submit',
FormHelper.disableSubmitButtonByEvent.bindAsEventListener(FormHelper));
}
}
function addListeners(e) {
addFormHelperListener();
}
Event.observe(window, 'load', addListeners);
How does this all work?
Starting from the bottom of the code...
The Event.observe(window, 'load', addListeners); line forces your web browser to call the addListeners(e) function once the page is done loading. The addListeners(e) function is a great place to put all function calls that "setup" event listeners.
The addFormHelperListener() function is then called. It loops over all the forms on your page and ties the submission of the form to the disableSubmitButtonByEvent function up there in that FormHelper object. The disableSubmitButtonByEvent takes a look at the event that fired off (the submission of the form) and pulls out the DOM element that caused it - the form.
Since the Ruby on Rails function, "submit_tag", is used on all my forms they contain the HTML attribute, name="commit". Which allows me to grab the submit button out of the form object. Then it's easy! You just change the value of the submit button and then disable it.
Now the user can't double click the submit button!
As I mentioned earlier, the code works for all the forms on my web application except the Ajax ones. See that function, disableSubmitButton?
When you're using the "form_remote_tag" function you just add this option and you're good to go.
:before => "FormHelper.disableSubmitButton(this)"
A full example looks like this:
<% form_remote_tag(:update => [ID OF ELEMENT TO UPDATE],
:before => "FormHelper.disableSubmitButton(this)",
:url => {:controller => 'piece',
:action => 'new_comment'},
:complete => "[I'M CALLING SOME JAVASCRIPT TO SYNC UP SOME PAGE ELEMENTS.]") do -%>
I hate tacking on that ":before" option, but I couldn't figure out why it wasn't working. I'm guessing it's got something to do with the ":complete" option....
FUN FACT:
I had originally written this:
for (var i=0; i<=formsInPage.length; i++)
like this:
for (var currentForm in formsInPage)
But it didn't work in Safari.
Safari, you're on notice.
Labels: javascript, ruby_on_rails

