The problem
Regular Wicket ajax buttons and links have no protection built-in for duplicate clicks. When a user clicks multiple times in succession, that will invoke the submit handler multiple times, causing all types of havoc with your code!
We can observe this problem by creating a simple AjaxButton:
AjaxButton badBtn = new AjaxButton("badBtn") {
@Override
protected void onSubmit(AjaxRequestTarget target) {
Thread.sleep(800); //simulate some process
System.out.println(System.nanoTime());
}
};
We use Thread.sleep here to simulate some logic that is being executed when the user clicks this button. In this example, it will be quite harmless. But imagine this onSubmit handler will be inserting rows into a database, deleting data, or processing a credit card!
The goal is to create a new component that will automatically provide protections against this.
Using SingleClickAjaxButton
Let’s create a new component that will extend IndicatingAjaxButton. It will use javascript to disable itself upon being clicked, and re-enable itself once it is done doing it’s work.
public abstract class SingleClickAjaxButton extends IndicatingAjaxButton {
private final boolean enableButtonAfterSubmit;
public SingleClickAjaxButton(String id, Form<?> form, boolean enableButtonAfterSubmit) {
super(id, form);
this.enableButtonAfterSubmit = enableButtonAfterSubmit;
}
@Override
protected void onError(AjaxRequestTarget target) {
//on error, just re-enable
enableButton(target);
}
@Override
protected void onAfterSubmit(AjaxRequestTarget target) {
if(enableButtonAfterSubmit) {
enableButton(target);
}
}
protected void enableButton(AjaxRequestTarget target) {
target.appendJavaScript("$('#"+this.getMarkupId()+"').prop('disabled', false);");
}
@Override
protected String getOnClickScript() {
//if the button should be re-enabled but hasnt due to some error, automatically re-enable it after 10 seconds
if(enableButtonAfterSubmit) {
return "$('#"+this.getMarkupId()+"').prop('disabled', true); setTimeout(function() { $('#"+this.getMarkupId()+"').prop('disabled', false); }, 10000);";
}
return null;
}
}
It takes as an argument, enableButtonAfterSubmit, which allows you to configure the type of button you need. You should pass true for multi-click buttons, such as a save button, which is intended to be used multiple times, just as long as the next save doesn’t come before the previous one has finished.
You should pass false if you intend the button to be single-click, such as when you are processing a credit card payment. The submit handler will redirect the browser to a different page when complete, so the button should never be re-enabled. Note: This is just a simple example, for credit card payments you should not only rely on Javascript protections!
Using SingleClickAjaxbutton
To use the new component is very simple, we can re-implement our “badBtn” from above, with the only change being the class name:
SingleClickAjaxButton goodBtn = new SingleClickAjaxButton("goodBtn") {
@Override
protected void onSubmit(AjaxRequestTarget target) {
Thread.sleep(800); //simulate some process
System.out.println(System.nanoTime());
}
};
You can check out the full source code below!