13. Displaying multiple items with repeaters

A common task for web applications is to display a set of items. The most typical scenario where we need such kind of visualization is when we have to display some kind of search result. With the old template-based technologies (like JSP) we used to accomplish this task using classic for or while loops:

  1. <html>
  2. <head>
  3. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  4. <title>Insert title here</title>
  5. </head>
  6. <body>
  7. <%
  8. for(int i = 12; i<=32; i++) {
  9. %>
  10. <div>Hello! I'm index n°<%= %></div>
  11. <%
  12. }
  13. %>
  14. </body>

To ease this task Wicket provides a number of special-purpose components called repeaters which are designed to use their related markup to display the items of a given set in a more natural and less chaotic way.

In this chapter we will see some of the built-in repeaters that come with Wicket.

13.1. The RepeatingView Component

Component org.apache.wicket.markup.repeater.RepeatingView is a container which renders its children components using the tag it is bound to. It can contain an arbitrary number of children elements and we can obtain a new valid id for a new child calling its method newChildId(). This component is particularly suited when we have to repeat a simple markup fragment, for example when we want to display some items as a HTML list:

HTML:

  1. <ul>
  2. <li wicket:id="listItems"></li>
  3. </ul>

Java Code:

  1. RepeatingView listItems = new RepeatingView("listItems");
  2. listItems.add(new Label(listItems.newChildId(), "green"));
  3. listItems.add(new Label(listItems.newChildId(), "blue"));
  4. listItems.add(new Label(listItems.newChildId(), "red"));

Generated markup:

  1. <ul>
  2. <li>green</li>
  3. <li>blue</li>
  4. <li>red</li>
  5. </ul>

As we can see in this example, each child component has been rendered using the parent markup as if it was its own.

13.2. The ListView Component

As its name suggests, component org.apache.wicket.markup.html.list.ListView is designed to display a given list of objects which can be provided as a standard Java List or as a model containing the concrete List. ListView iterates over the list and creates a child component of type org.apache.wicket.markup.html.list.ListItem for every encountered item.

Unlike RepeatingView this component is intended to be used with complex markup fragments containing nested components.

To generate its children, ListView calls its abstract method populateItem(ListItem item) for each item in the list, so we must provide an implementation of this method to tell the component how to create its children components. In the following example we use a ListView to display a list of Person objects:

HTML:

  1. ...
  2. <body>
  3. <div id="bd" style="display: table;">
  4. <div wicket:id="persons" style="display: table-row;">
  5. <div style="display: table-cell;"><b>Full name: </b></div>
  6. <div wicket:id="fullName" style="display: table-cell;"></div>
  7. </div>
  8. </div>
  9. </body>
  10. ...

Java Code (Page Constructor):

  1. public HomePage(final PageParameters parameters) {
  2. List<Person> persons = Arrays.asList(new Person("John", "Smith"),
  3. new Person("Dan", "Wong"));
  4. add(new ListView<Person>("persons", persons) {
  5. @Override
  6. protected void populateItem(ListItem<Person> item) {
  7. item.add(new Label("fullName", new PropertyModel(item.getModel(), "fullName")));
  8. }
  9. });
  10. }

Screenshot of generated page:

simple listview screenshot

In this example we have displayed the full name of two Person’s instances. The most interesting part of the code is the implementation of method populateItem where parameter item is the current child component created by ListView and its model contains the corresponding element of the list. Please note that inside populateItem we must add nested components to the item object and not directly to the ListView.

13.2.1. ListView and Form

By default ListView replaces its children components with new instances every time is rendered. Unfortunately this behavior is a problem if ListView is inside a form and it contains form components. The problem is caused by the fact that children components are replaced by new ones before form is rendered, hence they can’t keep their input value if validation fails and, furthermore, their feedback messages can not be displayed.

