Documentation

Documentation versions (currently viewingVaadin 24)

Grid

Grid is a feature-rich component and therefore complex. Due to the server-driven nature of the component, there are some exclusive features available for the Java API and when using Grid together with Vaadin Flow. This page shows how to use Grid in a Flow application. It also explains the unique features, such as configuring Lit renderers instead of generic component renderers and styling the component with the Theme property.

Binding to Data

Grid is bound to a List of items, by default. You can use the setItems() method to set the items.

For example, to show a list of beans in a Grid, you could do something like this:

// Have some data
List<Person> people = Arrays.asList(
        new Person("Nicolaus Copernicus", 1543),
        new Person("Galileo Galilei", 1564),
        new Person("Johannes Kepler", 1571));

// Create a grid bound to the list
Grid<Person> grid = new Grid<>();
grid.setItems(people);
grid.addColumn(Person::getName).setHeader("Name");
grid.addColumn(Person::getYearOfBirth)
        .setHeader("Year of birth");

layout.add(grid);

Behind the scenes, Grid uses the DataProvider interface to communicate with the backend. The setItems() method is a shorthand to create a ListDataProvider. For a large amount of data or other advanced use cases, you should probably use the DataProvider interface and lazy loading. See Data Providers for more.

To function, components such as the Grid and ComboBox require that the assigned items have identifiers that are stable and unique. This usually requires a proper implementation of the hashCode() and equals() methods as described in the section.

Handling Selection Changes

The Grid doesn’t implement directly the HasValue interface. Other selection components do typically implement this interface. Therefore, selection handling in the Grid is different from typical selection components. The Grid supports three selection options: single selection, multiple selection, and no selection. Each option is defined by a specific selection model.

For basic switching between selection models, you can use the setSelectionMode(SelectionMode) method. Possible options are SINGLE (default), MULTI, or NONE.

To access the selection API or to use Grid as an input field with Binder, you can use asSingleSelect() or asMultiSelect(), depending on the currently defined selection mode. Both the SingleSelect and MultiSelect interfaces implement the HasValue interface. In the MultiSelect interface, the value type is a Set of the item type.

For example, when using the HasValue interface with single and multi-select mode, you could do something like this:

Grid<Person> grid = new Grid<>();

grid.setSelectionMode(SelectionMode.SINGLE);
SingleSelect<Grid<Person>, Person> personSelect =
        grid.asSingleSelect();
// personSelect can now be used with Binder or
// HasValue interface
personSelect.addValueChangeListener(e -> {
    Person selectedPerson = e.getValue();
});

grid.setSelectionMode(SelectionMode.MULTI);
MultiSelect<Grid<Person>, Person> multiSelect =
        grid.asMultiSelect();
multiSelect.addValueChangeListener(e -> {
    Set<Person> selectedPersons = e.getValue();
});

Alternatively, you can use a grid-specific selection API. To get the selected value or values in any selection model, you can use a SelectionListener, with the provided generic SelectionEvent, to get the selected value or values.

To use addSelectionListener() to get all selected items, for example, you could do this:

Grid<Person> grid = new Grid<>();

// switch to multiselect mode
grid.setSelectionMode(SelectionMode.MULTI);

grid.addSelectionListener(event -> {
   Set<Person> selected = event.getAllSelectedItems();
   message.setText(selected.size() + " items selected");
});
Note
The listener is attached to the selection model and not the grid. It stops getting events when the selection mode is changed.

You can use the select(T) method to select values, programmatically. In multi-selection mode, this adds the given item to the selection.

The example here is using the select(T) method:

// in single-select, only one item is selected
grid.select(defaultItem);

// switch to multi select, clears selection
grid.setSelectionMode(SelectionMode.MULTI);
// Select items 2-4
people.subList(2, 3).forEach(grid::select);

You can get the current selection from the Grid using the getSelectedItems() method. The returned Set contains one item in single-selection mode, or several items in multi-selection mode.

Warning

If you change the grid’s selection mode, it clears the selection and fires a selection event. To keep the previous selection, reset the selection afterwards using the select() method.

Warning

If you change the grid’s items with either setItems() or the used DataProvider, it clears the selection and fires a selection event. To maintain the previous selection, reset the selection afterwards using the select() method.

Selection Models

