Form fields within a Wicket TabbedPanel

Here’s something which took me several days to figure out. If you have form fields within a TabbedPanel (or AjaxTabbedPanel), how do you ensure that they get validated and submitted correctly when the user switches tabs . The TabbedPanel unloads panels as the user switches tabs so only the currently selected tab gets submitted. One suggestion (from Julian Sinai) was to use AjaxFormValidatingBehavior as follows:

AjaxFormValidatingBehavior.addToAllFormComponents(form, “onblur”);

This ensures that form fields get submitted every time they lose the focus (which also happens when the user switches tabs). This however still leaves us with the problem that the tab switch has already occurred before you have a chance to react to validation errors.

The solution I eventually found was to prevent the user switching tabs until any form fields within the selected tab validate correctly.

I did this by overloading the newLink method of the TabbedPanel and returning an AjaxSubmitLink instead of the standard AjaxFallbackLink (wouldn’t it make sense to make AjaxSubmitLink the default if the Panel contains form fields?). Since the AjaxSubmitLink needs a form, I needed to additionally extend the AbstractTab used by the TabbedPanel to query its panel for a form.

Here’s the overload of the newLink method:

tabPanel = new TabbedPanel("tabs", tabs) {
	private static final long serialVersionUID = 1L;

	@Override
	protected WebMarkupContainer newLink(String linkId, final int index) {
		Form form = ((AbstractTabWithForm) tabs.get(getSelectedTab())).getForm();

		if (form != null) {
			return new AjaxSubmitLink(linkId, form) {
				private static final long serialVersionUID = 1L;

				@Override
				protected void onError(AjaxRequestTarget target, Form form) {
					super.onError(target, form);
					target.addComponent(tabPanel);
				}

				@Override
				protected void onSubmit(AjaxRequestTarget target, Form form) {
					setSelectedTab(index);
					if (target != null) {
						target.addComponent(tabPanel);
					}
				}

			};
		} else {
			return super.newLink(linkId, index);
		}
	}
};
tabPanel.setOutputMarkupId(true);
add(tabPanel);

Here’s the subclass of the AbstractTab to provide access to the form:

abstract class AbstractTabWithForm extends AbstractTab {
	private static final long serialVersionUID = 1L;

	public AbstractTabWithForm(IModel title) {
		super(title);
	}

	// override this if you have a form
	public Form getForm() {
		return null;
	}
}

Here’s how the tabs are added:

tabs.add(new AbstractTabWithForm(new Model("General")) {
	private static final long serialVersionUID = 1L;
	private BasePanel panel = null;

	@Override
	public Panel getPanel(String panelId) {
		try {
			if (panel == null)
				panel = new GeneralPanel(panelId, item);
		} catch (Exception e) {
			error(e.getMessage());
		}
		return panel;
	}

	@Override
	public Form getForm() {
		return panel.getForm();
	}
});

And here’s how it looks in practice – in the example below, the user is attempting to switch tabs before all required fields have been filled out in the current tab.

The user attempts to switch tabs before all required fields are filled

One thought on “Form fields within a Wicket TabbedPanel”

Leave a Reply