Sunday, March 31, 2013

Advanced Wicket: Dynamic ListView within a Form

With ListView, Wicket provides a nice and easy way to create dynamic lists with no problem to add, remove or shift items during runtime. When using a ListView to list input fields in a form however, things can get strange...here's a solution.

The Requirement

You want to create a form that contains multiple input fields. The user shall be able to add input fields or remove previously added  input fields.

Problem #1

By default the ListItems within a ListView are removed, newly created and added back to the ListView each time the ListView is rendered. If you add a new ListItem with a new input fields to the ListView, the user input in these fields will be lost when the page is re-rendered.

Solution #1

A solution to this problem is quickly found: just call listView.setReuseItems(true). This tells the ListView not to create new ListItems each time it is rendered. However, this leads to Problem #2.

Problem #2

Each ListView is backed by a model that contains a list of items that are to be displayed. If a new item is added to this list and setReuseItems is set to true, the ListView will wrap only this new item in a new ListItem and add the ListItem to the ListView the before it is rendered the next time (see ListView.onPopulate()). Since ListView works on the index of the backing model list, each time you insert a new item in the middle of this list, ListView will think you added it at the last position of the list. The result ist that each time you add a new item to the model list (no matter where you put it), the last item of the list is added to the ListView. When the ListView is rendered this can lead to duplicated rendering of the last item.

Solution # 2

Always (!) add items at the end of the model list that backs a ListView. This way, ListView will add the correct ListItem before being rendered again. If the new item is to be displayed in the middle of the list, override the ListView's renderIterator() method. Here you can create an iterator that sorts the items according to their model values. Here is an example from my Wicked Forms library:
@Override
protected Iterator<Component> renderIterator() {
 return iterator(new Comparator<Component>() {
  @Override
  @SuppressWarnings("unchecked")
  public int compare(Component o1, Component o2) {
   ListItem<Abstractformelementmodel> item1 = (ListItem<Abstractformelementmodel>) o1;
   AbstractFormElementModel model1 = item1.getModelObject();
   ListItem<Abstractformelementmodel> item2 = (ListItem<Abstractformelementmodel>) o2;
   AbstractFormElementModel model2 = item2.getModelObject();
   return model1.getIndex().compareTo(model2.getIndex());
  }
 });
};

Wicked Forms - Dynamic Forms Library

Before building your own dynamic form have a look at Wicked Forms. With Wicked Forms you can "describe" your form in plain java and let Wicket render it without having to hassle with details as described above. 
Post a Comment