You can access the used selection model using the getSelectionModel() method. The return type is the GridSelectionModel, which has a generic selection model API. However, you can cast that to the specific selection model type, typically either SingleSelectionModel or MultiSelectionModel.

You can also get the selection model using the setSelectionMode(SelectionMode) method.

You could use the setSelectionMode(SelectionMode) method, for example, to get the selection model like so:

// the default selection model
GridSingleSelectionModel<Person> defaultModel =
    (GridSingleSelectionModel<Person>) grid
        .getSelectionModel();

// Use multi-selection mode
GridMultiSelectionModel<Person> selectionModel =
    (GridMultiSelectionModel<Person>) grid
        .setSelectionMode(SelectionMode.MULTI);

Single-Selection Model

Obtaining a reference to the SingleSelectionModel allows you access to a fine-grained API for the single-selection use case.

You can use the addSingleSelect(SingleSelectionListener) method to access SingleSelectionEvent, which includes additional convenience methods and API options.

In single-selection mode, it’s possible to control whether the empty (null) selection is allowed. This is enabled by default.

Disallowing empty (null) selection, for example, using the setDeselectAllowed() method would look like this:

// preselect value
grid.select(defaultItem);

GridSingleSelectionModel<Person> singleSelect =
    (GridSingleSelectionModel<Person>) grid
        .getSelectionModel();

// disallow empty selection
singleSelect.setDeselectAllowed(false);

Multi-Selection Model

In multi-selection mode, a user can select multiple items by selecting checkboxes in the left column.

Obtaining a reference to the MultiSelectionModel allows you access to a fine-grained API for the multi-selection use case.

You can use the addMultiSelectionListener(MultiSelectionListener) method to access MultiSelectionEvent, which includes additional convenience methods and API options.

For example, you could use the addMultiSelectionListener() method to access selection changes like this:

// Grid in multi-selection mode
Grid<Person> grid = new Grid<>();
grid.setItems(people);
GridMultiSelectionModel<Person> selectionModel =
    (GridMultiSelectionModel<Person>) grid
        .setSelectionMode(SelectionMode.MULTI);

selectionModel.selectAll();

selectionModel.addMultiSelectionListener(event -> {
    message.setText(String.format(
            "%s items added, %s removed.",
            event.getAddedSelection().size(),
            event.getRemovedSelection().size()));

    // Allow deleting only if there's any selected
    deleteSelected.setEnabled(
            event.getNewSelection().isEmpty());
});

Handling Item-Click Events

It’s possible to handle item-click or double-click events, in addition to handling selection events. These can be used with selection events or on their own.

For example, to disable the selection mode using SelectionMode.NONE, but still get item-click events, you would do this:

grid.setSelectionMode(SelectionMode.NONE);
grid.addItemClickListener(event -> System.out
        .println(("Clicked Item: " + event.getItem())));

The clicked item here, together with other information about the click, is available via the event. Selection events are no longer available, and no visual selection is displayed when a row is clicked.

It’s possible to get separate selection and click events. An example of this follows, using Grid in multi-selection mode with an added click or double-click listener:

grid.setSelectionMode(SelectionMode.MULTI);
grid.addItemDoubleClickListener(event ->
        copy(grid.getSelectedItems()));

In the example code here, the local copy() method is called with the currently selected items when the user double-clicks a row.

Configuring Columns

The addColumn() method allows you to add columns to the Grid. The column configuration is defined in Grid.Column objects that are returned by the addColumn() method. The getColumns() method returns a list of currently configured columns.

The setter methods in Column have fluent-API functionality, making it easy to chain configuration calls for columns.

Below is an example of chaining column configuration calls:

Column<Person> nameColumn = grid
    .addColumn(Person::getName)
    .setHeader("Name")
    .setFlexGrow(0)
    .setWidth("100px")
    .setResizable(false);

Column Keys

You can set an identifier key for a column using the setKey() method. This allows you to retrieve the column from the grid at any time.

Below is an example using the setKey() method to set an identifier key for a column:

nameColumn.setKey("name");
grid.getColumnByKey("name").setWidth("100px");

Automatically Adding Columns

You can configure Grid to add columns automatically for every property in a bean by passing the class of the bean type to the grid’s constructor. The property names are set as the column keys, and you can use them to further configure the columns.

Below is an example in which columns are added automatically by passing the bean-type class to the constructor:

