There are many times in writing our Wicket applications that we want to render feedback messages close to the component that registered the message - especially in forms.  But, we also typically want to display all the other messages in one “catch-all” feedback panel near the top of the page.  Sometimes this can be difficult to do, especially if your form component feedback panels are added by borders, etc.  Here is one implementation that will allow you to have a single “catch-all” feedback panel on any page.

Every feedback panel can take a feedback message filter, so let’s start by showing a couple of feedback panels added to individual form components:

// create the form above
form.add(new FeedbackPanel("feedback", new ComponentFeedbackMessageFilter(form)));

final TextField name = new TextField("name", new PropertyModel(productModel, "name"));
name.setRequired(true);
form.add(new FeedbackPanel("nameFeedback", new ComponentFeedbackMessageFilter(name)));
form.add(name);

final TextField price = new TextField("price", new PropertyModel(productModel, "price"));
price.setRequired(true);
price.add(new MinimumValidator(0d));
form.add(new FeedbackPanel("priceFeedback", new ComponentFeedbackMessageFilter(price)));
form.add(price);

From this, you can see that we have a form, a name field, and a price field. Each has their own FeedbackPanel that shows the messages on just that component. So, the validators for the price field will register their messages on the price field, and the FeedbackPanel next to it will display those messages. If you call error(String) or info(String) on the form itself (for example, in onSubmit), those messages will show up in the form’s feedback panel. But, now how do we show all the other feedback messages that might otherwise be left unrendered? For instance, what if someone registers a feedback message directly on the session?

It’s not very difficult. We start by creating a generic feedback message filter that does the opposite of what a list of other filters does:

public class AllExceptFeedbackFilter implements IFeedbackMessageFilter {
	private static final long serialVersionUID = 1L;

	private IFeedbackMessageFilter[] filters = null;

	public AllExceptFeedbackFilter() {
		this(null);
	}

	public AllExceptFeedbackFilter(IFeedbackMessageFilter[] filters) {
		this.filters = filters;
	}

	@Override
	public boolean accept(FeedbackMessage message) {
		IFeedbackMessageFilter[] localFilters = getFilters();
		for (IFeedbackMessageFilter filter : localFilters) {
			if (filter.accept(message)) {
				return false;
			}
		}
		return true;
	}

	protected IFeedbackMessageFilter[] getFilters() {
		return filters;
	}

}

You can see that if any of the filters returned by getFilters accepts a message, our feedback filter will not accept it. It only accepts ones that are not accepted by any other filter. Now, if we use this in a feedback panel that auto-discovers all other feedback panels, we can combine that to have a “catch-all” feedback panel. Here’s how:

final FeedbackPanel pageFeedback = new FeedbackPanel("pageFeedback");

pageFeedback.setFilter(new AllExceptFeedbackFilter() {
	private static final long serialVersionUID = 1L;

	@Override
	protected IFeedbackMessageFilter[] getFilters() {
		final List filters = new ArrayList();
		getPage().visitChildren(FeedbackPanel.class, new IVisitor() {
			@Override
			public Object component(FeedbackPanel component) {
				if (pageFeedback.equals(component)) {
					return CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER;
				}
				filters.add(component.getFilter());
				return CONTINUE_TRAVERSAL;
			}
		});
		return filters.toArray(new IFeedbackMessageFilter[filters.size()]);
	}
});

add(pageFeedback);

As you can see, in our getFilters method, we traverse the component hierarchy and gather all of the filters for all other feedback panels on the page except ourselves. This way, our AllExceptFeedbackFilter will accept any message that isn’t accepted by some other filter on the page. You could turn this into a concrete class (rather than an anonymous inner class) and use it in all of your applications. One thing worth noting is that you can only have a single one of these on the page or else it won’t work - only one of them would show the messages.

Neat, huh?