Triggered by the StackOverflow Question Wicket : Notify if page model has been changed., here is a wicket behavior that you can attach to a form and that will stop the user from leaving the site once he has changed some values of the form.
Here is the Javascript file (it requires that either Prototype or MooTools are available):
// initialize namespace
if(!window.wicket)window.wicket={};
if(!wicket.behaviors)wicket.behaviors={};
if(!wicket.behaviors.guardform)wicket.behaviors.guardform={};
// check whether prototype or mootools are installed
wicket.behaviors.guardform.prototypeinstalled = window.Prototype && window.Prototype.version;
wicket.behaviors.guardform.mootoolsinstalled = window.MooTools && window.MooTools.version;
if(wicket.behaviors.guardform.mootoolsinstalled && !window.Selectors){
throw "MooTools must contain the Selectors api to use this behavior";
}
wicket.behaviors.guardform.dirty = false;
// default prompt, can be adjusted from the wicket behavior
wicket.behaviors.guardform.prompt = "You have changed values in the form.\n"
+ "Do you really want to edit and lose your changes?";
wicket.behaviors.guardform.attach=function(elem,event,handler){
if(wicket.behaviors.guardform.mootoolsinstalled){
elem.addEvent(event,handler);
}else if(wicket.behaviors.guardform.prototypeinstalled){
elem.observe(event,handler);
}
throw "Prototype or MooTools must be present to use this behavior";
}
wicket.behaviors.guardform.select=function(elem,args){
if(wicket.behaviors.guardform.mootoolsinstalled){
return elem.getElements(args);
}else if(wicket.behaviors.guardform.prototypeinstalled){
return elem.select(args);
throw "Prototype or MooTools must be present to use this behavior";
}
wicket.behaviors.guardform.parent=function(elem,selector){
if(wicket.behaviors.guardform.mootoolsinstalled){
return elem.getParent(args);
}else if(wicket.behaviors.guardform.prototypeinstalled){
return elem.up(args);
throw "Prototype or MooTools must be present to use this behavior";
}
wicket.behaviors.guardform.init=function(id){
var obj = $(id);
if(obj){
var first = true;
// attach onchange behavior for all child text components
wicket.behaviors.guardform.select(
obj,
"input[type=text], " // text fields
"textarea, " // text areas
"select" // select controls
).each(function(item){
if(first){
first=false;
}
wicket.behaviors.guardform.attach(item, "change", function(){
wicket.behaviors.guardform.dirty = true;
});
});
// attach window unload handler that will fire a prompt if form is dirty
wicket.behaviors.guardform.attach(window, "beforeunload", function(){
return (!wicket.behaviors.guardform.dirty ||
confirm(wicket.behaviors.guardform.prompt);
});
// now attach an 'undirty' function to the form
wicket.behaviors.guardform.attach(
obj,
'submit',
function(){
wicket.behaviors.guardform.dirty = false;
}
);
}
}
Then you need the actual wicket behavior, that you attach to the wicket form component (This version can only be attached to root forms, not nested forms, as they are internally remodeled to <div> tags). Here’s the Behavior class:
public class GuardDirtyFormBehavior extends AbstractBehavior{
private Component component;
private IModel promptModel = null;
public GuardDirtyFormBehavior setPromptModel(final IModel promptModel){
this.promptModel = promptModel;
return this;
}
@Override
public void bind(final Component component){
if(!(component instanceof Form)){
throw new WicketRuntimeException("Behavior must be attached to a form");
}
// if the model needs component wrapping, wrap it here
if(promptModel instanceof IComponentAssignedModel<?>){
promptModel=((IComponentAssignedModel<String>)promptModel).wrapOnAssignment(component);
}
this.component = component.setOutputMarkupId(true);
}
@Override
public void onComponentTag(final Component component, final ComponentTag tag){
if(!"form".equalsIgnoreCase(tag.getName())){
throw new WicketRuntimeException("Behavior must be attached to a root form, not a nested form.");
}
}
@Override
public void renderHead(final IHeaderResponse response){
response.renderJavascriptReference(new JavascriptResourceReference("GuardDirtyFormBehavior.js"));
if(this.promptModel != null){
final String prompt = this.promptModel.getObject();
response.renderJavascript("wicket.behaviors.guardform.prompt='"
+ prompt + "';", "wicket.behaviors.guardform.prompt");
}
response.renderOnDomReadyJavascript("wicket.behaviors.guardform.init('"
+ this.component.getMarkupId() + "');");
}
}
You can assign it like this:
public class Mypage extends WebPage{
public Mypage(){
add(
new Form<Object>("form")
.add(
new TextField<String>("textField1"),
new TextField<String>("textField2"),
new TextField<String>("textField3"),
new TextField<String>("textField4")
)
.add(new GuardDirtyFormBehavior())
);
}
}
You can also change the javascript prompt message using the wicket i18n mechanism by using setPromptModel together with a ResourceModel:
.add(new GuardDirtyFormBehavior()
.setPromptModel(new ResourceModel("form.dirty.prompt"))
)
This will use the wicket resource loading mechanism to look up a localized version of the resource key ‘form.dirty.prompt’.