Grid<Person> grid = new Grid<>(Person.class);
grid.getColumnByKey("yearOfBirth").setFrozen(true);

This constructor only adds columns for the direct properties of the bean type. The values are displayed as strings.

You can add columns for nested properties by using the dot notation with the setColumn(String) method.

Below is an example of adding a column for postalCode. It assumes Person has a reference to an Address object that has a postalCode property.

grid.addColumn("address.postalCode");

The column’s key here is "address.postalCode" and its header is "Postal Code". To use these String properties in addColumn(), you need to use the Grid constructor, which takes a bean-class parameter.

Defining & Ordering Automatically Added Columns

You can define which columns display, and the order in which they are displayed, in the grid, using the setColumns() method.

Below is an example in which columns are defined and they’re ordered using the setColumns() method:

Grid<Person> grid = new Grid<>(Person.class);
grid.setColumns("name", "age", "address.postalCode");
Tip
You can also use the setColumns() method to reorder the columns you already have.
Note
When calling setColumns(), all columns that are currently present in the grid are removed, and only those passed as parameters are added.

To add custom columns before the auto-generated columns, use instead the addColumns() method. You can avoid creating the auto-generated columns by using the Grid(Class, boolean) constructor.

Below is an example of adding custom columns:

Grid<Person> grid = new Grid<>(Person.class, false);
grid.addColumn(person -> person.getName().split(" ")[0])
    .setHeader("First name");
grid.addColumns("age", "address.postalCode");
Note
An IllegalArgumentException is thrown if you try to add columns that are already present in the grid.

Sortable Automatic Columns

All property-based columns are sortable, by default, if the property type implements Comparable.

Many data types, such as String, Number, primitive types and Date/LocalDate/LocalDateTime are Comparable, and therefore also sortable by default.

To make the column of a non-comparable property type sortable, you need to define a custom Comparator. See Column Sorting for more.

You can disable sorting for a specific column by using the setSortable() method.

Below is an example of this, disabling sorting on the address.postalCode column:

grid.getColumnByKey("address.postalCode")
        .setSortable(false);

You can also define a list of columns as sortable by using the setSortableColumns() method. This makes all other columns not sortable.

The example here shows how to set defined columns as sortable:

// All columns except "name" and "yearOfBirth"
// are not sortable
grid.setSortableColumns("name", "yearOfBirth");

Column Headers & Footers

By default, columns don’t have a header or a footer. They need to be set explicitly using the setHeader() and setFooter() methods. Both methods have two overloads: one accepts a plain text string, and the other a LitRenderer.

Below is an example of how to set headers and footers:

// Sets a simple text header
nameColumn.setHeader("Name");
// Sets a header using Html component,
// in this case bolding the caption "Name"
nameColumn.setHeader(new Html("<b>Name</b>"));

// Similarly for the footer
nameColumn.setFooter("Name");
nameColumn.setFooter(new Html("<b>Name</b>"));

See Using Lit Renderers for more information.

Column Reordering

Column reordering isn’t enabled by default. You can use the setColumnReorderingAllowed() method to allow column reordering by dragging.

Below is an example of how to enable column reordering:

grid.setColumnReorderingAllowed(true);

Hiding Columns

Columns can be hidden by calling the setVisible() method in Column.

Note
A hidden column still sends the data required to render it to the client side. The best practice is to remove — or not add — columns if the data isn’t needed on the client side. This reduces the amount of data sent, and it thereby lessens the load on the client.

Removing Columns

You can remove a single column using the removeColumn(Column) and removeColumnByKey(String) methods. You can also remove all currently configured columns using the removeAllColumns() method.

Setting Column Widths

By default, columns don’t have a defined width. They resize automatically based on the data displayed.

You can set the column width either relatively, using flex grow ratios by using the setFlexGrow() method, or you can set them explicitly using a CSS string value with setWidth() — with flex grow set to 0.

You can also enable user column resizing using the setResizable() method. The column is resized by dragging the column divider.

Setting Frozen Columns

You can freeze columns using the setFrozen() method. This ensures the set number of columns on the left remain static and visible when the user scrolls horizontally.

When columns are frozen, user reordering is limited to the frozen columns.

Below is an example of setting a column as frozen:

nameColumn.setFrozen(true);

Grouping Columns

You can group multiple columns together by adding them in the HeaderRow of the grid.

