Disable commandButton after first click to prevent double submission

  • I have an apex:commandButton on a visualforce page that invokes a method on the controller. There is currently no rerender value set for the button. Instead the controller method returns a PageReference to redirect the user as required.

    E.g.

    Visualforce:

    <apex:commandButton value="Save" action="{!save}"/>
    

    Controller:

    public PageReference save() {
        // save body ...
    
        // return the user the a parent opportunity
        return new PageReference('/' + opp.Id);
    }
    

    Users are currently able to click on the resulting save button multiple times before the controller completes and returns the PageReference to redirect the browser to an opportunity.

    How can I disable the commandButton after the first click?


    I tried wrapping the commandButton in an actionStatus and facet, but the need to define the rerender property prevented the resulting PageReference redirect.

    E.g.

    This will render nicely in the browser and prevent multiple clicks, but doesn't redirect to the Opportunity on completion.

    <apex:actionStatus id="saveStatus">
        <apex:facet name="stop">
            <apex:commandButton value="Save" action="{!save}" status="saveStatus" rerender="saveParentBlock" />
        </apex:facet>
        <apex:facet name="start">
            <apex:commandButton value="Saving..." disabled="true" status="saveStatus"/>
        </apex:facet>
    </apex:actionStatus>
    

    There is a similar question Using jQuery to disable VF page button onclick. I need to support an existing PageReference redirect, which slightly alters the requirements.

    Force.com Discussion Boards: Disabling a commandButton to prevent double submission

    Ideas: Disable command buttons on click in visualforce as standard

    Should this question be protected?

    @AdrianLarson Done.

    To be fair, they aren't really spam answers or "me too!". A couple of them lack any content other than links to other sites. I suspect one answer won't work at all as it disables the postback. It should be quick for someone to gain 10 rep if they really want to answer.

    Agreed. Pretty unusual to get this many answers to one question, but for the most part they don't require deletion. Another reason I wasn't sure.

    You've changed my mind. Unprotected now. The answers are at least attempting to answer the question. It hasn't been too problematic to deal with them.

  • Mark Pond

    Mark Pond Correct answer

    8 years ago

    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>
    

    Will this work where the controller method that the action calls returns a PageReference that the user should be redirected to? I don't need to do an ajax rerender of a component.

    Yes, just don't specify the rerender attribute info on the apex tags, return a PageReference on the action method and it'll behave exactly that way. This also doesn't use a setTimeout method call because you don't need it. You simply need to control the order in which these actions in the page occur. Using a setTimeout implementation allows the user the ability (albeit small) the ability to submit twice while the script pauses the action of disabling of the buttons.

    Mark - I don't understand why both the commandLink and the actionFunction are calling the same yourControllerMethod. Does the commandLink's action get bypassed by the onclick?

    @DavidCheng, yes you are correct. The `` doesn't need the action attribute on it as it won't ever be executed. Since the JavaScript is executed first and then that function returns `false` the click action on the link itself does not take place. So, the form post is happening via the `actionFunction` rather than the link click.

    If my action method has return pagereference then it does not navigate instead it stays on the same page.

    I know this is a very old post, but for some reason when I use the code above, my {!yourControllerMethod} gets executed 2 times per click, even though the button can be clicked only once. I also attempted removing the action button from the commandLink, but that did not solve the issue. Has anyone faced this problem?

    @IvoDimov if you ask another question and post your code in it, we can assist in getting it sorted out. You could add a link in it to this question for context / reference.

License under CC-BY-SA with attribution


Content dated before 7/24/2021 11:53 AM

Tags used