The Table is dead, long live the Grid

Submitted on 15 March 2015

Vaadin 7.4.0 is finally out and with it comes the long awaited successor of the Table component, the Grid. As for the rest of Vaadin, it’s quite well-documented.

This article aims at showing a simple example of this new component to highlights differences with the previous Table. In order to do so, let’s use an existing sample - namely my Vaadin workshop that displays a table of messages, with date, author, text and a delete button.

First steps with Grid

A first draft implementation would look like this:

public class SampleGrid extends Grid {

    public SampleGrid(Container.Indexed indexed) {
        setContainerDataSource(indexed);
    }
}

Grid’s columns

It’s simple and quite straightforward. At this point, nothing distinguishes the new Grid from the old Table, only default sortable columns. As most chat applications don’t allow that - and the one from the workshop neither, let’s disable it:

public class SampleGrid extends Grid {

    public SampleGrid(Container.Indexed indexed) {
        setContainerDataSource(indexed);
        getColumns().stream().forEach(c -> c.setSortable(false));
    }
}

So the Grid component has the concept of columns. One can get a reference on a single column column by passing the propertyId of the underlying item with getColumn(propertyId) or the sequence of all columns with getColumns(). Note that Java 8 helps with the handling of each column with the Stream API and a simple lambda’s usage.

Wrapped container

The next step is to hide the message’s id column; with Table, it was achieved by calling setVisibleColumns() and omitting the id property in the parameter array. With Vaadin 7.4, the Container hierarchy has been enriched with the Delegate pattern: the GeneratedPropertyContainer is a wrapper around another container but offers additional methods, including removeContainerProperty(propertyId) that effectively hides a property from the wrapped container. This produces the following code:

public class SampleGrid extends Grid {

    public SampleGrid(Container.Indexed indexed) {
        GeneratedPropertyContainer wrapperContainer = new GeneratedPropertyContainer(indexed);
        wrapperContainer.removeContainerProperty("id");
        setContainerDataSource(wrapperContainer);
        getColumns().stream().forEach(c -> c.setSortable(false));
    }
}

Renderers

Now, let’s format the date column. With Table, it required implementing a dedicated ColumnGenerator. At first glance, it seems using the addGeneratedProperty of the wrapped container mentioned in the above section would be the thing to do. However, for simple formatting issues, Vaadin 7.4 provides a Renderer interface: any column can be set such a renderer. Icing on the cake, a DateRenderer is provided that accepts a format string as an argument.

public class SampleGrid extends Grid {

    private static final String FORMAT = "%1$td/%1$tm/%1$tY %1$tH:%1$tM:%1$tS";

    public SampleGrid(Container.Indexed indexed) {
        GeneratedPropertyContainer wrapperContainer = new GeneratedPropertyContainer(indexed);
        wrapperContainer.removeContainerProperty("id");
        setContainerDataSource(wrapperContainer);
        getColumn("timeStamp").setRenderer(new DateRenderer(FORMAT));
        getColumns().stream().forEach(c -> c.setSortable(false));
    }
}

Note the format string uses the pattern from Formatter, not from SimpleDateFormat.

Multi-selection

The last task is to add the delete feature. Out-of-the-box, setting the selection mode to multi create a new column with checkboxes to select multiple lines:

public class SampleGrid extends Grid {

    private static final String FORMAT = "%1$td/%1$tm/%1$tY %1$tH:%1$tM:%1$tS";

    public SampleGrid(Container.Indexed indexed) {
        GeneratedPropertyContainer wrapperContainer = new GeneratedPropertyContainer(indexed);
        wrapperContainer.removeContainerProperty("id");
        setContainerDataSource(wrapperContainer);
        getColumn("timeStamp").setRenderer(new DateRenderer(FORMAT));
        getColumns().stream().forEach(c -> c.setSortable(false));
        setSelectionMode(SelectionMode.MULTI);
    }
}

Then, just create a simple button that will delete all selected lines. This gets the job done. However, this is not the original design: in the above screenshot, each line provides its own dedicated button. Pro, you have to click on the selected line so you prevent most mistakes; con, you cannot batch delete.

Along with the date renderer, Vaadin 7.4 also provides a ButtonRenderer that accepts a RenderClickListener to add behavior. This only needs to be set on the id column.

public class SampleGrid extends Grid {

    private static final String FORMAT = "%1$td/%1$tm/%1$tY %1$tH:%1$tM:%1$tS";

    public SampleGrid(Container.Indexed indexed) {
        GeneratedPropertyContainer wrapperContainer = new GeneratedPropertyContainer(indexed);
        setContainerDataSource(wrapperContainer);
        getColumn("timeStamp").setRenderer(new DateRenderer(FORMAT));
        getColumn("id").setRenderer(new ButtonRenderer(event -> {
            Object itemId = event.getItemId();
            indexed.removeItem(itemId);
        }));
        getColumns().stream().forEach(c -> c.setSortable(false));
        setSelectionMode(SelectionMode.MULTI);
    }
}

The downside of this approach is that you cannot change the button’s label as it is taken from the underlying object with no way of changing it. In this case, the message’s id will be displayed in a button and users might cluelessly delete the message. That’s out of the question.

This makes it only slightly more complex as we need to add a dedicated container property that always returns the wanted button label and assign it the renderer from above. The final code looks like the following:

public class SampleGrid extends Grid {

    private static final String FORMAT = "%1$td/%1$tm/%1$tY %1$tH:%1$tM:%1$tS";

    public SampleGrid(Container.Indexed indexed) {
        GeneratedPropertyContainer wrapperContainer = new GeneratedPropertyContainer(indexed);
        wrapperContainer.removeContainerProperty("id");
        setContainerDataSource(wrapperContainer);
        wrapperContainer.addGeneratedProperty("delete", new PropertyValueGenerator<String>() {
            @Override
            public String getValue(Item item, Object itemId, Object propertyId) {
                return "Delete";
            }

            @Override
            public Class<String> getType() {
                return String.class;
            }
        });
        getColumn("delete").setRenderer(new ButtonRenderer(event -> {
            Object itemId = event.getItemId();
            indexed.removeItem(itemId);
        }));
        getColumn("timeStamp").setRenderer(new DateRenderer(FORMAT));
        getColumns().stream().forEach(c -> c.setSortable(false));
    }
}

A new generated property is added with the PropertyValueGenerator always returning the string Delete. More advanced requirements could use a Locale to have a display depending on the user preferences, but this code is enough for ours. Note the button renderer is kept but assigned to the new delete generated column instead of the id’s. Finally, the multiselection mode has to be removed.

The final touch is to remove the column headers as befits any chat application. This is simply done with setHeaderVisible(false).

The final result looks like the original table, while saving some lines of code.

The code for this article can be browsed and forked on Github.