When you retrieve the HeaderRow, using the prependHeaderRow() or appendHeaderRow() methods, you can then group the columns using the join() method. Additionally, you can use the setText() and setComponent() methods on the join result to set the text or component for the joined columns.

For example, you can group columns like so:

// Create a header row
HeaderRow topRow = grid.prependHeaderRow();

// group two columns under the same label
topRow.join(nameColumn, ageColumn)
        .setComponent(new Label("Basic Information"));

// group the other two columns in the same header row
topRow.join(streetColumn, postalCodeColumn)
        .setComponent(new Label("Address Information"));

Using Renderers in Columns

You can configure columns to use a renderer to show the data in the cells. Conceptually, there are three types of renderer:

  1. Basic Renderer: Renders basic values, such as dates and numbers.

  2. Lit Renderer: Renders content using HTML markup and Lit data-binding syntax.

  3. Component Renderer: Renders content using arbitrary components.

Using Basic Renderers

You can use several basic renderers to configure grid columns.

Local Date Renderer

Use LocalDateRenderer to render LocalDate objects in the cells.

An example of this is below, which is using LocalDateRenderer with the addColumn() method:

grid.addColumn(new LocalDateRenderer<>(
        Item::getEstimatedDeliveryDate,
        () -> DateTimeFormatter.ofLocalizedDate(
                FormatStyle.MEDIUM)))
    .setHeader("Estimated delivery date");

LocalDateRenderer works with a DateTimeFormatter or a String format to render LocalDate objects.

Here is an example using a String format to render the LocalDate object:

grid.addColumn(new LocalDateRenderer<>(
        Item::getEstimatedDeliveryDate,
        "dd/MM/yyyy"))
    .setHeader("Estimated delivery date");

Local Date Time Renderer

Use LocalDateTimeRenderer to render LocalDateTime objects in the cells.

Below is an example using LocalDateTimeRenderer with the addColumn() method:

grid.addColumn(new LocalDateTimeRenderer<>(
        Item::getPurchaseDate,
        () -> DateTimeFormatter.ofLocalizedDateTime(
                FormatStyle.SHORT,
                FormatStyle.MEDIUM)))
    .setHeader("Purchase date and time");

LocalDateTimeRenderer also works with DateTimeFormatter — with separate styles for date and time — or a String format to render LocalDateTime objects.

Here is an example using a String format to render the LocalDateTime object:

grid.addColumn(new LocalDateTimeRenderer<>(
        Item::getPurchaseDate,
        "dd/MM HH:mm:ss")
).setHeader("Purchase date and time");

Number Renderer

Use NumberRenderer to render any type of Number in the cells. It’s especially useful for rendering floating-point values.

The example here is using NumberRenderer with the addColumn() method:

grid.addColumn(new NumberRenderer<>(Item::getPrice,
        NumberFormat.getCurrencyInstance())
).setHeader("Price");

It’s possible to set up the NumberRenderer with a String format, and an optional null representation.

For example, to use a String format to render a price do something like this:

grid.addColumn(new NumberRenderer<>(
        Item::getPrice, "$ %(,.2f",
        Locale.US, "$ 0.00")
).setHeader("Price");

Native Button Renderer

Use NativeButtonRenderer to create a clickable button in the cells. This creates a native <button> on the client side. Click events — or tap for touch devices — are handled on the server side.

Below is an example using NativeButtonRenderer with the addColumn() method:

grid.addColumn(
    new NativeButtonRenderer<>("Remove item",
       clickedItem -> {
           // remove the item
    })
);

You can configure a custom label for each item.

The example here is configuring NativeButtonRenderer to use a custom label:

grid.addColumn(new NativeButtonRenderer<>(
        item -> "Remove " + item,
        clickedItem -> {
            // remove the item
        })
);

Using Lit Renderers

Providing a LitRenderer for a column allows you to define the content of cells using HTML markup, and to use Lit notations for data binding and event handling.

The example here is using LitRenderer to embolden the names of the persons:

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

grid.addColumn(LitRenderer
       .<Person>of("<b>${item.name}</b>")
       .withProperty("name", Person::getName)
).setHeader("Name");

The template string here is passed for the static LitRenderer.of() method. Every property in the template needs to be defined in the withProperty() method. The ${item.name} is the Lit syntax for interpolating properties into the template. See the Lit documentation for more.

