Skip to content

Commit bd4afd2

Browse files
authored
feat: initial implementation
1 parent f30af8c commit bd4afd2

59 files changed

Lines changed: 4854 additions & 380 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ types.d.ts
1717
/frontend/index.html
1818
vite.generated.ts
1919
vite.config.ts
20-
/src/main/dev-bundle
20+
/src/main/dev-bundle
21+
/.claude/settings.local.json

CONFIGURATION_RESOLUTION.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Configuration Resolution: Scope-First vs. Type-First
2+
3+
## The Two Strategies
4+
5+
Given `Foo extends Entity` with configurations at four points in the matrix:
6+
7+
| | Instance | Global |
8+
|---|---|---|
9+
| **Foo** | I·Foo | G·Foo |
10+
| **Entity** | I·Entity | G·Entity |
11+
12+
**Scope-first** exhausts all instance settings before looking at global ones:
13+
```
14+
I·Foo → I·Entity → G·Foo → G·Entity → Default
15+
```
16+
17+
**Type-first** exhausts all Foo-specific settings before looking at Entity:
18+
```
19+
I·Foo → G·Foo → I·Entity → G·Entity → Default
20+
```
21+
22+
Both agree on `I·Foo` being first and `G·Entity` being last. The dispute is the middle two.
23+
24+
---
25+
26+
## Where Scope-First Makes Sense
27+
28+
**Setting: `textAlign` or `nullRepresentation`**
29+
30+
A developer creates a reporting grid and writes:
31+
```java
32+
easyGrid.typeConfiguration(Entity.class).setTextAlign(RIGHT);
33+
```
34+
The intent is *"every Entity-like column in this grid should be right-aligned."* Since `Foo IS-AN Entity`, Foo columns should also be right-aligned. Instance context wins: the developer explicitly opted every Entity column into this layout decision, and Foo, being substitutable for Entity, should inherit it.
35+
36+
If type-first were applied, `G·Foo` would sit between `I·Foo` and `I·Entity`. An unrelated global renderer for Foo would silently block the instance-level alignment — which is the opposite of what the developer intended.
37+
38+
---
39+
40+
## Where Type-First Makes Sense
41+
42+
**Setting: `rendererFactory`**
43+
44+
Globally, `Foo` has a specialized renderer registered:
45+
```java
46+
GlobalEasyGridConfiguration.forType(Foo.class)
47+
.setRendererFactory(FooRenderers.of(...));
48+
```
49+
Independently, an instance configures Entity columns with a generic formatter:
50+
```java
51+
easyGrid.typeConfiguration(Entity.class).setFormatter(e -> e.getId().toString());
52+
```
53+
54+
Under scope-first, `I·Entity` wins over `G·Foo`. The Foo-specific renderer — which encodes *what a Foo looks like*, arguably a type invariant — gets silently replaced by a generic Entity formatter that the developer may not have intended to apply to Foo.
55+
56+
Type-first preserves the type contract: `G·Foo` sits above `I·Entity`, so Foo's rendering semantics are honoured even across scope boundaries.
57+
58+
---
59+
60+
## Liskov Substitution Principle
61+
62+
LSP says: a `Foo` must be usable wherever an `Entity` is expected, without callers needing to know the difference.
63+
64+
Applied to configuration, it has two implications that pull in opposite directions:
65+
66+
**LSP supports scope-first for behavioural settings.** If you configure `Entity` at instance level — null representation, alignment, a locale-specific formatter — you are specifying how *this grid* handles all Entity values. A `Foo` substituting for `Entity` should satisfy those same observable postconditions. Refusing to inherit `I·Entity` because Foo has a `G·Foo` entry would mean the grid behaves differently for `Foo` vs `Entity` in ways the calling code did not anticipate.
67+
68+
**LSP supports type-first for type-defining settings.** LSP also requires that subtypes honour their own invariants. If `Foo` has a type-level rendering contract — *this is how a Foo is displayed* — then substituting a generic `Entity` renderer for it violates the Foo invariant. The renderer is not just a postcondition on the grid; it is a property of `Foo` itself.
69+
70+
The tension resolves in favour of **scope-first for all properties**. The type-first argument rests on the assumption that `I·Entity` displaces `G·Foo` unintentionally — but instance configuration is always deliberate. A developer who writes `forType(Entity.class).setFormatter(...)` knows `Foo IS-AN Entity`; if they wanted Foo to keep its global renderer they would have set `I·Foo` separately. The argument collapses entirely at broad overrides such as `forType(Object.class).setFormatter(...)`, where the developer has unambiguously stated that everything in this grid uses this formatter and global type-specific registrations cannot claim precedence. **Global registrations are defaults. Instance registrations are decisions. Decisions outrank defaults regardless of type specificity.**
71+
72+
---
73+
74+
## Current Implementation
75+
76+
The implementation uses **scope-first** for all properties. For a `Foo extends Entity` column the effective chain is:
77+
78+
```
79+
per-column → I·Foo → I·Entity → I·Object → G·Foo → G·Entity → G·Object → Default
80+
```
81+
82+
This is built in two pieces:
83+
84+
**Instance chain**`EasyGridConfigurationClassMap.getOrCreate(Foo)` walks the Java class hierarchy within the same map, producing a `ColumnConfigurationImpl` chain:
85+
86+
```
87+
I·Foo(impl) → I·Entity(impl) → I·Object(impl)
88+
```
89+
90+
**Global chain**`GlobalEasyGridConfiguration.resolve(Foo)` similarly produces:
91+
92+
```
93+
G·Foo(impl) → G·Entity(impl) → G·Object(impl)
94+
```
95+
96+
**Bridge**`InstanceEasyGridConfiguration.forType(Foo)` wraps both into a `ColumnConfigurationLink`:
97+
98+
```
99+
ColumnConfigurationLink(primary = I·Foo chain, fallback = G·Foo chain)
100+
```
101+
102+
`ColumnConfigurationLink.get()` consults the primary chain first and only falls back to the global chain when the primary returns `null` for a given property.
103+
104+
**Per-column**`InstanceEasyGridConfiguration.resolve(Foo)` wraps the link in a fresh `ColumnConfigurationImpl` whose fields are overridden by column-level setters (`setNullRepresentation`, `setFormatter`, `setRendererFactory`).
105+
106+
**Default** — when the entire chain returns `null` for `getRendererFactory()`, `EasyColumn.createRenderer` applies a `ColumnConfigurationTextRenderer` with null-representation support as the last resort.

