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);" />

Tags:

Visualforce