How to do double-click prevention in JSF
Asked Answered
M

9

34

We have a few search pages that run against a lot of data and take a while to complete. When a user clicks on the search button, we'd like to not allow them to submit the search result a second time.

Is there a best practice for doing "double-click" detection/prevention in JSF?

The PrimeFaces component seems like it can do what we want as it will disable the UI for a period of time between when the search button is clicked and when the search completes, but is there a more generic strategy we can use (perhaps something that isnt reliant on PrimeFaces)? Ideally, any click of the button will either be disabled or disregarded until the search completes. We dont necessarily need to disable the entire UI (as blockUI allows you to do).

Marylynnmarylynne answered 25/5, 2012 at 14:41 Comment(0)
C
41

If you're using solely ajax requests, you could use jsf.ajax.addOnEvent handler of the JSF JavaScript API for this. The below example will apply on all buttons of type="submit".

function handleDisableButton(data) {
    if (data.source.type != "submit") {
        return;
    }

    switch (data.status) {
        case "begin":
            data.source.disabled = true;
            break;
        case "complete":
            data.source.disabled = false;
            break;
    }    
}

jsf.ajax.addOnEvent(handleDisableButton);

Alternatively, if you need this on specific buttons only, use the onevent attribute of <f:ajax>.

<h:commandButton ...>
    <f:ajax ... onevent="handleDisableButton" />
</h:commandButton>

If you also need to apply this on synchronous requests, then you need to take into account that when you disable a button during onclick, then the button's name=value pair won't be sent as request parameter and hence JSF won't be able to identify the action and invoke it. You should thus only disable it after the POST request has been sent by the browser. There is no DOM event handler for this, you'd need to use the setTimeout() hack which disables the button ~50ms after click.

<h:commandButton ... onclick="setTimeout('document.getElementById(\'' + this.id + '\').disabled=true;', 50);" />

This is only rather brittle. It might be too short on slow clients. You'd need to increase the timeout or head to another solution.

That said, keep in mind that this only prevents double submits when submitting by a web page. This does not prevent double submits by programmatic HTTP clients like URLConnection, Apache HttpClient, Jsoup, etc. If you want to enforce uniqueness in the data model, then you should not be preventing double submits, but preventing double inserts. This can in SQL easily be achieved by putting an UNIQUE constraint on the column(s) of interest.

See also:

Chameleon answered 25/5, 2012 at 14:56 Comment(1)
In the case of ajax submit, this still lets the original double-click go through twice. The control is disabled only after the double-click. The database constraint is tricky: how would you prevent a double payment where multiple payments are allowed?Meaningless
H
2

i came upon this question, having the same problem. The solution did not work for me - after a brief look at primefaces.js i guess they do not use jsf.ajax there anymore.

so i had to work something out myself and here is my solution, for people who also can not use the one in the answer by BalusC:

// we override the default send function of
// primeFaces here, so we can disable a button after a click
// and enable it again after     
var primeFacesOriginalSendFunction = PrimeFaces.ajax.AjaxUtils.send;

PrimeFaces.ajax.AjaxUtils.send = function(cfg){    
  var callSource = '';

  // if not string, the caller is a process - in this case we do not interfere
  if(typeof(cfg.source) == 'string') {
    callSource = jQuery('#' + cfg.source);
    callSource.attr('disabled', 'disabled');
  }

  // in each case call original send
  primeFacesOriginalSendFunction(cfg);

  // if we disabled the button - enable it again
  if(callSource != '') {
    callSource.attr('disabled', 'enabled');
  }
};
Hegemony answered 9/10, 2013 at 12:17 Comment(4)
The jquery bit has to be callSource = $(PrimeFaces .escapeClientId(cfg.source));Edmundedmunda
It's important the code doesn't run twice (otherwise you quickly run into performance problems or even crashes).Edmundedmunda
Starting with PrimeFaces 5, the API has changed. PrimeFaces.ajax.AjaxUtils.send has probably become PrimeFaces.ajax.Request.send.Edmundedmunda
This is no longer needed starting with PrimeFaces 12. It's done automatically.Martell
P
2

You can use 'onclick' and 'oncomplete' listeners. When user click on button - disable it. When action completed - enable.

<p:commandButton id="saveBtn"
                 onclick="$('#saveBtn').attr('disabled',true);"
                 oncomplete="$('#saveBtn').attr('disabled',false);"
                 actionListener="#{myBean.save}" />
Physicist answered 30/10, 2013 at 17:29 Comment(1)
This didn't work. I was still able to click the button several times whilst the action was processing.Victualler
A
1