To avoid this kind of problem we can force ListView to reuse its children components using its method setReuseItems and passing true as parameter. If for any reason we need to refresh children components after we have invoked setReuseItems(true), we can use MarkupContainer’s method removeAll() to force ListView to rebuild them.

13.3. The RefreshingView Component

Component org.apache.wicket.markup.repeater.RefreshingView is a subclass of RepeatingView that comes with a customizable rendering strategy for its children components.

RefreshingView defines abstract methods populateItem(Item) and getItemModels(). The first method is similar to the namesake method seen for ListView, but it takes in input an instance of class org.apache.wicket.markup.repeater.Item which is a subclass of ListItem. RefreshingView is designed to display a collection of models containing the actual items. An iterator over these models is returned by the other abstract method getItemModels.

The following code is a version of the previous example that uses RefreshingView in place of ListView:

HTML:

  1. ...
  2. <body>
  3. <div id="bd" style="display: table;">
  4. <div wicket:id="persons" style="display: table-row;">
  5. <div style="display: table-cell;"><b>Full name: </b></div>
  6. <div wicket:id="fullName" style="display: table-cell;"></div>
  7. </div>
  8. </div>
  9. </body>
  10. ...

Java Code (Page Constructor):

  1. public HomePage(final PageParameters parameters) {
  2. //define the list of models to use
  3. final List<IModel<Person>> persons = new ArrayList<IModel<Person>>();
  4. persons.add(Model.of(new Person("John", "Smith")));
  5. persons.add(Model.of(new Person("Dan", "Wong")));
  6. add(new RefreshingView<Person>("persons") {
  7. @Override
  8. protected void populateItem(Item<Person> item) {
  9. item.add(new Label("fullName", new PropertyModel(item.getModel(), "fullName")));
  10. }
  11. @Override
  12. protected Iterator<IModel<Person>> getItemModels() {
  13. return persons.iterator();
  14. }
  15. });
  16. }

13.3.1. Item reuse strategy

Similar to ListView, the default behavior of the RefreshingView is to replace its children with new instances every time is rendered. The strategy that decides if and how children components must be refreshed is returned by method getItemReuseStrategy. This strategy is an implementation of interface IItemReuseStrategy. The default implementation used by RefreshingView is class DefaultItemReuseStrategy but Wicket provides also strategy ReuseIfModelsEqualStrategy which reuses an item if its model has been returned by the iterator obtained with method getItemModels.

To set a custom strategy we must use method setItemReuseStrategy.

13.4. Pageable repeaters

Wicket offers a number of components that should be used when we have to display a big number of items (for example the results of a select SQL query).

All these components implement interface org.apache.wicket.markup.html.navigation.paging.IPageable and use interface IDataProvider (placed in package org.apache.wicket.markup.repeater.data) as data source. This interface is designed to support data paging. We will see an example of data paging later in paragraph 13.4.2.

The methods defined by IDataProvider are the following:

  • iterator(long first, long count): returns an iterator over a subset of the entire dataset. The subset starts from the item at position first and includes all the next count items (i.e. it’s the closed interval first+count ).

  • size(): gets the size of the entire dataset.

  • model(T object): this method is used to wrap an item returned by the iterator with a model. This can be necessary if, for example, we need to wrap items with a detachable model to prevent them from being serialized.

Wicket already provides implementations of IDataProvider to work with a List as data source (ListDataProvider) and to support data sorting (SortableDataProvider).

13.4.1. Component DataView

Class org.apache.wicket.markup.repeater.data.DataView is the simplest pageable repeater shipped with Wicket. DataView comes with abstract method populateItem(Item) that must be implemented to configure children components. In the following example we use a DataView to display a list of Person objects in a HTML table:

HTML:

  1. <table>
  2. <tr>
  3. <th>Name</th><th>Surname</th><th>Address</th><th>Email</th>
  4. </tr>
  5. <tr wicket:id="rows">
  6. <td wicket:id="dataRow"></td>
  7. </tr>
  8. </table>

