如何在JSF中实现双击预防

34

我们有几个搜索页面需要处理大量数据,需要一段时间才能完成。当用户点击搜索按钮时,我们希望不允许他们再次提交搜索结果。

在JSF中,是否有关于“双击”检测/预防的最佳实践?

PrimeFaces组件似乎可以做到我们想要的效果,因为它会在搜索按钮被点击和搜索完成之间的一段时间内禁用UI,但是是否有更通用的策略可供使用(可能不依赖于PrimeFaces)?理想情况下,任何按钮点击都将被禁用或忽略,直到搜索完成。我们不一定需要禁用整个UI(因为blockUI可以实现该功能)。

9个回答

42
如果您仅使用ajax请求,可以使用JSF JavaScript API的jsf.ajax.addOnEvent处理程序来实现此功能。下面的示例将应用于所有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);

如果您只需要在特定按钮上使用此功能,请使用<f:ajax>onevent属性。

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

如果您需要在同步请求中应用此功能,则需要注意,在onclick期间禁用按钮时,按钮的name=value对将不会作为请求参数发送,因此JSF将无法识别操作并调用它。因此,您应该在浏览器发送POST请求后才禁用它。这里没有DOM事件处理程序,您需要使用setTimeout()技巧,在单击后约50ms禁用按钮。
<h:commandButton ... onclick="setTimeout('document.getElementById(\'' + this.id + '\').disabled=true;', 50);" />

这种方法非常脆弱,对于慢速客户端来说可能过于短暂。您需要增加超时时间或寻找其他解决方案。

请注意,这只能防止通过网页提交的重复提交,无法防止通过编程HTTP客户端(如URLConnection,Apache HttpClient,Jsoup等)的重复提交。如果您想在数据模型中强制执行唯一性,则不应该防止重复提交,而应该防止重复插入。在SQL中,可以通过在感兴趣的列上放置UNIQUE约束来轻松实现此目标。

另请参阅:


在ajax提交的情况下,这仍然会让原始的双击事件发生两次。控件只有在双击后才被禁用。数据库约束是棘手的问题:如果允许多次支付,你如何防止重复支付? - Edward

2

您可以使用“onclick”和“oncomplete”侦听器。当用户点击按钮时,禁用它。当动作完成时 - 启用。

<p:commandButton id="saveBtn"
                 onclick="$('#saveBtn').attr('disabled',true);"
                 oncomplete="$('#saveBtn').attr('disabled',false);"
                 actionListener="#{myBean.save}" />

这个不起作用。在操作正在处理的时候,我仍然能够点击按钮多次。 - DD.

2
我遇到了同样的问题,看到这个问题后,我尝试了解决方案,但是并没有成功 - 在短暂地查看primefaces.js之后,我猜测他们不再在那里使用jsf.ajax了。
所以我必须自己想出一个解决方案,以下是我的解决方案,供那些无法使用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');
  }
};

jquery 部分必须调用 callSource = $(PrimeFaces.escapeClientId(cfg.source)); - Stephan Rauh
重要的是代码不要运行两次(否则您很快就会遇到性能问题甚至崩溃)。 - Stephan Rauh
2
从PrimeFaces 5开始,API已经发生了变化。PrimeFaces.ajax.AjaxUtils.send可能已经变成了PrimeFaces.ajax.Request.send。 - Stephan Rauh
从PrimeFaces 12开始,这不再需要。它会自动完成。 - Jasper de Vries

1

PrimeFaces 12及以上版本

从PrimeFaces 12开始,当p:commandButtons触发Ajax请求时,默认情况下它们会被禁用。当Ajax请求完成后,按钮将重新启用。

要禁用此默认行为,请使用disableOnAjax="false"

在此处查看演示:https://www.primefaces.org/showcase/ui/button/commandButton.xhtml

PrimeFaces 11及更低版本

BalusC的方法非常好,但如果您正在使用PrimeFaces,则可能会遇到样式问题。因为某些类没有切换,所以按钮看起来不会被禁用。

如果您正在寻找一个同时处理样式的解决方案,可以将CommandButtonRenderer替换为一个使用按钮的小部件来禁用和启用它的渲染器。

PrimeFaces Extensions 8或更高版本包含这样的渲染器。您可以像这样将其添加到您的faces-config.xml中:

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

你可以在展示页面中看到其使用情况
如果你不想或无法使用PFE,你可以通过获取渲染类并将其添加到项目中来实现:

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

注意:这仍然需要您将渲染器添加到faces-config.xml中。
另请参阅:
- 在PrimeFaces 8之前如何使用resolveWidgetVar?

1

以上任何一种方法都没有为我解决问题(我真的尝试了它们中的每一个)。当用户双击登录按钮时,我的表单总是被发送两次。我正在使用JSF(Mojarra 2.1.6)和Glassfish 3.1.2。

请注意,这是一个非AJAX登录页面。

因此,这是我解决它的方法:

  1. 在页面头部或表单外部的任何位置定义一个全局JavaScript变量来控制提交:
var submitting = false;
  1. 当提交 h:form 表单的 onsubmit 事件被触发时,请将其设置为 true
<h:form onsubmit="submitting = true">
  1. 在h:commandLink的点击事件中检查变量的值:
<h:commandLink ... onclick="if(submitting){return false}">

这只是另一种简单的替代方案,已在Chrome [版本47.0.2526.106(64位)]、Mozilla Firefox(37.0.2)和Internet Explorer 11中进行了测试。希望能帮助到某些人。

相信您在使用后退按钮时遇到了问题- 提交表单,然后使用后退按钮返回到该页面。提交的变量值可能是“true”。 - BestPractices
非常感谢您,@BestPractices,但我没有理解您的观点。我尝试了所有其他的替代方案,而我的是唯一一个在我的情况下有效的。我并不是说他们是错的,我只是想分享我的看法,试图为这个问题做出贡献。 - Jaumzera

1
对我来说,它是这样工作的:

<h:commandLink ... onclick="jQuery(this).addClass('ui-state-disabled')">

0

使用隐藏和显示完成了一个简单的工作,在具有输入类型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>

0
我通过点击按钮后简单地隐藏按钮来解决了这个问题:
<p:commandButton... onclick="jQuery(this).css('visibility','hidden')" />

0
非常有用的解决方案jsf-primefaces,与facelets模板一起使用可以扩展到其他页面的消费者。
<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>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接