When using a custom Web Component or a Vaadin element in a Lit renderer, remember to import the component. This can be done using @JsModule or @Uses, if the component has a server-side counterpart. It ensures that all StyleSheet, HtmlImport, and JavaScript dependencies for the component are loaded when the Grid is used.

Creating Custom Properties

You can use a LitRenderer to create and display new properties, properties the item didn’t originally contain.

The example below is using LitRenderer to compute the approximate age of each person and add it in a new column. Age is the current year minus the birth year.

grid.addColumn(LitRenderer
        .<Person>of("${item.age} years old")
        .withProperty("age",
                person -> Year.now().getValue()
                        - person.getYearOfBirth())
).setHeader("Age");

Using Expressions

Lit templates can include any type of JavaScript expression, not limited to binding single property values.

For example, by evaluating the person’s age in the template expression, the age column could also be written as this:

grid.addColumn(LitRenderer
        .<Person>of("${new Date().getFullYear() - item.yearOfBirth} years old")
        .withProperty("yearOfBirth", Person::getYearOfBirth);
).setHeader("Age");

Binding Beans

If an object contains a bean property that has sub-properties, it’s only necessary to make the bean accessible by calling the withProperty() method. The sub-properties become accessible automatically.

Warning
All properties of the bean, even ones which aren’t used in the template, are sent to the client. Therefore, use this feature with caution.

The example that follows is using the withProperty() method to access multiple sub-properties. This assumes that Person has a field for the Address bean, which has street, number and postalCode fields with corresponding getter and setter methods.

grid.addColumn(LitRenderer.<Person>of(
        "<div>${item.address.street}, number " +
        "${item.address.number}<br>" +
        "<small>${item.address.postalCode}</small>" +
        "</div>")
        .withProperty("address", Person::getAddress))
    .setHeader("Address");

Handling Events

You can define event handlers for the elements in your template, and hook them to server-side code, by calling the withFunction() method on your LitRenderer. This is useful for editing items in the grid.

The example that follows is using the withFunction() method to map defined method names to server-side code. The snippet adds a new column with two buttons: one to edit a property of the item; and one to remove the item. Both buttons define a method to call for click events.

grid.addColumn(LitRenderer.<Person>of(
     "<button @click=\"${handleUpdate}\">Update</button>" +
     "<button @click=\"${handleRemove}\">Remove</button>")
    .withFunction("handleUpdate", person -> {
        person.setName(person.getName() + " Updated");
        grid.getDataProvider().refreshItem(person);
    }).withFunction("handleRemove", person -> {
        ListDataProvider<Person> dataProvider =
            (ListDataProvider<Person>) grid
                .getDataProvider();
        dataProvider.getItems().remove(person);
        dataProvider.refreshAll();
    })).setHeader("Actions");

When the server-side data used by the grid here is edited, the grid’s DataProvider is refreshed by calling the refreshItem() method. This ensures that the changes are in the element. When an item is removed, the refreshAll() method call ensures that all of the data is updated.

You will need to use Lit notation for event handlers. The @click is Lit syntax for the native click. The LitRenderer has a fluent API, so you can chain the commands, like LitRenderer.of().withProperty().withProperty().withFunction()…​

The withFunction() handler can also receive more data in addition to the item. To pass additional data from client to the server-side handler, you need to invoke the function in the Lit template with the desired extra parameters. The additional data can be accessed via the second handler parameter — of type JsonArray.

Below is an example of this:

grid.addColumn(LitRenderer.<Person>of(
     "<input .value=\"${item.profession}\" @change=\"${e => changed(e.target.value)}\">")
    .withFunction("changed", (person, args) -> {
        String profession = args.getString(0);
        person.setProfession(profession);
        grid.getDataProvider().refreshItem(person);
    }).withProperty("profession", Person::getProfession));

The functions defined by the withFunction() method can be called with any number of additional parameters. The additional argument of type String (the updated profession) is obtained from the second handler parameter with args.getString(0), where the number is the index of the argument in the JsonArray.

Accessing Model Properties

In addition to the most commonly used item and index, Grid has the following meta properties associated with each item. You can access these properties in the template via the model object.

model.expanded

Indicates whether the item is expanded or collapsed (relevant only for TreeGrid).

model.level

Indicates the the hierarchy level of the item (relevant only for TreeGrid).

model.selected

