The Easy Grid Add-on is a Vaadin Flow component that automatically generates a fully functional, sortable data grid from a Java POJO definition. It uses reflection to discover bean properties, maps them to appropriately typed and formatted columns, and provides a clean Java API for controlling column visibility, ordering, rendering, row actions, and data export via the Grid Exporter Add-on.
Given a POJO class T, EasyGrid<T> introspects its properties (via getter/setter conventions) and creates a Grid.Column for each one, with appropriate renderers and sorting behavior.
Each Java type maps to a default column renderer and configuration:
| Java Type | Renderer | Alignment | Sorting |
|---|---|---|---|
String |
TextRenderer |
Start | Alphabetical |
Integer, int |
NumberRenderer |
End | Numeric |
Long, long |
NumberRenderer |
End | Numeric |
Double, double, Float, float |
NumberRenderer |
End | Numeric |
BigDecimal |
NumberRenderer |
End | Numeric |
Boolean, boolean |
TextRenderer ("Yes"/"No") |
Center | — |
LocalDate |
LocalDateRenderer |
Start | Chronological |
LocalDateTime |
LocalDateTimeRenderer |
Start | Chronological |
LocalTime |
TextRenderer (formatted) |
Start | Chronological |
Enum<?> |
TextRenderer (name) |
Start | Alphabetical |
Custom type mappings can be registered globally or per-grid instance.
All columns backed by Comparable property types are sortable by default. Multi-column sorting is supported. Sorting can be disabled per column.
EasyGrid<T> wraps Vaadin's Grid<T> and supports all standard data provider mechanisms:
- In-memory lists (
setItems(List<T>)) - Lazy data providers (
setDataProvider(DataProvider<T, ?>)) - Callback data providers for backend integration
// Basic: all discovered properties become columns
EasyGrid<Person> grid = new EasyGrid<>(Person.class);
grid.setItems(personService.findAll());
// Selective: only specified properties become columns, in order
EasyGrid<Person> grid = new EasyGrid<>(Person.class, "firstName", "lastName", "email", "age");
grid.setItems(personService.findAll());EasyGrid<Person> grid = new EasyGrid<>(Person.class);
// Configure individual columns
grid.getColumnConfig("firstName")
.withHeader("First Name")
.withSortable(true)
.withWidth("200px")
.withResizable(true);
grid.getColumnConfig("age")
.withHeader("Age")
.withTextAlign(ColumnTextAlign.END);
grid.getColumnConfig("birthDate")
.withHeader("Date of Birth")
.withDateFormat("dd/MM/yyyy");
grid.getColumnConfig("subscriber")
.withHeader("Subscribed")
.withBooleanLabels("Active", "Inactive");
// Hide columns
grid.hideColumns("id", "createdAt", "updatedAt");
// Set column order (only listed columns shown, in this order)
grid.setColumnOrder("firstName", "lastName", "email", "birthDate", "age");
// Freeze columns (always visible when scrolling horizontally)
grid.getColumnConfig("firstName").withFrozen(true);The getColumnConfig(String propertyName) method returns a fluent wrapper:
public class EasyColumnConfig<T, V> {
// Header
EasyColumnConfig<T, V> withHeader(String header);
EasyColumnConfig<T, V> withHeader(Component headerComponent);
// Footer
EasyColumnConfig<T, V> withFooter(String footer);
EasyColumnConfig<T, V> withFooter(Component footerComponent);
// Sizing
EasyColumnConfig<T, V> withWidth(String width);
EasyColumnConfig<T, V> withFlexGrow(int flexGrow);
EasyColumnConfig<T, V> withAutoWidth(boolean autoWidth);
EasyColumnConfig<T, V> withResizable(boolean resizable);
// Sorting
EasyColumnConfig<T, V> withSortable(boolean sortable);
EasyColumnConfig<T, V> withSortProperty(String... properties);
// Alignment
EasyColumnConfig<T, V> withTextAlign(ColumnTextAlign align);
// Freezing
EasyColumnConfig<T, V> withFrozen(boolean frozen);
EasyColumnConfig<T, V> withFrozenToEnd(boolean frozen);
// Visibility
EasyColumnConfig<T, V> withVisible(boolean visible);
// Custom rendering
EasyColumnConfig<T, V> withRenderer(Renderer<T> renderer);
EasyColumnConfig<T, V> withComponentRenderer(SerializableFunction<T, Component> componentProvider);
EasyColumnConfig<T, V> withValueFormatter(SerializableFunction<V, String> formatter);
// Type-specific formatting
EasyColumnConfig<T, V> withDateFormat(String pattern);
EasyColumnConfig<T, V> withDateTimeFormat(String pattern);
EasyColumnConfig<T, V> withNumberFormat(String pattern);
EasyColumnConfig<T, V> withBooleanLabels(String trueLabel, String falseLabel);
// Access underlying Vaadin column
Grid.Column<T> getColumn();
}Row actions are buttons or menu items displayed in an actions column, typically at the end of each row.
// Add action buttons per row
grid.addRowAction("Edit", VaadinIcon.EDIT, person -> {
editPerson(person);
});
grid.addRowAction("Delete", VaadinIcon.TRASH, ButtonVariant.LUMO_ERROR, person -> {
personService.delete(person);
grid.getDataProvider().refreshAll();
});
// Actions as a context menu (overflow menu) instead of inline buttons
grid.setRowActionsAsMenu(true);
// Conditional action visibility
grid.addRowAction("Activate", VaadinIcon.CHECK, person -> {
personService.activate(person);
}).withVisibleWhen(person -> !person.isActive());
grid.addRowAction("Deactivate", VaadinIcon.CLOSE, person -> {
personService.deactivate(person);
}).withVisibleWhen(person -> person.isActive());
// Configure the actions column
grid.getActionsColumnConfig()
.withHeader("Actions")
.withWidth("150px")
.withFrozenToEnd(true);public class EasyRowAction<T> {
// Conditional visibility
EasyRowAction<T> withVisibleWhen(SerializablePredicate<T> predicate);
// Conditional enablement
EasyRowAction<T> withEnabledWhen(SerializablePredicate<T> predicate);
// Tooltip
EasyRowAction<T> withTooltip(String tooltip);
EasyRowAction<T> withTooltip(SerializableFunction<T, String> tooltipProvider);
// Confirmation dialog before executing the action
EasyRowAction<T> withConfirmation(String message);
EasyRowAction<T> withConfirmation(String title, String message);
}The add-on integrates with the Grid Exporter Add-on to provide data export capabilities.
// Enable export with default formats (Excel, CSV, Docx)
grid.enableExport();
// Enable specific export formats
grid.enableExport(ExportFormat.EXCEL, ExportFormat.CSV);
// Configure export
grid.getExportConfig()
.withFileName("person-report")
.withSheetName("People")
.withTitle("Person Report");
// Custom column export configuration (e.g., different header for export)
grid.getColumnConfig("firstName").withExportHeader("Given Name");
// Exclude a column from export (e.g., the actions column)
grid.getColumnConfig("actions").withExportable(false);Register custom column configurations that apply to all EasyGrid instances:
// Register a global formatter for a custom type
EasyGrid.registerTypeConfig(Money.class, config -> {
config.withTextAlign(ColumnTextAlign.END);
config.withValueFormatter(money -> money.getCurrency() + " " + money.getAmount());
});
// Register a global renderer for nested types
EasyGrid.registerTypeConfig(Address.class, config -> {
config.withValueFormatter(address ->
address.getStreet() + ", " + address.getCity()
);
});// Single selection (default)
grid.setSelectionMode(Grid.SelectionMode.SINGLE);
grid.addSelectionListener(event -> {
event.getFirstSelectedItem().ifPresent(this::showDetails);
});
// Multi-selection
grid.setSelectionMode(Grid.SelectionMode.MULTI);
grid.addSelectionListener(event -> {
Set<Person> selected = event.getAllSelectedItems();
// bulk action
});EasyGrid<T> supports in-memory filtering for list-based data providers:
// Add a header filter row (auto-generated filter fields per column)
grid.enableHeaderFilters();
// Programmatic filtering
grid.setFilter(person ->
person.getAge() >= 18 && person.isActive()
);
// Combined with external filter (for use with EasyCRUD)
grid.setExternalFilter(SerializablePredicate<T> filter);// Row click
grid.addItemClickListener(event -> {
showDetails(event.getItem());
});
// Row double-click
grid.addItemDoubleClickListener(event -> {
editPerson(event.getItem());
});
// Sort change
grid.addSortListener(event -> {
// handle sort change
});When no explicit header is provided, headers are auto-generated from property names using camelCase-to-title-case conversion:
| Property Name | Generated Header |
|---|---|
firstName |
First Name |
lastName |
Last Name |
dateOfBirth |
Date Of Birth |
email |
Email |
isActive |
Is Active |
Nested properties can be referenced using dot notation:
grid.setColumnOrder("firstName", "lastName", "address.city", "address.postalCode");
grid.getColumnConfig("address.city")
.withHeader("City")
.withSortable(true);EasyGrid<T> must be fully serializable for Vaadin session persistence. All internal state, column configurations, action handlers, and renderers must be serializable.
// Minimal usage
EasyGrid<Person> simpleGrid = new EasyGrid<>(Person.class);
simpleGrid.setItems(personService.findAll());
add(simpleGrid);
// Customized usage
EasyGrid<Person> grid = new EasyGrid<>(Person.class);
// Configure columns
grid.setColumnOrder("firstName", "lastName", "email", "birthDate", "age", "subscriber");
grid.hideColumns("id", "createdAt", "updatedAt");
grid.getColumnConfig("firstName").withHeader("Name").withFrozen(true);
grid.getColumnConfig("birthDate").withHeader("Born").withDateFormat("dd/MM/yyyy");
grid.getColumnConfig("subscriber").withBooleanLabels("Yes", "No");
grid.getColumnConfig("age").withTextAlign(ColumnTextAlign.END);
// Row actions
grid.addRowAction("Edit", VaadinIcon.EDIT, this::editPerson);
grid.addRowAction("Delete", VaadinIcon.TRASH, ButtonVariant.LUMO_ERROR, person -> {
personService.delete(person);
grid.getDataProvider().refreshAll();
}).withConfirmation("Are you sure you want to delete this person?");
// Enable export
grid.enableExport();
grid.getExportConfig().withFileName("people-export");
// Set data
grid.setItems(personService.findAll());
add(grid);- Vaadin Flow (24.x)
- Grid Exporter Add-on (for export functionality)
- Lombok (per Flowing Code convention for new add-ons)
- Inline cell editing (use EasyForm for editing)
- Server-side data persistence
- Tree grid / hierarchical data
- Lazy loading (supported via Vaadin's DataProvider API but not auto-configured)