25

My code:

<h:form id="newBSTypePanel" >
    <h:panelGrid columns="2" id="newRecod" >
        <h:outputText value="Name"/><h:inputText value="#{treeTableController.newBStypeBean.currentObject.TYPENAME.value}" required="true" />
        <p:commandButton value="save" action="#{treeTableController.saveNewNodes}" oncomplete="Dlg.hide()" update="productDataForm"/>
        <p:commandButton value="close" oncomplete="Dlg.hide()" />
    </h:panelGrid>
</h:form>

There is quite a bit of functionality associated with the save action. If I click the button repeatedly, it may save a few records in the database. That's not my wish. How can I prevent multiple clicks and resolve this?

Zach Lysobey
  • 14,959
  • 20
  • 95
  • 149
leo173
  • 303
  • 2
  • 5
  • 13
  • Does this answer your question? [How to do double-click prevention in JSF](https://stackoverflow.com/questions/10756426/how-to-do-double-click-prevention-in-jsf) – Jasper de Vries Aug 20 '21 at 09:49

4 Answers4

41

The <p:commandButton>'s Client Side API Widget:

  • PrimeFaces.widget.CommandButton

  • Method Params Return Type Description

  • disable() - void Disables button

  • enable() - void Enables button

So you can just use like this:

<p:commandButton widgetVar="saveButton"
                 onclick="saveButton.disable()"
                 value="save"
                 action="#{treeTableController.saveNewNodes}" 
                 oncomplete="saveButton.enable();Dlg.hide()"
                 update="productDataForm"/>
Jasper de Vries
  • 19,370
  • 6
  • 64
  • 102
FishGel
  • 1,100
  • 2
  • 16
  • 27
  • Yes,It really works! Thanks! Before known your answer, I add sth like this** ** I think these codes may utilize the function of ajax which append with **p:commandButton** – leo173 Jun 28 '11 at 00:10
  • Not at all.Yeah, your conjecture is very convincing .hehe – FishGel Jun 28 '11 at 01:36
  • 12
    For newer versions of primefaces (lost a few minutes for that): ` – esmin Sep 03 '15 at 14:12
11

For the newer versions of PrimeFaces, the solution would be:

 <p:commandButton widgetVar="saveButton"
                 onclick="PF('saveButton').disable()"
                 value="save"
                 action="#{treeTableController.saveNewNodes}" 
                 oncomplete="PF('saveButton').enable();PF('Dlg').hide()"
                 update="productDataForm"/>
Jasper de Vries
  • 19,370
  • 6
  • 64
  • 102
julianfperez
  • 1,726
  • 5
  • 38
  • 69
3

As a generic approach you could customize the button renderer, so you can automatically apply a fix to all (eligible) buttons in your application.

I use this renderer for a PrimeFaces p:commandButton:

public class CommandButtonSingleClickRenderer extends CommandButtonRenderer {

    @Override
    protected void encodeMarkup(FacesContext context, CommandButton button) throws IOException {
        if (isEligible(button)) {
            final String widgetVar = button.resolveWidgetVar(context);
            final String onClick = getAttributeValue(context, button, "onclick");
            final String onComplete = getAttributeValue(context, button, "oncomplete");
            button.setOnclick(prefix(onClick, getToggleJS(widgetVar, false)));
            button.setOncomplete(prefix(onComplete, getToggleJS(widgetVar, true)));
        }
        super.encodeMarkup(context, button);
    }

    protected boolean isEligible(final CommandButton button) {
        final ActionListener[] listeners = button.getActionListeners();
        return button.isAjax()
                    && button.isRendered()
                    && (button.getActionExpression() != null || (listeners != null && listeners.length > 0))
                    && !button.isDisabled()
                    && !isConfirmation(button);
    }

    protected boolean isConfirmation(final CommandButton button) {
        final String styleClass = button.getStyleClass();
        return styleClass != null && styleClass.contains("ui-confirm");
    }

    protected String getToggleJS(final String widgetVar, final boolean enabled) {
        return String.format("var w=PrimeFaces.widgets['%s'];if(w){w.%sable();};", widgetVar, enabled ? "en" : "dis");
    }

    protected String getAttributeValue(final FacesContext context, final CommandButton button, final String attribute) {
        final ValueExpression ve = button.getValueExpression(attribute);
        if (ve != null) {
            return (String) ve.getValue(context.getELContext());
        }
        String key = attribute + "CommandButtonSingleClickRenderer";
        String value = (String) button.getAttributes().get(key);
        if (value == null) {
            value = (String) button.getAttributes().get(attribute);
            button.getAttributes().put(key, value == null ? Constants.EMPTY_STRING : value);
        }
        return value;
    }

    protected String prefix(final String base, final String prefix) {
        return base == null ? prefix : prefix + base;
    }

}

faces-config.xml:

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

This renderer was added to PrimeFaces Extensions 8.0. If you are using PFE, you can simply add this renderer to your faces-config.xml render-kit section:

<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>

See https://www.primefaces.org/showcase-ext/sections/renderers/commandButtonSingleClick.jsf

Jasper de Vries
  • 19,370
  • 6
  • 64
  • 102
3

Use Javascript and Timer

<script>

function disableClick(){
   document.getElementById('saveButton').disables = true;
   setTimeout('document.getElementById(\'saveButton\').disables = false', 5000)"
}
</script>


 <h:form id="newBSTypePanel" >
    <h:panelGrid columns="2" id="newRecod" >
        <h:outputText value="Name"/><h:inputText value="#{treeTableController.newBStypeBean.currentObject.TYPENAME.value}" required="true" />
        <p:commandButton value="save" action="#{treeTableController.saveNewNodes}" oncomplete="Dlg.hide()" onclick="disableClick()" id="saveButton" update="productDataForm"/>
        <p:commandButton value="close" oncomplete="Dlg.hide()" />
    </h:panelGrid>
</h:form>
Dejell
  • 13,947
  • 40
  • 146
  • 229