Indicates whether the item is selected or not.

model.detailsOpened

Indicates whether the details row for the item is opened or closed.

Example: Creating a custom tree toggle for the TreeGrid

// The click listener needs to check if the event gets canceled (by
// vaadin-grid-tree-toggle) and only invoke the callback if it does.
// vaadin-grid-tree-toggle will cancel the event if the user clicks on
// a non-focusable element inside the toggle.
var clickListener = "e => requestAnimationFrame(() => { e.defaultPrevented && onClick(e) })";

grid.addColumn(LitRenderer.<Person> of(
    "<vaadin-grid-tree-toggle @click=${" + clickListener + "} .leaf=${item.leaf} .expanded=${model.expanded} .level=${model.level}>"
            + "${item.name}</vaadin-grid-tree-toggle>")
    .withProperty("leaf",
            item -> !grid.getDataCommunicator().hasChildren(item))
    .withProperty("name",
            item -> item.getName())
    .withFunction("onClick", item -> {
        if (grid.getDataCommunicator().hasChildren(item)) {
            if (grid.isExpanded(item)) {
                grid.collapse(item);
            } else {
                grid.expand(item);
            }
        }
    }));

Using Component Renderers

You can use any component in the grid cells by providing a ComponentRenderer for a column. To define how the component is generated for each item, you need to pass a Function for the ComponentRenderer.

For example, to add a column that contains a different icon depending on the person’s gender, you would do something like this:

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

grid.addColumn(new ComponentRenderer<>(person -> {
    if (person.getGender() == Gender.MALE) {
        return VaadinIcon.MALE.create();
    } else {
        return VaadinIcon.FEMALE.create();
    }
})).setHeader("Gender");

It’s also possible to provide a separate Supplier to create the component, and a Consumer to configure it for each item.

The example below uses ComponentRenderer with a Consumer:

SerializableBiConsumer<Div, Person> consumer =
        (div, person) -> div.setText(person.getName());
grid.addColumn(
        new ComponentRenderer<>(Div::new, consumer))
    .setHeader("Name");

If the component is the same for each item, you only need to provide the Supplier.

The example here is using ComponentRenderer with a Supplier:

grid.addColumn(
    new ComponentRenderer<>(
             () -> VaadinIcon.ARROW_LEFT.create()));

You can create complex content for the grid cells by using the component APIs.

This example is using ComponentRenderer to create complex content that listens for events and wraps multiple components in layouts:

grid.addColumn(new ComponentRenderer<>(person -> {

    // text field for entering a new name for the person
    TextField name = new TextField("Name");
    name.setValue(person.getName());

    // button for saving the name to backend
    Button update = new Button("Update", event -> {
        person.setName(name.getValue());
        grid.getDataProvider().refreshItem(person);
    });

    // button that removes the item
    Button remove = new Button("Remove", event -> {
        ListDataProvider<Person> dataProvider =
            (ListDataProvider<Person>) grid
                .getDataProvider();
        dataProvider.getItems().remove(person);
        dataProvider.refreshAll();
    });

    // layouts for placing the text field on top
    // of the buttons
    HorizontalLayout buttons =
            new HorizontalLayout(update, remove);
    return new VerticalLayout(name, buttons);
})).setHeader("Actions");
Note
addComponentColumn() is a shorthand for addColumn() with a ComponentRenderer.

Editing grid items requires refreshing the grid’s DataProvider. The reasoning is the same as for Handling Events mentioned earlier. See Data Providers for more.

Enabling Expanding Rows

The Grid supports expanding rows that reveal more details about the items. The additional information is hidden, unless the user chooses to reveal it, keeping the grid appearance clean and simple, while simultaneously allowing detailed explanations.

You can enable expanding rows using the setItemDetailsRenderer() method, which allows either a LitRenderer or a ComponentRenderer to define how the details are rendered.

Below is an example using the setItemDetailsRenderer() method with a ComponentRenderer:

grid.setItemDetailsRenderer(
    new ComponentRenderer<>(person -> {
        VerticalLayout layout = new VerticalLayout();
        layout.add(new Label("Address: " +
                person.getAddress().getStreet() + " " +
                person.getAddress().getNumber()));
        layout.add(new Label("Year of birth: " +
                person.getYearOfBirth()));
        return layout;
}));

