tags:

views:

602

answers:

3

I have a simple form that I don't want accidentally submitted multiple times.

I can easily block the user from multiple clicks when they first see the page, but I have no control over them hitting the back button.

So, having worked with Struts, I decided that a form submit token was the way to go.

Is there an easier way? Is this functionality already in Seam? If there isn't, how should I go about building this functionality into Seam?

// EDIT // Just a clarification here, I do NOT need something that will keep the user from double- clicking. That is already solved.

The specific use-case is as follows: The user clicks the button. Action runs. Some unspecified time in the future, the user hits the back button enough times to get back to the page with the button. The user clicks the button again.

How do I protect against that?

+2  A: 

To avoid double submits caused by impatiently pressing the submit button you could use a piece of Javascript which disables the submit button a few ms after onclick.

Example:

<h:commandButton 
    id="foo"
    value="submit"
    action="#{bean.submit}"
    onclick="setTimeout('document.getElementById(\'' + this.id + '\').disabled=true;', 50);"
/>

To avoid double submits by pressing the back button and ignoring the browser's warning that you may risk to resend the data, you need to implement the Post-Redirect-Get (PRG) pattern.

In JSF this can be done in basically 2 ways. Either using <redirect/> in <navigation-case>:

<navigation-case>
    <from-action>#{bean.submit}</from-action>
    <from-outcome>success</from-outcome>
    <to-view-id>/page.jsf</to-view-id>
    <redirect/>
</navigation-case>

or by invoking ExternalContext#redirect() in bean's action method:

public void submit() {
    doYourThing();
    FacesContext.getCurrentInstance().getExternalContext().redirect("page.jsf");
}

The only disadvantage is that the redirect implicitly creates a new request, hereby garbaging the initial request including all of its attributes (and thus also all request scoped managed beans and FacesMessages). In some cases it doesn't matter, but in other cases it surely would. I don't do Seam, but if I am correct, they have solved this problem with help of the so-called conversation scope and automatically retaining the FacesMessages through the redirect. You could take benefit of it.

BalusC
+2  A: 

With a4j/RichFaces, use the a4j:queue or assign a queue to the button. That way multiple clicks will get queued and only one will actually go through. The way to set it globally for your app if you're using RichFaces (we did this instead of setting up a queue everywhere), is to put the following in your web.xml:

<context-param>
 <param-name>org.richfaces.queue.global.enabled</param-name>
 <param-value>true</param-value>
</context-param>
Ashish Tonse
+3  A: 

Seam comes with the s:token component and it is what you are looking for: http://docs.jboss.org/seam/2.2.0.GA/reference/en-US/html/controls.html#d0e28933

kpolice