VGrid

VGrid is Flow Viritin's enhanced Grid. Beyond the usual fluent “with*-returns-this” treatment, it ships with a richer styling and formatting API than Vaadin core Grid, plus sensible defaults for record and bean-based data.

Why VGrid over Grid

  • Columns can be referred to with getter method references instead of property name strings, so the compiler and the IDE's rename refactoring keep them in sync.
  • addColumn(Person::getFirstName) gives the column a key, a header and sorting, instead of a bare, headerless column.
  • One column of an otherwise standard grid can be given a component based rendering in place, without rebuilding the column set.
  • Reads columns in declaration order for both POJOs and Java records (the core introspector returns them in a random order).
  • Falls back to Jackson when the default Vaadin mechanism cannot resolve a column (e.g. for default methods introduced in Java 8).
  • Column.getStyle() actually applies the style – the core API silently drops style rules.
  • Adds an optional built-in column selector menu similar to the one in Vaadin 8.

Columns without strings

Vaadin core Grid identifies properties by name, as strings. VGrid accepts a method reference to the getter anywhere a property name is expected, which the compiler checks and the IDE renames along with the getter:

VGrid<Person> grid = new VGrid<>(Person.class, false);

grid.setColumns(Person::getFirstName, Person::getLastName, Person::getAge);
grid.setSortableColumns(Person::getLastName);
grid.getColumnByKey(Person::getAge).setHeader("Years");

Every method that takes a property name has such a counterpart:

String API Getter reference API
setColumns("firstName") setColumns(Person::getFirstName)
withProperties("firstName") withProperties(Person::getFirstName)
hideProperties("firstName") hideProperties(Person::getFirstName)
addColumns("firstName") addColumns(Person::getFirstName)
addColumn("firstName") addPropertyColumn(Person::getFirstName)
setSortableColumns("firstName") setSortableColumns(Person::getFirstName)
getColumnByKey("firstName") getColumnByKey(Person::getFirstName)
removeColumnByKey("firstName") removeColumnByKey(Person::getFirstName)
setColumnOrder(...) setColumnOrder(Person::getAge, Person::getFirstName)
column.setKey("firstName") column.withKey(Person::getFirstName)
column.setSortProperty("firstName") column.withSortProperties(Person::getFirstName)

Record accessors work just as well as JavaBeans getters:

record Person(String name, int age) {}

VGrid<Person> grid = new VGrid<>(Person.class, false);
grid.setColumns(Person::name, Person::age);

Nested properties, the equivalent of the "address.street" dot notation, are expressed by chaining with PropertyRef:

grid.setColumns(
    PropertyRef.of(Person::getName),
    PropertyRef.of(Person::getAddress).then(Address::getStreet));

The property name is dug out of the SerializedLambda that the JVM creates for serializable lambdas, so this needs no extra dependency and no particular compiler – javac and the Eclipse compiler both work. The resolution is cached per call site, so the cost is paid once per Person::getFirstName expression in your source code.

Only an actual method reference carries a property name. A lambda expression like p -> p.getFirstName() does not, and fails fast with an IllegalArgumentException telling you to use a method reference.

Columns added with a getter reference

Grid.addColumn(ValueProvider) produces a column with no key, no header and no sorting, which is why applications tend to repeat what the getter already said:

// Vaadin core Grid
addColumn(Person::getFirstName).setHeader("First Name");
addColumn(Person::getLastName).setHeader("Last Name");
addColumn(Person::getAge).setHeader("Age");

VGrid recognises that the value provider is a getter reference and configures the column just like addColumn("firstName") would: the property name becomes the column key, the property caption becomes the header, and a Comparable property becomes sortable. The above is simply:

// VGrid, same headers, and sortable as a bonus
addColumn(Person::getFirstName);
addColumn(Person::getLastName);
addColumn(Person::getAge);

Note that this is a runtime observation about the value provider, not a rule about how the call is written, so a ValueProvider variable or parameter that happens to hold a getter reference is treated the same way.

Anything that is not a getter reference is left exactly as Vaadin core leaves it. In particular a lambda expression, a method that is not a JavaBeans getter or a property of the bean type, toString, getClass, hashCode, and addComponentColumn in its entirety.

Whatever you set explicitly wins:

// The header derived from the getter is replaced
addColumn(Person::getFirstName).setHeader("Etunimi");

// So is the key, no "Column key cannot be changed"
addColumn(Person::getFirstName).setKey("name");