By default, the row’s detail opens by clicking the row. Clicking the row again, or clicking another row to open its detail, automatically closes the first row’s detail. You can disable this behavior by calling the grid.setDetailsVisibleOnClick(false) method. You can show and hide item details programmatically using the setDetailsVisible() method, and test whether an item’s detail is visible using the isDetailsVisible() method.

Note
By default, items are selected by clicking them. If you want the click action only to show the item details without selection, you need to use the grid.setSelectionMode(SelectionMode.NONE) method.

Column Sorting

Column sorting in the grid has an order to its process. The first click on the column header sorts the column. The second click reverses the sort order. And the third click resets the column to its unsorted state.

If multi-sorting is enabled, the user can sort by multiple columns. Then the first click sorts the first column, while subsequent clicks on a second and more sortable column headers, add secondary and more sort criteria.

Enabling Column Sorting

For columns that have been created from a property name (grid.addColumn("firstName")), sorting is enabled by default — if the type of the property implements Comparable. In general this only applies to grids that have been created from a class name (new Grid(Person.class)).

For columns that have been created from a ValueProvider (grid.addColumn(Person::getFirstName)), or from a renderer such as LitRenderer (grid.addColumn(new LitRenderer<Person>(…​))), sorting is not enabled by default.

Sorting for a column can be enabled or disabled manually:

// Enabled sorting for a column
column.setSortable(true);

// Disable sorting for a column
column.setSortable(false);

// Check whether a column is sortable
var isSortable = column.isSortable();

Depending on how the column was created, it may need additional configuration. In order to determine this, you need to consider the two sorting mechanisms supported by Grid: in-memory sorting or backend sorting.

In-memory sorting is sorting that is automatically applied by the framework when using an in-memory data provider. Items are sorted using a Comparator that can either be created automatically by the column or has to be specified manually by the developer.

Backend sorting is applied by providing a list of QuerySortOrder objects a custom DataProvider implemented by the developer. Sort order objects contain the property names that the developer must use for sorting items in their backend, such as a database. See Data Providers for more.

With that in mind, you can determine which columns are automatically configured for in-memory or backend sorting:

  • For columns created from a property name, in-memory sorting is automatically configured if the type of the property implements Comparable. Otherwise, a custom comparator must be provided by the developer. Backend sorting is configured automatically in any case, as the column knows the property name to pass to the data provider.

  • For columns created from a ValueProvider, only in-memory sorting is configured automatically, where the value provider is used to build a comparator. Backend sorting is not configured automatically, as the column does not know which property name to pass to the data provider.

  • For columns created from a renderer, neither in-memory sorting, nor backend sorting is configured automatically. The column cannot create a comparator from the renderer, nor does it know the property name to pass to the data provider.

If either sort mechanism is not configured automatically, it can be configured manually as explained in the following sections.

Configuring In-Memory Sorting

For in-memory sorting, a custom comparator can be configured using Column.setComparator. Use this if a column is not automatically configured for in-memory sorting, or if you want to customize the in-memory sorting behavior.

The example below shows how to configure a comparator for a column using a LitRenderer. The renderer shows the person’s name and email address, for sorting only the email address is used.

grid.addColumn(LitRenderer.<Person>of(
        "<div>${item.name}<br>" +
        "<small>${item.email}</small></div>")
        .withProperty("name", Person::getName)
        .withProperty("email", Person::getEmail))
    .setComparator(Person::getEmail)
    .setHeader("Person");

This next example is customizing the in-memory sorting for a column created from a value provider by implementing a comparator that is not case-sensitive:

grid.addColumn(Person::getName)
    .setComparator((person1, person2) ->
        person1.getName()
            .compareToIgnoreCase(person2.getName()))
    .setHeader("Name");

Configuring Backend Sorting

For backend sorting, the sort properties that are passed to a data provider can be configured using Column.setSortProperty. The method allows for providing multiple sort properties, which are passed to the data provider in the specified order. Use this if a column is not automatically configured for backend sorting, or if you want to customize the backend sorting behavior.

The example here is configuring sort properties for a column created from a value provider:

grid.addColumn(Person::getName)
        .setSortProperty("name", "email")
        .setHeader("Person");

The next example is setting a sort property for a column using a LitRenderer. The renderer shows the person’s name and email address, for sorting only the email address is used.