FEATURE_ROW_ACTIONS.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Feature: Row Actions (Roadmap)
2+
3+
> **Note:** This feature is planned for a future iteration and is not yet implemented.
4+
5+
# Feature: Row Actions
6+
7+
Row actions are buttons or menu items displayed in a dedicated actions column, created and managed by `EasyGrid` on the wrapped grid.
8+
9+
## API
10+
11+
### `EasyGrid<T>` methods
12+
13+
```java
14+
// Add an action button (label + icon)
15+
EasyRowAction<T> addRowAction(String label, VaadinIcon icon, SerializableConsumer<T> handler);
16+
17+
// Add an action button with a theme variant
18+
EasyRowAction<T> addRowAction(String label, VaadinIcon icon, ButtonVariant variant, SerializableConsumer<T> handler);
19+
20+
// Render all actions as a context menu (overflow menu) instead of inline buttons
21+
void setRowActionsAsMenu(boolean asMenu);
22+
23+
// Access the underlying Grid.Column for header, width, freezing, etc.
24+
Grid.Column<T> getActionsColumn();
25+
```
26+
27+
### `EasyRowAction<T>`
28+
29+
```java
30+
public class EasyRowAction<T> {
31+
// Conditional visibility
32+
EasyRowAction<T> withVisibleWhen(SerializablePredicate<T> predicate);
33+
34+
// Conditional enablement
35+
EasyRowAction<T> withEnabledWhen(SerializablePredicate<T> predicate);
36+
37+
// Tooltip
38+
EasyRowAction<T> withTooltip(String tooltip);
39+
EasyRowAction<T> withTooltip(SerializableFunction<T, String> tooltipProvider);
40+
41+
// Confirmation dialog before executing the action
42+
EasyRowAction<T> withConfirmation(String message);
43+
EasyRowAction<T> withConfirmation(String title, String message);
44+
}
45+
```
46+
47+
## Usage
48+
49+
```java
50+
// Inline action buttons
51+
easyGrid.addRowAction("Edit", VaadinIcon.EDIT, person -> {
52+
editPerson(person);
53+
});
54+
55+
easyGrid.addRowAction("Delete", VaadinIcon.TRASH, ButtonVariant.LUMO_ERROR, person -> {
56+
personService.delete(person);
57+
easyGrid.getDataProvider().refreshAll();
58+
}).withConfirmation("Are you sure you want to delete this person?");
59+
60+
// Actions as a context menu (overflow menu) instead of inline buttons
61+
easyGrid.setRowActionsAsMenu(true);
62+
63+
// Conditional visibility
64+
easyGrid.addRowAction("Activate", VaadinIcon.CHECK, person -> {
65+
personService.activate(person);
66+
}).withVisibleWhen(person -> !person.isActive());
67+
68+
easyGrid.addRowAction("Deactivate", VaadinIcon.CLOSE, person -> {
69+
personService.deactivate(person);
70+
}).withVisibleWhen(person -> person.isActive());
71+
72+
// Configure the actions column via the underlying Grid.Column
73+
easyGrid.getActionsColumn()
74+
.setHeader("Actions")
75+
.setWidth("150px")
76+
.setFrozenToEnd(true);
77+
```