None of alternatives above has worked for me (I've really tried each one of them). My form was always sent twice when user double-clicked the login button. I'm working with JSF (Mojarra 2.1.6) on Glassfish 3.1.2.

Consider that it was a non-AJAX login page.

So here's the way I solved it:

  1. define a global JavaScript var to control submition in the page header or anywhere outside your form:
var submitting = false;
  1. set it to true when submit h:form onsubmit event is fired:
<h:form onsubmit="submitting = true">
  1. Check the var's value on h:commandLink's click event:
<h:commandLink ... onclick="if(submitting){return false}">

This is just another simple alternative and it was tested in Chrome [Version 47.0.2526.106 (64-bit)], Mozilla Firefox (37.0.2) and Internet Explorer 11. I hope it helps someone.

Araliaceous answered 13/5, 2016 at 23:36 Comment(2)
Believe you have an issue with the back button- submit the form and then use the back button to go back to that page. The submitting variable value is likely "true".Marylynnmarylynne
Thank you so much, @BestPractices, but I didn't get your point. I tried all the other alternatives and mine was the only that works in my case. I'm not telling they are wrong, I'm just sharing my point in a try to contribute to the question.Araliaceous
C
1

For me works this way:

<h:commandLink ... onclick="jQuery(this).addClass('ui-state-disabled')">
Carbolated answered 21/3, 2019 at 15:18 Comment(2)
I tried this general idea and it worked well, using Primefaces commandButton onstart/oncomplete/onerror instead of onclick. A double-click does nothing, only a true single click makes it work. <p:commandButton id="createBtn" ajax="true" value="Submit" onstart="jQuery(this).addClass('ui-state-disabled')" oncomplete="jQuery(this).removeClass('ui-state-disabled')" onerror="jQuery(this).removeClass('ui-state-disabled')" process="topLevelContainer" update="topLevelContainer" actionListener="#{mybean.createAction}"/>Unstained
Update: This appears to work in Win10/Firefox, Win10/Chrome, Android/Firefox. It does not seem to work in Win10/Edge or Android/Chrome. I do not have access to iPhone/iPad to test. Probably upgrading to Primefaces 12+ is the best solution.Unstained
M
1

PrimeFaces 12 and up

From PrimeFaces 12, p:commandButtons are disabled by default when they trigger an Ajax request. The button is enabled again when the Ajax request is finished.

To disable this default behavior, use disableOnAjax="false".

See a demo at: https://www.primefaces.org/showcase/ui/button/commandButton.xhtml

PrimeFaces 11 and lower

The approach by BalusC is great, but if you are using PrimeFaces you'll run into styling issues. Because some classes are not toggled, the button will not look disabled.

If you are looking for a solution which takes care of styling as well, you can replace the CommandButtonRenderer with one that disables the button on click using the button's widget to disable and enable it.

PrimeFaces Extensions 8 or up contains such a renderer. You can add this to your faces-config.xml like:

<render-kit>
  <renderer>
    <component-family>org.primefaces.component</component-family>
    <renderer-type>org.primefaces.component.CommandButtonRenderer</renderer-type>
    <renderer-class>org.primefaces.extensions.renderer.CommandButtonSingleClickRenderer</renderer-class>
  </renderer>
</render-kit>

You can see it in action in the showcase.

If you cannot or don't want to use PFE, you can add the render class to your project by getting it from:

https://github.com/primefaces-extensions/primefaces-extensions/blob/master/core/src/main/java/org/primefaces/extensions/renderer/CommandButtonSingleClickRenderer.java

Note: this still requires you to add the renderer to your faces-config.xml.

See also

Martell answered 24/2, 2020 at 8:44 Comment(0)
W
0

very useful solution jsf-primefaces, used with facelets template spreads to other pages consumers

<f:view>
    <Script language="javascript">
        function checkKeyCode(evt)
        {
            var evt = (evt) ? evt : ((event) ? event : null);
            var node = (evt.target) ? evt.target : ((evt.srcElement) ? evt.srcElement : null);
            if(event.keyCode==116)
            {
                evt.keyCode=0;
                return false
            }
        }
        document.onkeydown=checkKeyCode;

        function handleDisableButton(data) {
            if (data.source.type != "submit") {
                return;
            }

            switch (data.status) {
                case "begin":
                    data.source.disabled = true;
                    break;
                case "complete":
                    data.source.disabled = false;
                    break;
            }    
        }
        jsf.ajax.addOnEvent(handleDisableButton);
    </Script>

</f:view>

<h:head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link href="./resources/css/default.css" rel="stylesheet" type="text/css" />
    <link href="./resources/css/cssLayout.css" rel="stylesheet" type="text/css" />
    <title>infoColegios - Bienvenido al Sistema de Administracion</title>
</h:head>

<h:body onload="#{login.validaDatos(e)}">
    <p:layout fullPage="true">

        <p:layoutUnit position="north" size="120" resizable="false" closable="false" collapsible="false">                   
            <p:graphicImage value="./resources/images/descarga.jpg" title="imagen"/> 
            <h:outputText value="InfoColegios - Bienvenido al Sistema de Administracion" style="font-size: large; color: #045491; font-weight: bold"></h:outputText>                    
        </p:layoutUnit>

        <p:layoutUnit position="west" size="175" header="Nuestra Institución" collapsible="true" effect="drop" effectSpeed="">
            <p:menu> 
                <p:submenu>
                    <p:menuitem value="Quienes Somos" url="http://www.primefaces.org/showcase-labs/ui/home.jsf" />

                </p:submenu>
            </p:menu>
        </p:layoutUnit>

        <p:layoutUnit position="center">
            <ui:insert name="content">Content</ui:insert>
        </p:layoutUnit>

    </p:layout>
</h:body>

Winkelman answered 4/1, 2013 at 0:25 Comment(0)
C
0

Did a simple work with hide and show, works well with element having input type submit Jquery

$(":submit").click(function (event) {
    // add exception to class skipDisable 
    if (!$(this).hasClass("skipDisable")) {
        $(this).hide();
        $(this).after("<input type='submit' value='"+$(this).val()+"' disabled='disabled'/>");
    }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<form>
    <input type="submit" value="hello">
</form>
Civilized answered 21/7, 2016 at 5:39 Comment(0)
O
0

I addressed this issue by simply hiding the button after clicking it:

<p:commandButton... onclick="jQuery(this).css('visibility','hidden')" />
Obla answered 15/6, 2022 at 13:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.