Java Code:

  1. //method loadPersons is defined elsewhere
  2. List<Person> persons = loadPersons();
  3. ListDataProvider<Person> listDataProvider = new ListDataProvider<Person>(persons);
  4. DataView<Person> dataView = new DataView<Person>("rows", listDataProvider) {
  5. @Override
  6. protected void populateItem(Item<Person> item) {
  7. Person person = item.getModelObject();
  8. RepeatingView repeatingView = new RepeatingView("dataRow");
  9. repeatingView.add(new Label(repeatingView.newChildId(), person.getName()));
  10. repeatingView.add(new Label(repeatingView.newChildId(), person.getSurname()));
  11. repeatingView.add(new Label(repeatingView.newChildId(), person.getAddress()));
  12. repeatingView.add(new Label(repeatingView.newChildId(), person.getEmail()));
  13. item.add(repeatingView);
  14. }
  15. };
  16. add(dataView);

Please note that in the code above we have used also a RepeatingView component to populate the rows of the table.

In the next paragraph we will see a similar example that adds support for data paging.

13.4.2. Data paging

To enable data paging on a pageable repeater, we must first set the number of items to display per page with method setItemsPerPage(long items). Then, we must attach the repeater to panel PagingNavigator (placed in package org.apache.wicket.markup.html.navigation.paging) which is responsible for rendering a navigation bar containing the links illustrated in the following picture:

paging navigator

Project PageDataViewExample mixes a DataView component with a PagingNavigator to display the list of all countries of the world sorted by alphabetical order. Here is the initialization code of the project home page:

HTML:

  1. <table>
  2. <tr>
  3. <th>ISO 3166-1</th><th>Name</th><th>Long name</th><th>Capital</th><th>Population</th>
  4. </tr>
  5. <tr wicket:id="rows">
  6. <td wicket:id="dataRow"></td>
  7. </tr>
  8. </table>

Java Code:

  1. public HomePage(final PageParameters parameters) {
  2. super(parameters);
  3. //method loadCountriesFromCsv is defined elsewhere in the class.
  4. //It reads countries data from a csv file and returns each row as an array of Strings.
  5. List<String[]> countries = loadCountriesFromCsv();
  6. ListDataProvider<String[]> listDataProvider = new ListDataProvider<String[]>(countries);
  7. DataView<String[]> dataView = new DataView<String[]>("rows", listDataProvider) {
  8. @Override
  9. protected void populateItem(Item<String[]> item) {
  10. String[] countriesArr = item.getModelObject();
  11. RepeatingView repeatingView = new RepeatingView("dataRow");
  12. for (int i = 0; i < countriesArr.length; i++){
  13. repeatingView.add(new Label(repeatingView.newChildId(), countriesArr[i]));
  14. }
  15. item.add(repeatingView);
  16. }
  17. };
  18. dataView.setItemsPerPage(15);
  19. add(dataView);
  20. add(new PagingNavigator("pagingNavigator", dataView));
  21. }

The data of a single country (ISO code, name, long name, capital and population) are handled with an array of strings. The usage of PagingNavigator is quite straightforward as we need to simply pass the pageable repeater to its constructor.

To explore the other pageable repeaters shipped with Wicket you can visit the examples site where you can find live examples of these components.

Wicket provides also component PageableListView which is a sublcass of ListView that implements interface IPageable, hence it can be considered a pageable repeater even if it doesn’t use interface IDataProvider as data source.

13.5. Summary

In this chapter we have explored the built-in set of components called repeaters which are designed to repeat their own markup in output to display a set of items. We have started with component RepeatingView which can be used to repeat a simple markup fragment.

Then, we have seen components ListView and RefreshingView which should be used when the markup to repeat contains nested components to populate.

Finally, we have discussed those repeaters that support data paging and that are called pageable repeaters. We ended the chapter looking at an example where a pageable repeater is used with panel PagingNavigator to make its dataset navigable by the user.