README.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
1-
[![Published on Vaadin Directory](https://img.shields.io/badge/Vaadin%20Directory-published-00b4f0.svg)](https://vaadin.com/directory/component/easy-grid-addon)
2-
[![Stars on vaadin.com/directory](https://img.shields.io/vaadin-directory/star/app-layout-addon.svg)](https://vaadin.com/directory/component/easy-grid-addon)
3-
[![Build Status](https://jenkins.flowingcode.com/job/easy-grid-addon/badge/icon)](https://jenkins.flowingcode.com/job/easy-grid-addon)
1+
[![Published on Vaadin Directory](https://img.shields.io/badge/Vaadin%20Directory-published-00b4f0.svg)](https://vaadin.com/directory/component/easy-grid-add-on)
2+
[![Stars on vaadin.com/directory](https://img.shields.io/vaadin-directory/star/easy-grid-add-on.svg)](https://vaadin.com/directory/component/easy-grid-add-on)
3+
[![Build Status](https://jenkins.flowingcode.com/job/EasyGrid-addon/badge/icon)](https://jenkins.flowingcode.com/job/EasyGrid-addon)
44
[![Maven Central](https://img.shields.io/maven-central/v/com.flowingcode.vaadin.addons/easy-grid-addon)](https://mvnrepository.com/artifact/com.flowingcode.vaadin.addons/easy-grid-addon)
55

6-
# Easy Grid Add-on
6+
# Easy Grid Add-On
77

8-
This is a template project for building new Vaadin 24 add-ons
8+
Easy Grid Add-On simplifies the creation and configuration of Vaadin Grid components through automatic column discovery, a fluent typed-column API, and per-column/instance/global configuration.
99

1010
## Features
1111

12-
* List the features of your add-on in here
12+
* Automatic column discovery from bean properties
13+
* Fluent typed-column API with per-property customization (formatter, renderer, null representation, alignment)
14+
* Type-aware configuration at per-column, per-instance, and global levels
1315

1416
## Online demo
1517

1618
[Online demo here](http://addonsv24.flowingcode.com/easy-grid)
1719

1820
## Download release
1921

20-
[Available in Vaadin Directory](https://vaadin.com/directory/component/easy-grid-addon)
22+
[Available in Vaadin Directory](https://vaadin.com/directory/component/easy-grid-add-on)
2123

2224
### Maven install
2325

@@ -75,13 +77,23 @@ Then, follow these steps for creating a contribution:
7577

7678
This add-on is distributed under Apache License 2.0. For license terms, see LICENSE.txt.
7779

78-
EASY_GRID_ADDON is written by Flowing Code S.A.
80+
Easy Grid Add-On is written by Flowing Code S.A.
7981

8082
# Developer Guide
8183

8284
## Getting started
8385

84-
Add your code samples in this section
86+
```java
87+
// Auto-discover columns from bean properties
88+
EasyGrid<Person> grid = new EasyGrid<>(Person.class);
89+
grid.setItems(personService.findAll());
90+
add(grid);
91+
92+
// Or specify columns explicitly
93+
EasyGrid<Person> grid = new EasyGrid<>(Person.class, "firstName", "lastName", "email");
94+
grid.setItems(personService.findAll());
95+
add(grid);
96+
```
8597

8698
## Special configuration when using Spring
8799

0 commit comments

Comments
 (0)