If you have a complex object with several fields that themselves might be complex objects it can be hard to figure out where to place the validation logic to ensure that the object at the point of the form submission is correct.

For example if you have an IModel<User> and then want to tie each property of the User object to a form field. But if you use a property model directly it can be hard to ensure that the field itself is valid. Sometimes you want to validate two fields of the object and using the PropertyModel or CompoundPropertyModel directly can cause problems.

By creating a custom component that extends FormComponentPanel<T> you can internalize the complexity of validating an object so that at the level where it is used the concerns can be specified in terms of IModel<User> instead of the interior objects that make up a User object.

UserEditPanel extends FormComponentPanel<User>

Given a User object of:

public class User implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private final String email;
	private final String name;
	private final String password;
	private final Date birth;

	public User(String email, String name,
            String password, Date birth) throws BusinessException {
				this.email = email;
				this.name = name;
				this.password = password;
				this.birth = birth;
}

	public static long getSerialversionuid() {
		return serialVersionUID;
	}

	public String getEmail() {
		return email;
	}

	public String getName() {
		return name;
	}

	public String getPassword() {
		return password;
	}

	public Date getBirth() {
		return birth;
	}

}

We want to validate each field individually.

For example:

public class UserEditPanel extends FormComponentPanel<User> {

    private TextField<String>emailField;

    ...
    /**
     * @param id
     */
    public UserEditPanel(String id, IModel<User>userModel) {
        super(id, userModel);
        
        emailField = new  TextField("emailField", new Model<String>(""));
        
        ...
        emailField.add(new EmailValidator());
    }

    @Override
    protected void convertInput() {
        
        /**
         * Build up a new User instance from the values in the fields.
         *
         */
        
        User u = new User(emailField.getModelObject(), ...);
        
        
        setConvertedInput(u);
        
        
    }

    /*
     * Here we pull out each field from the User if it exists and put the contents into the fields.
     */
    @Override
    protected void onBeforeRender() {
        
        User u = this.getModelObject();
        
        if (u != null) {
            // copy the field values into the form fields.
            this.emailField.setModelObject(u.getEmailAddress());
        }
    }


}   

We only implemented for one field but you can see the pattern; each field in the User object is mapped to a FormComponent in the UserEditPanel. We can validate each field individually. The models for the fields are not linked so changes only propagate into the real valid model via the UserEditPanel.convertInput() method.

Then only when all fields are valid will we convert the values on those sub-components into an instance of the User object. Note that after convertInput has been called UserEditPanel model object has not yet been updated. Only after any IValidator<User>'s on the UserEditPanel itself pass will the model object be set to the new value.

Using the UserEditPanel

By encapsulating the User object creation and validation logic within the Panel you can just use the form like this:


UserEditPanel userEditPanel = new UserEditPanel ("userPanel", new Model<User>());

add (form = new Form("form") {

@Override
            protected void onSubmit() {
               
// this is only called if there are no validation errors in the form fields
// the validation logic built into the UserEditPanel is implicity used and will prevent this method from being called
// if an error is detected.

                User u = userEditPanel.getModelObject();

                userService.save(u);               
            }
    });

form.add (userEditPanel);


The user of the UserEditPanel doesn't need to care about the internals of the component. If there is a problem the validation phase will fail and a feedback message generated. Only when the field details have been implemented correctly will the form.onSubmit() method be called and the userEditPanel.getModelObject() return the fully constructed User instance.

  • No labels