Wicket and focus

Our first approach was to create a DefaultFocusBehavior (as described by Julian Sinai) and attach it to the component which should receive the focus. The problem with this is that after a validation error during a submit, you usually want the focus to jump to the first invalid field, so the user can easily correct his erroneous input. The problem with the DefaultFocusBehavior is that the component to which you added it still has it and will try to grab the focus again. We created two methods for handling focus in our BasePage class (from which all of our application pages are inherited) – setFocus and setFocusToFirstInvalidComponent. The first one is called during page initialization and the second on validation failure. They share enough class variables which allow them to ensure that only one component has the DefaultFocusBehavior at a time.

Wicket component strings

Wicket components have a convenient getString method to load a (translatable string from a properties file associated with the component (or any component in its hierarchy). It also allows you to provide parameters to those strings in the form “${count} new messages” where count is looked up as a property of the model you pass to getString.

Sometimes, however, I’d prefer to just have simple numbered parameters and provide them as varargs to getString(). So, in my properties file, I’d have “${0} new messages” and I’d call “getString(id, 15)” to get “15 new messages”. To do this, you need to add a method something like this:

String getStringWithParameters(String id, Object... args) {
 return getString(id, new Model(args));
}

This method takes your varargs and creates an object array which the standard getString function can use to look up your parameters by index.

Using the HTML TBODY tag as a container for table rows

A common issue which Wicket developers face is the need for an AJAX update of the HTML output of a repeater (such as a ListView) which is part of a HTML table (where the repeating element is represented by a single <tr wicket:id=”mylist”> line in the table). The problem is that you can’t just use “target.addComponent(mylist)” – you need some kind of container around the repeater. Using a nested table or div as a container may interfere with your table layout.

The solution is to use a <tbody wicket:id=”mylistcontainer”> tag around the <tr> which you can add to your page with a WebMarkupContainer and then pass to target.addComponent. It provides you with a container around one or more table rows which you can address from Wicket without messing up your table layout.

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