Wicket behavior to guard changed form.

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

About these ads
This entry was posted in Java, Wicket. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s