Grid
Although most grid content is typically plain text, cell renderers can be used to render the contents of specific columns using components and native HTML elements.
A cell renderer is a function that generates the HTML to be rendered into a cell, defined through Java, Lit or React code.
new tab
@state()
private items: Person[] | undefined;
protected override async firstUpdated() {
const { people } = await getPeople();
this.items = people;
}
protected override render() {
return html`
<vaadin-grid .items="${this.items}">
<vaadin-grid-selection-column></vaadin-grid-selection-column>
<vaadin-grid-column
header="Employee"
flex-grow="0"
auto-width
${columnBodyRenderer(this.employeeRenderer, [])}
></vaadin-grid-column>
<vaadin-grid-column path="profession" auto-width></vaadin-grid-column>
<vaadin-grid-column
header="Status"
auto-width
${columnBodyRenderer(this.statusRenderer, [])}
></vaadin-grid-column>
</vaadin-grid>
`;
}
private employeeRenderer: GridColumnBodyLitRenderer<Person> = (person) => html`
<vaadin-horizontal-layout style="align-items: center;" theme="spacing">
<vaadin-avatar
img="${person.pictureUrl}"
name="${person.firstName} ${person.lastName}"
></vaadin-avatar>
<vaadin-vertical-layout style="line-height: var(--lumo-line-height-m);">
<span>${person.firstName} ${person.lastName}</span>
<span style="font-size: var(--lumo-font-size-s); color: var(--lumo-secondary-text-color);">
${person.email}
</span>
</vaadin-vertical-layout>
</vaadin-horizontal-layout>
`;
private statusRenderer: GridColumnBodyLitRenderer<Person> = ({ status }) => html`
<span theme="badge ${status === 'Available' ? 'success' : 'error'}">${status}</span>
`;
Grid Cell Renderers in Flow
The Grid API in Flow supports three types of cell renderer:
-
Basic Renderer: Formatted plain text values and native HTML buttons.
-
Component Renderer: Renders content using UI components; Easy to use, but can have an adverse effect on performance.
-
Lit Renderer: Renders content using HTML markup and Lit data-binding syntax; More lightweight than component renderers.
The example above demonstrates a Lit Renderer in the first column and a Component Renderer in the last column.
Basic Renderers
The following basic renderers can be used in the Flow Grid component.
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 configure 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
})
);
Component Renderer
Component renderers are easy to build, but slow to render as they generate a component for each item in the dataset for a given column. The rendered components are fully controllable on the server side.
For each rendered cell, Grid creates a corresponding component instance on the server side. A dataset of 100 items with 10 columns using a component renderer produces up to 1,000 components that need to be managed. The more components you use in a component renderer, the greater the impact on performance.
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");
Incidentally, addComponentColumn()
is a shorthand for addColumn()
with a ComponentRenderer
.
Editing grid items requires refreshing the grid’s DataProvider
. See Data Providers for more details.
Lit Renderer
Lit renders quickly, but requires you to write HTML code. Components can be used in Lit renderers through their custom HTML tags.
Lit templates are immutable: the state of the components can’t be managed on the server side. However, the template can have different representations, depending on the state of the item.
The only data sent from the server — other than the template itself, which is sent only once — is the extra name property of each item.
Lit templates do support event handling on the server side. However, you can’t, for example, disable or change the text of a button from the event handler. For such situations, use an editor instead.
With Lit renderers, the server doesn’t keep track of the components in each cell. It only manages the state of the items in each row. The client doesn’t have to wait for the server to send missing information about what needs rendering. It can use the template to render all of the cells it requires.
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.
Custom Properties in Lit Renderers
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 in Lit Renderers
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 in Lit Renderers
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 in Lit Renderers
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’ll 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 (e.g., 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’ll 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. This is relevant only for
TreeGrid
. model.level
-
Indicates the hierarchy level of the item. This is 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.
The example below shows how to create 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);
}
}
}));