If another column has already reserved the property name, the key is simply not assigned, so two columns of the same property don't clash.

The behaviour can be turned off per grid, or for the whole application if you are upgrading a code base that relies on the bare columns:

grid.setAutoConfigureFromGetterReferences(false);

// Or once at startup, affects grids created afterwards
VGrid.setAutoConfigureFromGetterReferencesByDefault(false);

Re-rendering a single column

Replacing the presentation of one column of an otherwise standard grid usually means tearing the whole set of columns down and building it back up: the ones before the special column, the special one, and the ones after it. setComponentRenderer swaps just the rendering, and leaves the column where it was with the rest of its configuration – key, header, width, sorting – intact:

VGrid<Person> grid = new VGrid<>(Person.class);

grid.setComponentRenderer(Person::getEmail,
    person -> new Anchor("mailto:" + person.getEmail(), person.getEmail()));

The column key works as well as the getter reference, which is handy when the grid was built from strings:

grid.setComponentRenderer("email",
    person -> new Anchor("mailto:" + person.getEmail(), person.getEmail()));

The column keeps the comparator it was created with, so in-memory sorting still sorts by the underlying property value even though the cell now shows a component.

For a LitRenderer or one of the built-in renderers, setRenderer is the general form of the same thing:

grid.setRenderer(Person::getJoinTime,
    new LocalDateTimeRenderer<>(Person::getJoinTime, "dd.MM.yyyy"));

Both are also available on the column itself, if you have one at hand (there the general form is withRenderer, as Column already has a setRenderer of its own):

grid.getColumnByKey(Person::getEmail)
    .setComponentRenderer(person -> new Anchor("mailto:" + person.getEmail(), person.getEmail()))
    .setWidth("20em");

Column styling with typed Style methods

Column.getStyle() returns the same Style instance you use elsewhere in Flow, so strongly typed setters like setBackground, setTextAlign or setOutline are available – no more set("border", "1px solid") string fiddling.

VGrid<Person> grid = new VGrid<>(Person.class);
grid.setItems(people);

grid.getColumnByKey("firstName")
    .setHeader("First name")
    .getStyle()
        .setBackground("red")
        .setColor("white")
        .setFont("bold 20px Arial")
        .setTextAlign(Style.TextAlign.RIGHT)
        .setOutline("2px dotted black");

Assigning the same rule to multiple columns reuses the underlying style element, so the overhead of styling many columns stays small:

grid.getColumnByKey("firstName").getStyle().set("color", "red");
// Reuses the "color: red" style element from above
grid.getColumnByKey("lastName").getStyle().set("color", "red");

Per-row styling with a RowStyler

withRowStyler lets you compute a row's style from the item itself. The style is applied to the whole row without template tricks:

VGrid<Person> grid = new VGrid<>(Person.class)
    .withRowStyler((person, style) -> {
        if (person.getId() % 5 == 0) {
            style.setColor("blue");
            style.setBackgroundColor("lightgray");
        }
    });

Grid-wide CellFormatter

Rather than configuring each column's renderer individually, a single CellFormatter can format values for every column. It receives the column and the cell value, and returns the string to display.

VGrid<Person> grid = new VGrid<>(Person.class)
    .withCellFormatter((column, value) -> {
        if (value instanceof LocalDateTime dt) {
            return dt.format(DateTimeFormatter.ISO_LOCAL_DATE);
        }
        // Target specific columns by header text or key if needed:
        // if (column.getHeaderText().contains("Wind")) {
        //     return "%.0f m/s".formatted((Double) value);
        // }
        return VGrid.CellFormatter.defaultVaadinFormatting(value);
    });

VGrid.CellFormatter.defaultVaadinFormatting handles the common cases (null becomes the empty string, everything else goes through String.valueOf), so your formatter only has to worry about the types it wants to customise.

Built-in column selector menu

Enable an in-header menu that lets users show or hide columns:

VGrid<Person> grid = new VGrid<>(Person.class)
    .withColumnSelector();

Works with records

No extra setup is needed for Java records – columns appear in the order the components are declared in the record:

record Person(String name, int age) {}

VGrid<Person> grid = new VGrid<>(Person.class);
grid.setItems(
    new Person("Alice", 25),
    new Person("Bob", 30),
    new Person("Charlie", 22)
);

setColumns(Person::age, Person::name) – or the string based setColumns("age", "name") – still works if you want a different order.