Over a million developers have joined DZone.

The Table is Dead, Long Live the Grid

· DevOps Zone

Discover how to optimize your DevOps workflows with our cloud-based automated testing infrastructure, brought to you in partnership with Sauce Labs

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 callingsetVisibleColumns() and omitting the id property in the parameter array. With Vaadin 7.4, theContainer hierarchy has been enriched with the Delegate pattern: the GeneratedPropertyContainer is a wrapper around another container but offers additional methods, includingremoveContainerProperty(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 Rendererinterface: 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.

Header

The final touch is to remove the column headers as befits any chat application. This is simply done withsetHeaderVisible(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.

Download “The DevOps Journey - From Waterfall to Continuous Delivery” to learn learn about the importance of integrating automated testing into the DevOps workflow, brought to you in partnership with Sauce Labs.

Topics:

Published at DZone with permission of Nicolas Frankel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}