grid.addColumn(LitRenderer.<Person>of(
        "<div>${item.name}<br>" +
        "<small>${item.email}</small></div>")
        .withProperty("name", Person::getName)
        .withProperty("email", Person::getEmail))
    .setSortProperty("email")
    .setHeader("Person");

An alternative way to configure backend sorting is to use a SortOrderProvider, which is called on demand when the sort order is changed. Use this if you need fine-grained control over how QuerySortOrder objects are created and sent to the DataProvider.

This example is defining a SortOrderProvider for backend sorting:

grid.addColumn(Person::getName)
    .setSortOrderProvider(direction -> Arrays
        .asList(new QuerySortOrder("name", direction),
                new QuerySortOrder("email", direction))
        .stream())
    .setHeader("Person");

Enabling Multi-Sorting

To allow users to sort by more than one column at the same time, you can use the setMultiSort() method to enable multi-sorting at the grid level.

This example is using the setMultiSort() method to enable multi-sorting:

grid.setMultiSort(true);

Receiving Sort Events

You can add a SortListener to the grid to receive general sort events. Each time sorting of the grid is changed, an event is fired. You can access the DataCommunicator to receive the sorting details.

The example below is using the addSortListener() method to add a SortListener:

grid.addSortListener(event -> {
    String currentSortOrder = grid.getDataCommunicator()
            .getBackEndSorting().stream()
            .map(querySortOrder -> String.format(
                   "{sort property: %s, direction: %s}",
                   querySortOrder.getSorted(),
                   querySortOrder.getDirection()))
            .collect(Collectors.joining(", "));
    System.out.println(String.format(
            "Current sort order: %s. User-clicked: %s.",
            currentSortOrder, event.isFromClient()));
});

Styling the Grid

Styling the Grid component or any Vaadin component requires some Web Component and shadow-DOM knowledge. Styling depends on the component’s position in the DOM: If the component is in the shadow DOM, you can apply styling within the component or using variables. If the component is in the "normal" DOM (i.e., not in the shadow DOM), normal CSS styling applies.

Additionally, the Grid supports the theme attribute, which allows you to customize component styling.

Celebrity grid is used in styling examples below:

Grid<Celebrity> grid = new Grid<>();
grid.setItems(Celebrity.getPeople());
grid.addClassName("styled");
grid.addColumn(new ComponentRenderer<>(person -> {
    TextField textField = new TextField();
    textField.setValue(person.getName());
    textField.addClassName("style-" +
            person.getGender());
    textField.addValueChangeListener(
        event -> person.setName(event.getValue()));
    return textField;
})).setHeader("Name");

grid.addColumn(new ComponentRenderer<>(person -> {
    DatePicker datePicker = new DatePicker();
    datePicker.setValue(person.getDob());
    datePicker.addValueChangeListener(event -> {
        person.setDob(event.getValue());
    });
    datePicker.addClassName("style-" +
            person.getGender());
    return datePicker;
})).setHeader("DOB");

grid.addColumn(new ComponentRenderer<>(person -> {
    Image image = new Image(person.getImgUrl(),
            person.getName());
    return image;
})).setHeader("Image");

Styling with the Theme Property

The default Lumo theme includes different variations that you can use to style the grid. You can provide one or more variations.

This example uses the addThemeVariants() method to define theme variations for the grid:

grid.addThemeVariants(GridVariant.LUMO_NO_ROW_BORDERS,
        GridVariant.LUMO_NO_BORDER, GridVariant.LUMO_ROW_STRIPES);

Styling with CSS

You can use normal CSS styling for the content in grid cells. Although the Grid component itself is in the shadow DOM, the actual values (i.e., cell contents) are in slots and therefore in the light DOM.

For example, you can set the maximum size for images in the grid cells like so:

vaadin-grid vaadin-grid-cell-content img {
    max-height: 4em;
}

The vaadin-grid-cell-content is in the light DOM, and the selector vaadin-grid vaadin-grid-cell-content points to all of the grid’s cells.

You can also use a class to apply styles to a specific component instance. For example, to apply rounded borders and center images in a grid with a "styled" class name, you would do something like this:

vaadin-grid.styled vaadin-grid-cell-content img {
    border-radius: 2em;
    margin-left: 50%;
    transform: translate(-50%);
}

To learn how to customize component styles, see Styling Vaadin Components.

F478ED56-7248-4788-BC75-4E05605EB33F