diff --git a/FEATURE_ROW_ACTIONS.md b/FEATURE_ROW_ACTIONS.md new file mode 100644 index 0000000..688dd3e --- /dev/null +++ b/FEATURE_ROW_ACTIONS.md @@ -0,0 +1,73 @@ +# Feature: Row Actions + +Row actions are buttons or menu items displayed in a dedicated actions column, created and managed by `EasyGrid` on the wrapped grid. + +## API + +### `EasyGrid` methods + +```java +// Add an action button (label + icon) +EasyRowAction addRowAction(String label, VaadinIcon icon, SerializableConsumer handler); + +// Add an action button with a theme variant +EasyRowAction addRowAction(String label, VaadinIcon icon, ButtonVariant variant, SerializableConsumer handler); + +// Render all actions as a context menu (overflow menu) instead of inline buttons +void setRowActionsAsMenu(boolean asMenu); + +// Access the underlying Grid.Column for header, width, freezing, etc. +Grid.Column getActionsColumn(); +``` + +### `EasyRowAction` + +```java +public class EasyRowAction { + // Conditional visibility + EasyRowAction withVisibleWhen(SerializablePredicate predicate); + + // Conditional enablement + EasyRowAction withEnabledWhen(SerializablePredicate predicate); + + // Tooltip + EasyRowAction withTooltip(String tooltip); + EasyRowAction withTooltip(SerializableFunction tooltipProvider); + + // Confirmation dialog before executing the action + EasyRowAction withConfirmation(String message); + EasyRowAction withConfirmation(String title, String message); +} +``` + +## Usage + +```java +// Inline action buttons +easyGrid.addRowAction("Edit", VaadinIcon.EDIT, person -> { + editPerson(person); +}); + +easyGrid.addRowAction("Delete", VaadinIcon.TRASH, ButtonVariant.LUMO_ERROR, person -> { + personService.delete(person); + grid.getDataProvider().refreshAll(); +}).withConfirmation("Are you sure you want to delete this person?"); + +// Actions as a context menu (overflow menu) instead of inline buttons +easyGrid.setRowActionsAsMenu(true); + +// Conditional visibility +easyGrid.addRowAction("Activate", VaadinIcon.CHECK, person -> { + personService.activate(person); +}).withVisibleWhen(person -> !person.isActive()); + +easyGrid.addRowAction("Deactivate", VaadinIcon.CLOSE, person -> { + personService.deactivate(person); +}).withVisibleWhen(person -> person.isActive()); + +// Configure the actions column via the underlying Grid.Column +easyGrid.getActionsColumn() + .setHeader("Actions") + .setWidth("150px") + .setFrozenToEnd(true); +``` diff --git a/SPECIFICATIONS.md b/SPECIFICATIONS.md index 0994cd2..3d42912 100644 --- a/SPECIFICATIONS.md +++ b/SPECIFICATIONS.md @@ -2,13 +2,15 @@ ## 1. Overview -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. +The Easy Grid Add-on is a configuration helper that wraps an externally instantiated Vaadin `Grid`. It uses reflection to discover bean properties, maps them to appropriately typed and formatted columns, and provides a clean Java API for controlling column ordering and type-specific rendering. + +`EasyGrid` is **not** a component — it configures an existing `Grid` that was created and managed by the caller. Data binding, selection, events, and all other standard `Grid` features are accessed directly on the wrapped `Grid` instance. ## 2. Core Concepts ### 2.1 Automatic Column Discovery -Given a POJO class `T`, `EasyGrid` introspects its properties (via getter/setter conventions) and creates a `Grid.Column` for each one, with appropriate renderers and sorting behavior. +Given a POJO class `T`, `EasyGrid` introspects its properties (via getter/setter conventions) and creates a `Grid.Column` for each one on the wrapped grid, with appropriate renderers and sorting behavior. ### 2.2 Type-to-Renderer Mapping @@ -31,200 +33,84 @@ Custom type mappings can be registered globally or per-grid instance. ### 2.3 Sorting -All columns backed by `Comparable` property types are sortable by default. Multi-column sorting is supported. Sorting can be disabled per column. - -### 2.4 Data Binding - -`EasyGrid` wraps Vaadin's `Grid` and supports all standard data provider mechanisms: - -- In-memory lists (`setItems(List)`) -- Lazy data providers (`setDataProvider(DataProvider)`) -- Callback data providers for backend integration +All columns backed by `Comparable` property types are sortable by default. Multi-column sorting is supported. ## 3. API Design ### 3.1 Construction +`EasyGrid` takes an existing `Grid` instance and a bean class. The caller retains full ownership of the grid. + ```java // Basic: all discovered properties become columns -EasyGrid grid = new EasyGrid<>(Person.class); +Grid grid = new Grid<>(); +EasyGrid easyGrid = new EasyGrid<>(grid, Person.class); grid.setItems(personService.findAll()); +add(grid); // Selective: only specified properties become columns, in order -EasyGrid grid = new EasyGrid<>(Person.class, "firstName", "lastName", "email", "age"); +Grid grid = new Grid<>(); +EasyGrid easyGrid = new EasyGrid<>(grid, Person.class, "firstName", "lastName", "email", "age"); grid.setItems(personService.findAll()); +add(grid); ``` -### 3.2 Column Configuration — Fluent API +### 3.2 Column Ordering and Visibility ```java -EasyGrid 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); -``` +easyGrid.setColumnOrder("firstName", "lastName", "email", "birthDate", "age"); -### 3.3 Column Configuration Wrapper — `EasyColumnConfig` - -The `getColumnConfig(String propertyName)` method returns a fluent wrapper: - -```java -public class EasyColumnConfig { - // Header - EasyColumnConfig withHeader(String header); - EasyColumnConfig withHeader(Component headerComponent); - - // Footer - EasyColumnConfig withFooter(String footer); - EasyColumnConfig withFooter(Component footerComponent); - - // Sizing - EasyColumnConfig withWidth(String width); - EasyColumnConfig withFlexGrow(int flexGrow); - EasyColumnConfig withAutoWidth(boolean autoWidth); - EasyColumnConfig withResizable(boolean resizable); - - // Sorting - EasyColumnConfig withSortable(boolean sortable); - EasyColumnConfig withSortProperty(String... properties); - - // Alignment - EasyColumnConfig withTextAlign(ColumnTextAlign align); - - // Freezing - EasyColumnConfig withFrozen(boolean frozen); - EasyColumnConfig withFrozenToEnd(boolean frozen); - - // Visibility - EasyColumnConfig withVisible(boolean visible); - - // Custom rendering - EasyColumnConfig withRenderer(Renderer renderer); - EasyColumnConfig withComponentRenderer(SerializableFunction componentProvider); - EasyColumnConfig withValueFormatter(SerializableFunction formatter); - - // Type-specific formatting - EasyColumnConfig withDateFormat(String pattern); - EasyColumnConfig withDateTimeFormat(String pattern); - EasyColumnConfig withNumberFormat(String pattern); - EasyColumnConfig withBooleanLabels(String trueLabel, String falseLabel); - - // Access underlying Vaadin column - Grid.Column getColumn(); -} +// Hide columns +easyGrid.hideColumns("id", "createdAt", "updatedAt"); ``` -### 3.4 Row Actions - -Row actions are buttons or menu items displayed in an actions column, typically at the end of each row. - -```java -// 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); -``` +### 3.3 Column Configuration — `EasyColumn` -### 3.5 Row Action Wrapper — `EasyRowAction` +`getColumnConfig(String propertyName)` returns a wrapper that provides type-specific formatting. For all other column properties (header, footer, width, alignment, sorting, freezing, visibility, renderer), use `getColumn()` to access the underlying `Grid.Column` directly. ```java -public class EasyRowAction { - // Conditional visibility - EasyRowAction withVisibleWhen(SerializablePredicate predicate); +public class EasyColumn { - // Conditional enablement - EasyRowAction withEnabledWhen(SerializablePredicate predicate); + // Type-specific formatting (EasyGrid-managed, applied to the column renderer) + EasyColumn withValueFormatter(SerializableFunction formatter); + EasyColumn withDateFormat(String pattern); + EasyColumn withDateTimeFormat(String pattern); + EasyColumn withNumberFormat(String pattern); + EasyColumn withBooleanLabels(String trueLabel, String falseLabel); - // Tooltip - EasyRowAction withTooltip(String tooltip); - EasyRowAction withTooltip(SerializableFunction tooltipProvider); - - // Confirmation dialog before executing the action - EasyRowAction withConfirmation(String message); - EasyRowAction withConfirmation(String title, String message); + // Access underlying Vaadin column for all other configuration + Grid.Column getColumn(); } ``` -### 3.6 Grid Exporter Integration - -The add-on integrates with the Grid Exporter Add-on to provide data export capabilities. +Usage example: ```java -// Enable export with default formats (Excel, CSV, Docx) -grid.enableExport(); +easyGrid.getColumnConfig("birthDate") + .withDateFormat("dd/MM/yyyy"); -// Enable specific export formats -grid.enableExport(ExportFormat.EXCEL, ExportFormat.CSV); +easyGrid.getColumnConfig("subscriber") + .withBooleanLabels("Active", "Inactive"); -// Configure export -grid.getExportConfig() - .withFileName("person-report") - .withSheetName("People") - .withTitle("Person Report"); +// Use getColumn() for standard Grid.Column configuration +easyGrid.getColumnConfig("firstName").getColumn() + .setHeader("First Name") + .setFrozen(true) + .setWidth("200px"); +``` -// Custom column export configuration (e.g., different header for export) -grid.getColumnConfig("firstName").withExportHeader("Given Name"); +### 3.4 Row Actions -// Exclude a column from export (e.g., the actions column) -grid.getColumnConfig("actions").withExportable(false); -``` +See [FEATURE_ROW_ACTIONS.md](FEATURE_ROW_ACTIONS.md). -### 3.7 Global Type Configuration +### 3.5 Global Type Configuration Register custom column configurations that apply to all `EasyGrid` instances: ```java // Register a global formatter for a custom type EasyGrid.registerTypeConfig(Money.class, config -> { - config.withTextAlign(ColumnTextAlign.END); config.withValueFormatter(money -> money.getCurrency() + " " + money.getAmount()); }); @@ -236,62 +122,9 @@ EasyGrid.registerTypeConfig(Address.class, config -> { }); ``` -### 3.8 Selection - -```java -// 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 selected = event.getAllSelectedItems(); - // bulk action -}); -``` - -### 3.9 Filtering - -`EasyGrid` supports in-memory filtering for list-based data providers: - -```java -// 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 filter); -``` - -### 3.10 Events - -```java -// 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 -}); -``` - ## 4. Default Header Generation -When no explicit header is provided, headers are auto-generated from property names using camelCase-to-title-case conversion: +When a column is added by `EasyGrid` and no explicit header has been set on it, the header is auto-generated from the property name using camelCase-to-title-case conversion: | Property Name | Generated Header | |--------------|-----------------| @@ -306,62 +139,52 @@ When no explicit header is provided, headers are auto-generated from property na Nested properties can be referenced using dot notation: ```java -grid.setColumnOrder("firstName", "lastName", "address.city", "address.postalCode"); +EasyGrid easyGrid = new EasyGrid<>(grid, Person.class, + "firstName", "lastName", "address.city", "address.postalCode"); -grid.getColumnConfig("address.city") - .withHeader("City") - .withSortable(true); +easyGrid.getColumnConfig("address.city").getColumn() + .setHeader("City") + .setSortable(true); ``` ## 6. Serialization -`EasyGrid` must be fully serializable for Vaadin session persistence. All internal state, column configurations, action handlers, and renderers must be serializable. +`EasyGrid` must be fully serializable for Vaadin session persistence. All internal state, column configurations, and renderers must be serializable. ## 7. Usage Example — Complete ```java -// Minimal usage -EasyGrid simpleGrid = new EasyGrid<>(Person.class); -simpleGrid.setItems(personService.findAll()); -add(simpleGrid); - -// Customized usage -EasyGrid 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()); +Grid grid = new Grid<>(); +EasyGrid easyGrid = new EasyGrid<>(grid, Person.class); +// Column ordering and visibility +easyGrid.setColumnOrder("firstName", "lastName", "email", "birthDate", "age", "subscriber"); +easyGrid.hideColumns("id", "createdAt", "updatedAt"); + +// Type-specific formatting +easyGrid.getColumnConfig("birthDate").withDateFormat("dd/MM/yyyy"); +easyGrid.getColumnConfig("subscriber").withBooleanLabels("Yes", "No"); + +// Standard Grid.Column configuration via getColumn() +easyGrid.getColumnConfig("firstName").getColumn() + .setHeader("Name") + .setFrozen(true); +easyGrid.getColumnConfig("age").getColumn() + .setTextAlign(ColumnTextAlign.END); + +// Data binding and adding to layout — done on the Grid directly +grid.setItems(personService.findAll()); add(grid); ``` ## 8. Dependencies - Vaadin Flow (24.x) -- Grid Exporter Add-on (for export functionality) - Lombok (per Flowing Code convention for new add-ons) ## 9. Non-Goals (Out of Scope) +- Data binding, selection, events, filtering, sorting configuration — use `Grid` or GridHelper directly - 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)