Disable commandButton after first click to prevent double submission
The mechanism I have found to be most maintainable uses a JavaScript function called by an element's click
event, which internally calls an actionFunction
to post the form, disables the buttons on the page and then returns false on the commandlink/button. This is used in conjunction with an oncomplete
on the actionFunction
to re-enable the buttons when the form has been (ajax) posted and returns a result to the page.
Without the rerender
attribute the form performs a full postback along with the disabling of the buttons.
Note: You can't disable the button(s) before the form is posted or the data sent to the controller will not include which button/link within the form was clicked and thus which action to execute.
<script src="//ajax.googleapis.com/ajax/libs/jquery/latest/jquery.js"></script>
<script>
function buttonsEnabled(enabled) {
// retrieve all of the buttons or links on the page
// with the css class of btn
var $buttons = jQuery('.btn');
if (enabled === false) {
// add the btnDisabled class to give it the look of being disabled
// add the disabled attribute to actually disable interactability
$buttons.toggleClass('btnDisabled', true).attr('disabled', 'disabled');
} else {
// remove the css class and the disabled attribute
$buttons.toggleClass('btnDisabled', false).attr('disabled', null);
}
}
function doSomeWork() {
// first, call the action function to post the form
doSomeWorkActionFunction();
// second, disable the buttons
buttonsEnabled(false);
// third, return false to prevent the click from
// posting the form a second time
return false;
}
</script>
<apex:form>
<apex:actionFunction name="doSomeWorkActionFunction"
action="{!yourControllerMethod}"
oncomplete="buttonsEnabled(true);"
rerender="whateverYouNeedToRerender"></apex:actionFunction>
<apex:commandLink action="{!yourControllerMethod}"
value="Your Text Here"
id="theCommandLink"
onclick="return doSomeWork();" />
</apex:form>
This seems to work:
<apex:commandButton value="Save" onclick="this.onclick=function(){return false;}" action="{!SaveAll}" />
It appears that the first time through, the event listeners fire before the onclick gets replaced by the "do nothing" function. So I can press the button as often as I want but only the first press counts. None of the actionfunction / rerender things worked for my whole page submit, but this does, and it's simple.
This is one possible solution, I'm still keen to see if there is a better way of doing this, such as getting the actionStatus and facet to work with a PageReference redirect.
If the commandButton onclick event is used to immediately disable the button the post back to the controller method won't occur.
This won't work:
<apex:commandButton id="save" value="Save" action="{!save}"
onclick="this.disabled='disabled';return true;" />
Instead, create a JavaScript function that will disable the button after a short timeout via the commandButtons onclick event. You could inline this all into the onclick as required. I found it easier to split the functions out so I could disable other related buttons, etc.
<script>
function disableOnSubmit(input) {
setTimeout('disableAfterTimeout(\'' + input.id + '\');', 50);
}
function disableAfterTimeout(id) {
var toDisable = document.getElementById( id );
toDisable.disabled = 'disabled';
// Use the Salesforce CSS style to make the button appear disabled
toDisable.className = 'btnDisabled';
toDisable.value = "Saving..."
}
</script>
<apex:commandButton id="save" value="Save" action="{!save}" onclick="disableOnSubmit(this);" />