47

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

5
  • Should this question be protected?
    – Adrian Larson
    Commented Mar 20, 2016 at 20:18
  • @AdrianLarson Done. Commented Mar 20, 2016 at 20:22
  • 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. Commented Mar 20, 2016 at 20:32
  • 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.
    – Adrian Larson
    Commented Mar 20, 2016 at 20:32
  • 1
    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. Commented Mar 20, 2016 at 20:42

10 Answers 10

19

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>
8
  • 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. Commented Mar 26, 2013 at 21:00
  • 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 Pond
    Commented Mar 26, 2013 at 23:29
  • 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? Commented Mar 18, 2014 at 23:54
  • @DavidCheng, yes you are correct. The <apex:commandlink /> 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.
    – Mark Pond
    Commented Mar 20, 2014 at 18:29
  • If my action method has return pagereference then it does not navigate instead it stays on the same page.
    – snehakem
    Commented Jul 20, 2016 at 20:24
21

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.

2
  • 1
    Upvoted due to simplicity. Worked for me, too. Commented Oct 12, 2016 at 12:44
  • Simple, worked for me.
    – gorav
    Commented Jan 23, 2019 at 16:33
14

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);" />
3
  • I've tried this for Save/Cancel buttons, and for "punch-out" buttons--where the user navigates temporarily to another form--using both direct GETs from the page and controller-action redirects. Works flawlessly. I extended it a little to allow the value (the button label after onClick) to be passed in from the button declaration. Commented Jun 12, 2014 at 12:59
  • any idea why you can't immediately disable the button? (how does this prevent the submit from executing?)
    – NSjonas
    Commented Apr 22, 2015 at 4:44
  • @NSjonas I believe it is an HTML thing. See Disable a button on click and Successful controls "Controls that are disabled cannot be successful." with respect to a control that is valid for submission. Commented Apr 22, 2015 at 19:40
3

You can use action statuses to accomplish this without javascript

<apex:actionStatus id="mySaveStatus1">
    <apex:facet name="stop">
        <apex:commandButton value="Save Mappings" action="{!save}" rerender="dummy" status="mySaveStatus1"/>
    </apex:facet>
    <apex:facet name="start">
        <apex:commandButton value="Processing..." status="mySaveStatus1" disabled="true"/>
    </apex:facet>
</apex:actionStatus>

Not my idea, found it here via https://tquilamockingbird.wordpress.com/2011/11/10/salesforce-actionstatus-with-disabled-button-and-processing-image-100-on-platform/

3

Took @Daniel Ballinger solution and improved upon it a bit. For one, his version required that the button be given an Id. This uses a closure instead of selecting by the Id.

<script>
        function disableOnSubmit(input) {
            var btn = input;
            setTimeout(function(){ 
                btn.disabled = 'disabled';
                // Use the Salesforce CSS style to make the button appear disabled
                btn.className = 'btnDisabled';
                btn.value = "Saving..."; 
            }, 50);
        }
</script>    
<apex:commandButton value="Save" action="{!save}" onclick="disableOnSubmit(this);" />
2

Try commandButton with actionFunction:

<commandButton onclick="disableMe" oncomplete="runActionJS();"/>
<actionFunction name="runActionJS" action"{!action}"/>
3
  • Will the actionFunction respect the PageReference returned by the controller action and redirect the user? Also, how does the disableMe function work? If it disables the button too soon nothing will happen. Commented Mar 26, 2013 at 21:02
  • Yes it respects the pageReference. This actually took me a while to figure out! disableMe is a JS function that just sets the style of the button to disabled (grays it out).
    – Mossi
    Commented Apr 3, 2013 at 16:41
  • changing the style of the button does not prevent double click. Do you set the attribute disabled to true as well in your JS function?
    – snehakem
    Commented Jul 20, 2016 at 22:24
1

This is my approach of how to disable commandButton until first click is processed and re-enable after first click results are returned from controller to page.

<apex:page>
    <script>
        var defaultEventListenerForSubmitBtn;
    </script>
    <apex:outputPanel id="op-panel">
    </apex:outputPanel>
    <apex:commandButton value="Submit" 
                        action="{!controllerActionMethod}" 
                        rerender="op-panel"  
                        onclick="this.value='Processing..';defaultEventListenerForSubmitBtn = this.onclick;this.onclick=function(){this.value='Processing..';return false;}" 
                        oncomplete="this.onclick = defaultEventListenerForSubmitBtn;this.value='Submit';"/>
</apex:page>
0

I was too behind this scenario to stop calling the action multiple times. This is what i've done. Maybe it will be useful.

Below is the command button.

<apex:commandButton value="Save" action="{!SaveForm}" id="csSave" onClick="return ValidatePage();"  />

When i click on the save buton at once the JS validation function ValidatePage() is called. In this i rename the value of the submit button as "Saving..." by the code

document.getElementById("FormPage:Form:FormBlock:csButtons:csSave").value = "Saving...";

And at the very first line of the Valaidation() function i put the following

var csSaveStatus = document.getElementById("LFormPage:Form:FormBlock:csButtons:csSave").value;
    if(csSaveStatus == "Saving...")
    {
        //alert("Save in progress.");
        return false;
    }

This will always return false silently and action is prevented to call multiple times. Additionally for the "disabled button" feel i just changed the button class name.

var btn1 = document.querySelector("[id$='csSave']");
btn1.className = 'btn btnDisabled';

Here is my JS validation function altogether.

function ValidateLoyaltyCase() {

var csSaveStatus = document.getElementById("FormPage:Form:FormBlock:csButtons:csSave").value;
if (csSaveStatus == "Saving...") {
    //alert("Save in progress.");
    return false;
}

document.getElementById("FormPage:Form:FormBlock:csButtons:csSave").value = "Saving...";
var btn1 = document.querySelector("[id$='csSave']");
btn1.className = 'btn btnDisabled';

}

-1

I prefer just hiding the button (rather than disabling it) and displaying an animated "waiting" gif when the button is clicked. It still allows the form to process properly and gives the user a little more feedback.

This assumes you have a resource called "WaitingGif" (mine is a spinny little animated gif):

<apex:pageBlockButtons >
    <div class="waitingGifDiv" >
        <apex:image id="WaitingGif" value="{!URLFOR($Resource.WaitingGif)}" width="50" height="50" style="float:center-right; "/>
    </div>
    <div class="SubmitButtonDiv">
        <apex:commandButton id="SubmitButton" styleClass="SubmitButton" value="Submit Case" action="{!submitCase}" />
    </div>
</apex:pageBlockButtons>

Then you'll need just a little javascript (in this case with a little jquery help) to first hide the animated gif when the page loads. Then when the user clicks the button, hide the button and unhide the animated gif.

 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script >

    j$ = jQuery.noConflict();
    j$(document).ready(function() {
       //code when page is ready 
       j$('.waitingGifDiv').hide();

       j$('.SubmitButton').on('click',function() {
           j$('.waitingGifDiv').show();
           j$('.SubmitButtonDiv').hide();
        });
    });
</script>
1
  • 2
    Or you could use the built in actionSatus components and ditch the javascript and need for jquery.....If used properly the button will be hidden and the animation will be visible during the ajax request
    – Eric
    Commented Jan 31, 2015 at 1:10
-1

Simplest of all. Just adding the onClick event handler and setting the disabled attribute to true solved my problem of repeated form submission and duplicate record creation.

<apex:commandButton value="Save" action="{!save}" onClick="this.disabled=true;"/>
1
  • 2
    Does this work in practice? I'd found disabling the button onclick would prevent the POST back from occurring. Commented Mar 20, 2016 at 20:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .