Wicket table with variable number of columns

12,324

Solution 1

You can nest ListView. The markup you want will look something like this:

<table>
  <tr><th wicket:id="headerlist"><span wicket:id="header"></span></th></tr>
  <tr wicket:id="contentlist"><td wicket:id="column">
    <span wicket:id="data"></span>
  </td></tr>
</table>

You will then need three ListView. The first (headerlist) will fill in the headers from the keys list. This is simple, so I'll skip the example.

The second (contentlist) will be across your data list. In the populateItems method you will add the third ListView (column), which will again iterate across the keys list:

add(new ListView<Map<String,MyObject>>("contentlist", data) {
  protected void populateItem(ListItem<Map<String,MyObject>> item) {
    final Map<String,MyObject> map = item.getModelObject();
    // Inner list - using item.add to add to the inner list
    item.add(new ListView<String>("column", keys) {
      protected void populateItem(ListItem<String> item) {
        String key = item.getModelObject();
        item.add(new Label("data", map.get(key).toString()));
      }
    });
  }
});

Solution 2

Below, examples using DefaultDataTable and nested ListViews.

Note that, while the DataTable approach may look less straightforward (well, it depends on the eye of the beholder), it actually separates more cleanly data fetching from visualization, and you get pagination out-of-the-box: try adding more data, or lowering the rowsPerPage (DefaultDataTable's last constructor param).

public class HomePage extends WebPage {

    static final String A = "a";
    static final String B = "b";

    public HomePage() {
        final List<String> keys = Arrays.asList(A, B);
        final List<Map<String, Integer>> data = Arrays.asList(
            map(A, 1).put(B, 11).toMap(),
            map(A, 2).put(B, 12).toMap(),
            map(A, 3).put(B, 13).toMap(),
            map(A, 4).put(B, 14).toMap(),
            map(A, 5).put(B, 15).toMap(),
            map(A, 6).put(B, 16).toMap(),
            map(A, 7).put(B, 17).toMap(),
            map(A, 8).put(B, 18).toMap(),
            map(A, 9).put(B, 19).toMap());

        // Using a DefaultDataTable
        ISortableDataProvider dataProvider = new SortableDataProvider() {
            public Iterator iterator(int first, int count) {
                int start = Math.min(0, first);
                int end = Math.min(data.size(), start + count);
                return data.subList(start, end).iterator();
            }
            public int size() {
                return data.size();
            }
            public IModel model(Object object) {
                return new CompoundPropertyModel(object);
            }
        };
        List columns = new ArrayList();
        for (String key : keys)
            columns.add(new PropertyColumn(Model.of(key), key));
        add(new DefaultDataTable("dataTable", columns, dataProvider, 20));

        // Using a nested ListViews
        add(new ListView("headers", keys) {
            @Override
            protected void populateItem(ListItem item) {
                item.add(new Label("header", String.valueOf(item.getModelObject())));
            }
        });
        add(new ListView("listView", data) {
            @Override
            protected void populateItem(ListItem item) {
                final Map rowMap = (Map) item.getModelObject();
                item.add(new ListView("nested", keys) {
                    @Override
                    protected void populateItem(ListItem item) {
                        Object value = rowMap.get(item.getModelObject());
                        item.add(new Label("value", String.valueOf(value)));
                    }
                });
            }
        });
    }

    // to make building the data structure a little more fun :)
    private MapBuilder<String, Integer> map(String key, Integer value) {
        return new MapBuilder<String, Integer>().put(key, value);
    }
    private static class MapBuilder<K, V> {
        Map<K, V> map = new HashMap<K, V>();
        MapBuilder<K, V> put(K key, V value) {
            map.put(key, value);
            return this;
        }
        Map<K, V> toMap() {
            return map;
        }
    }
}


<html xmlns:wicket="http://wicket.apache.org">
<body>

  <table wicket:id="dataTable"></table>

  <table>
    <tr>
      <th wicket:id="headers">
          <span wicket:id="header"></span>
      </th>
    </tr>
    <tr wicket:id="listView">
      <td wicket:id="nested">
        <span wicket:id="value"></span>
      </td>
    </tr>
  </table>

</body>
</html>

Solution 3

You can of course use nested ListViews, but you can also use DataTable and its descendants, which were specifically designed for this task. As a bonus you can also get things like sorting and pagination out of them.

Solution 4

Thanks Tetsuo! I've filled in the Generics for Tetsuo's variable column examples:

  1. DefaultDataTable
  2. Nested ListViews

Wicket HTML

<html xmlns:wicket="http://wicket.apache.org">
<body>

  <h3>Example 1</h3>
  <table wicket:id="dataTable"></table>

  <br>

  <h3>Example 2</h3>
  <table>

    <tr>
      <th wicket:id="headers">
        <span wicket:id="header"></span>
      </th>
    </tr>

    <tr wicket:id="listView">
      <td wicket:id="nested">
        <span wicket:id="value"></span>
      </td>
    </tr>

  </table>

</body>
</html>

Wicket Java

public class HomePage extends WebPage 
{
    /** Represents serialVersionUID. */
    private static final long serialVersionUID = 20150701L;

    static final String A = "alpha";
    static final String B = "beta";
    static final String C = "gamma";

    public HomePage()
    {
        super();

        final List<String> keys = Arrays.asList(A, B, C);

        final List<Map<String,Integer>> data = Arrays.asList
        (
            map(A, 1).put(B, 11).put(C, 21).toMap(),
            map(A, 2).put(B, 12).put(C, 22).toMap(),
            map(A, 3).put(B, 13).put(C, 23).toMap(),
            map(A, 4).put(B, 14).put(C, 24).toMap(),
            map(A, 5).put(B, 15).put(C, 25).toMap(),
            map(A, 6).put(B, 16).put(C, 26).toMap(),
            map(A, 7).put(B, 17).put(C, 27).toMap(),
            map(A, 8).put(B, 18).put(C, 28).toMap(),
            map(A, 9).put(B, 19).put(C, 29).toMap()
        );

        // Using a DefaultDataTable
        ISortableDataProvider<Map<String,Integer>,String> dataProvider = 
            new SortableDataProvider<Map<String,Integer>,String>()
        {
            /** Represents serialVersionUID. */
            private static final long serialVersionUID = HomePage.serialVersionUID;

            public Iterator<Map<String,Integer>> iterator(long first, long count)
            {
                int start = Math.max(0, (int) first);
                int end = Math.min(data.size(), start + (int) count);
                return data.subList(start, end).iterator();
            }

            public long size()
            {
                return data.size();
            }

            public IModel<Map<String,Integer>> model(Map<String,Integer> object)
            {
                return new CompoundPropertyModel<Map<String,Integer>>(object);
            }
        };

        List<PropertyColumn<Map<String,Integer>,String>> columns =
            new ArrayList<PropertyColumn<Map<String,Integer>,String>>();

        for (String key : keys)
            columns.add(new PropertyColumn<Map<String,Integer>,String>(Model.of(key), key));

        // Example 1 - Using a DataTable
        // Wicket: "dataTable"
        add(new DefaultDataTable<Map<String,Integer>,String>("dataTable", columns, dataProvider, 5));

        // Example 2 - Using nested ListViews
        // Wicket: "headers"
        add
        (
            new ListView<String>("headers", keys)
            {
                /** Represents serialVersionUID. */
                private static final long serialVersionUID = HomePage.serialVersionUID;

                @Override
                protected void populateItem(ListItem<String> item)
                {
                    // Wicket: "header"
                    item.add(new Label("header", String.valueOf(item.getModelObject())));
                }
            }
        );

        add
        (
            // Wicket: "listView"
            new ListView<Map<String,Integer>>("listView", data)
            {
                /** Represents serialVersionUID. */
                private static final long serialVersionUID = HomePage.serialVersionUID;

                @Override
                protected void populateItem(ListItem<Map<String,Integer>> item)
                {
                    final Map<String,Integer> rowMap = item.getModelObject();
                    item.add
                    (
                        // Wicket: "nested"
                        new ListView<String>("nested", keys)
                        {
                            private static final long serialVersionUID = HomePage.serialVersionUID;
                            @Override
                            protected void populateItem(ListItem<String> item)
                            {
                                Integer value = rowMap.get(item.getModelObject());

                                // Wicket: "value"
                                item.add(new Label("value", String.valueOf(value)));
                            }
                        }
                    );
                }
            }
        );
    }

    // Make building the data structure a little more fun :)
    private MapBuilder<String, Integer> map(String key, Integer value)
    {
        return new MapBuilder<String, Integer>().put(key, value);
    }

    private static class MapBuilder<K, V>
    {
        Map<K, V> map = new HashMap<K, V>();

        MapBuilder<K, V> put(K key, V value)
        {
            map.put(key, value);
            return this;
        }

        Map<K, V> toMap()
        {
            return map;
        }
    }
}

Generic Usage

DefaultDataTable extends DataTable

  • @param The model object type
  • @param The type of the sorting parameter

IColumn

  • @param The type of the object that will be rendered in this column's cells
  • @param The type of the sorting parameter

ISortableDataProvider

  • @param The model object type (omitted in JavaDoc) ??
  • @param The type of the sorting parameter
Share:
12,324
Mario Duarte
Author by

Mario Duarte

Senior Software Engineer

Updated on June 04, 2022

Comments

  • Mario Duarte
    Mario Duarte almost 2 years

    I've been creating tables by adding a ListView (providing it with my data as a List<MyObject>) to the page, and assigning the corresponding ids to each column in the html file.

    However now I have a situation where instead of a simple List<MyObject> I have List<Map<String,MyObject>>. I also get a list with all the possible keys of the nested map (List<String>). Now I need to create a table where each value of the Map should be in the column with the name of the key pointing to that value.

    Let's say I have the following data:

    keys = ['a', 'b']
    
    data = [ { 'a' = 1, 'b' = 2 },
             { 'a' = 3, 'b' = 4 },
             { 'a' = 5, 'b' = 6}] 
    

    I would like to create the table:

    <table>
        <tr>
            <th>a</th>
            <th>b</th>
        </tr>
        <tr>
            <td>1</td>
            <td>2</td>
        </tr>
        <tr>
            <td>3</td>
            <td>4</td>
        </tr>
        <tr>
            <td>5</td>
            <td>6</td>
        </tr>
    </table>
    

    Knowing that the names and number of keys in the nested Map can change, what is the best way to implement this in wicket?