Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing arbitrary number of fields in a Wicket form validator

I need to validate something about several Wicket input fields of type TextField<BigDecimal> (namely that the sum of percentages is 100). There are one to many such input fields; thing is, I don't know in advance how many.

(simplified example)

private class PercentageValidator extends AbstractFormValidator {
    @Override
    public FormComponent<?>[] getDependentFormComponents() {
        // ...
    }

    @Override
    public void validate(Form<?> form) {
        List<TextField<BigDecimal>> fields = // TODO

        // the actual validation where the value of every field is needed
    }
}

Java code for the ListView:

ListView<?> listView = new ListView<PropertyShare>("shares", shares) {
    @Override
    protected void populateItem(ListItem<PropertyShare> item) {
    // ... 
        item.add(new TextField<BigDecimal>("share", ... model ...));
    }
};

HTML:

<tr wicket:id="shares">
   <td> ... </td>
   <td>
     <input wicket:id="share" type="text" size="4"> %
   </td>

</tr>

I tried keeping every TextField in a collection on the Page, but this approach fails as the populateItem() method of the enclosing ListView gets called not only the the Page is first created, so duplicate fields get added to the collection. (I couldn't figure out an easy way to keep it duplicate-free.)

The fact that ListView is used also seems to somewhat complicate finding the fields from the form object in the validate() method. I suppose I need to get the ListView with form.get("shares") and iterate through its children?

What's the "right way" to access any number of fields enclosed by a repeater such as ListView?

like image 352
Jonik Avatar asked Dec 07 '25 07:12

Jonik


2 Answers

An alternative approach would be to subclass TextField and then use a Visitor to pick out all the descendant components of your subclass.

This way you can avoid unchecked casting and you don't have to rely on the ids, which isn't a very robust approach.

Edit: in practice, it would look something like this:

The subclass:

private static class ShareField extends TextField<BigDecimal> {
   // ...
}

Helper method that finds all ShareFields from the form:

private List<ShareField> findShareFields(Form form) {
    final List<ShareField> fields = Lists.newArrayList();
    form.visitChildren(ShareField.class, new IVisitor<ShareField>() {
        @Override
        public Object component(ShareField component) {
            fields.add(component);
            return CONTINUE_TRAVERSAL;
        }
    });
    return fields;
}
like image 185
biziclop Avatar answered Dec 08 '25 21:12

biziclop


Right, while writing the question, it dawned on me that simply looping through the children of form.get("shares") and getting the field with id "share" would probably work.

It indeed does. Here's a helper method that finds the "share" fields:

@SuppressWarnings("unchecked")
private List<TextField<BigDecimal>> findFields(Form form) {
    List<TextField<BigDecimal>> fields = Lists.newArrayList();
    MarkupContainer container = (MarkupContainer) form.get("shares");
    for (Iterator<? extends Component> it = container.iterator(); it.hasNext();) {
        MarkupContainer c = (MarkupContainer) it.next();
        fields.add((TextField<BigDecimal>) c.get("share"));
    }
    return fields;
}

However, there are three somewhat ugly casts in the above method, and one of those (Component -> TextField<BigDecimal>) produces an "unchecked cast" warning.

If you can clean up this solution, or know of better approaches, feel free to comment or post other answers!

like image 40
Jonik Avatar answered Dec 08 '25 19:12

Jonik