Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ types.d.ts
/frontend/index.html
vite.generated.ts
vite.config.ts
/src/main/dev-bundle
/src/main/dev-bundle
/.claude/settings.local.json
106 changes: 106 additions & 0 deletions CONFIGURATION_RESOLUTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Configuration Resolution: Scope-First vs. Type-First

## The Two Strategies

Given `Foo extends Entity` with configurations at four points in the matrix:

| | Instance | Global |
|---|---|---|
| **Foo** | I·Foo | G·Foo |
| **Entity** | I·Entity | G·Entity |

**Scope-first** exhausts all instance settings before looking at global ones:
```
I·Foo → I·Entity → G·Foo → G·Entity → Default
```

**Type-first** exhausts all Foo-specific settings before looking at Entity:
```
I·Foo → G·Foo → I·Entity → G·Entity → Default
```
Comment on lines +13 to +20
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifiers to fenced code blocks.

Line 13, Line 18, Line 78, Line 86, Line 92, and Line 98 use unlabeled fences, which triggers MD040 and can fail markdown lint gates.

Suggested fix
-```
+```text
 I·Foo → I·Entity → G·Foo → G·Entity → Default

- +text
I·Foo → G·Foo → I·Entity → G·Entity → Default


-```
+```text
per-column → I·Foo → I·Entity → I·Object → G·Foo → G·Entity → G·Object → Default

- +text
I·Foo(impl) → I·Entity(impl) → I·Object(impl)


-```
+```text
G·Foo(impl) → G·Entity(impl) → G·Object(impl)

- +text
ColumnConfigurationLink(primary = I·Foo chain, fallback = G·Foo chain)

Also applies to: 78-100

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 13-13: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 18-18: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CONFIGURATION_RESOLUTION.md` around lines 13 - 20, Several fenced code blocks
in CONFIGURATION_RESOLUTION.md are unlabeled and trigger MD040; update each
triple-backtick fence that contains sequences like "I·Foo → I·Entity → G·Foo →
G·Entity → Default", "I·Foo → G·Foo → I·Entity → G·Entity → Default",
"per-column → I·Foo → I·Entity → I·Object → G·Foo → G·Entity → G·Object →
Default", "I·Foo(impl) → I·Entity(impl) → I·Object(impl)", "G·Foo(impl) →
G·Entity(impl) → G·Object(impl)", and "ColumnConfigurationLink(primary = I·Foo
chain, fallback = G·Foo chain)" to use a language identifier (e.g., text) after
the opening ``` so the fences read ```text and satisfy markdown linting.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defer to reviewers' judgment. This file is intended for AI consumption.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@javier-godoy, understood — I'll dismiss this concern. Since CONFIGURATION_RESOLUTION.md is intended for AI consumption rather than human readers or standard documentation pipelines, markdown lint compliance is not a meaningful concern here.


✏️ Learnings added
Learnt from: javier-godoy
Repo: FlowingCode/EasyGridAddon PR: 10
File: CONFIGURATION_RESOLUTION.md:13-20
Timestamp: 2026-05-14T12:05:24.375Z
Learning: The file `CONFIGURATION_RESOLUTION.md` in the `FlowingCode/EasyGridAddon` repository is intended for AI consumption, not for human readers or standard documentation pipelines. Markdown style/lint issues (e.g., MD040 unlabeled fenced code blocks) should not be flagged in this file during code reviews.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is okay for me to dismiss this.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


Both agree on `I·Foo` being first and `G·Entity` being last. The dispute is the middle two.

---

## Where Scope-First Makes Sense

**Setting: `textAlign` or `nullRepresentation`**

A developer creates a reporting grid and writes:
```java
easyGrid.typeConfiguration(Entity.class).setTextAlign(RIGHT);
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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.

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.

---

## Where Type-First Makes Sense

**Setting: `rendererFactory`**

Globally, `Foo` has a specialized renderer registered:
```java
GlobalEasyGridConfiguration.forType(Foo.class)
.setRendererFactory(FooRenderers.of(...));
```
Independently, an instance configures Entity columns with a generic formatter:
```java
easyGrid.typeConfiguration(Entity.class).setFormatter(e -> e.getId().toString());
```

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.

Type-first preserves the type contract: `G·Foo` sits above `I·Entity`, so Foo's rendering semantics are honoured even across scope boundaries.

---

## Liskov Substitution Principle

LSP says: a `Foo` must be usable wherever an `Entity` is expected, without callers needing to know the difference.

Applied to configuration, it has two implications that pull in opposite directions:

**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.

**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.

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.**

---

## Current Implementation

The implementation uses **scope-first** for all properties. For a `Foo extends Entity` column the effective chain is:

```
per-column → I·Foo → I·Entity → I·Object → G·Foo → G·Entity → G·Object → Default
```

This is built in two pieces:

**Instance chain** — `EasyGridConfigurationClassMap.getOrCreate(Foo)` walks the Java class hierarchy within the same map, producing a `ColumnConfigurationImpl` chain:

```
I·Foo(impl) → I·Entity(impl) → I·Object(impl)
```

**Global chain** — `GlobalEasyGridConfiguration.resolve(Foo)` similarly produces:

```
G·Foo(impl) → G·Entity(impl) → G·Object(impl)
```

**Bridge** — `InstanceEasyGridConfiguration.forType(Foo)` wraps both into a `ColumnConfigurationLink`:

```
ColumnConfigurationLink(primary = I·Foo chain, fallback = G·Foo chain)
```

`ColumnConfigurationLink.get()` consults the primary chain first and only falls back to the global chain when the primary returns `null` for a given property.

**Per-column** — `InstanceEasyGridConfiguration.resolve(Foo)` wraps the link in a fresh `ColumnConfigurationImpl` whose fields are overridden by column-level setters (`setNullRepresentation`, `setFormatter`, `setRendererFactory`).

**Default** — when the entire chain returns `null` for `getRendererFactory()`, `EasyColumn.createRenderer` applies a `ColumnConfigurationTextRenderer` with null-representation support as the last resort.
77 changes: 77 additions & 0 deletions FEATURE_ROW_ACTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Feature: Row Actions (Roadmap)

> **Note:** This feature is planned for a future iteration and is not yet implemented.

# Feature: Row Actions
Comment thread
paodb marked this conversation as resolved.

Row actions are buttons or menu items displayed in a dedicated actions column, created and managed by `EasyGrid` on the wrapped grid.

## API

### `EasyGrid<T>` methods

```java
// Add an action button (label + icon)
EasyRowAction<T> addRowAction(String label, VaadinIcon icon, SerializableConsumer<T> handler);

// Add an action button with a theme variant
EasyRowAction<T> addRowAction(String label, VaadinIcon icon, ButtonVariant variant, SerializableConsumer<T> 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<T> getActionsColumn();
```

### `EasyRowAction<T>`

```java
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);
}
```

## 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);
easyGrid.getDataProvider().refreshAll();
}).withConfirmation("Are you sure you want to delete this person?");
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// 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);
```
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
[![Published on Vaadin Directory](https://img.shields.io/badge/Vaadin%20Directory-published-00b4f0.svg)](https://vaadin.com/directory/component/easy-grid-addon)
[![Stars on vaadin.com/directory](https://img.shields.io/vaadin-directory/star/app-layout-addon.svg)](https://vaadin.com/directory/component/easy-grid-addon)
[![Build Status](https://jenkins.flowingcode.com/job/easy-grid-addon/badge/icon)](https://jenkins.flowingcode.com/job/easy-grid-addon)
[![Published on Vaadin Directory](https://img.shields.io/badge/Vaadin%20Directory-published-00b4f0.svg)](https://vaadin.com/directory/component/easy-grid-add-on)
[![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)
[![Build Status](https://jenkins.flowingcode.com/job/EasyGrid-addon/badge/icon)](https://jenkins.flowingcode.com/job/EasyGrid-addon)
[![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)

# Easy Grid Add-on
# Easy Grid Add-On

This is a template project for building new Vaadin 24 add-ons
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.

## Features

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

## Online demo

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

## Download release

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

### Maven install

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

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

EASY_GRID_ADDON is written by Flowing Code S.A.
Easy Grid Add-On is written by Flowing Code S.A.

# Developer Guide

## Getting started

Add your code samples in this section
```java
// Auto-discover columns from bean properties
EasyGrid<Person> grid = new EasyGrid<>(Person.class);
grid.setItems(personService.findAll());
add(grid);

// Or specify columns explicitly
EasyGrid<Person> grid = new EasyGrid<>(Person.class, "firstName", "lastName", "email");
grid.setItems(personService.findAll());
add(grid);
Comment on lines +86 to +95
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Java, can a local variable be redeclared in the same scope (e.g., EasyGrid grid twice in one method/block)?

💡 Result:

No, in Java, a local variable cannot be redeclared within the same scope [1][2][3]. If you attempt to declare a variable with the same name as an existing local variable in the same block or method, the Java compiler will report a compilation error [1][2][3]. Java uses block scoping, meaning a variable's scope is defined by the nearest enclosing curly braces {} [1][4]. Within that specific scope, each identifier must be unique [1][2]. For example, the following code is invalid and will fail to compile: void myMethod { EasyGrid grid = new EasyGrid<>; //... EasyGrid grid = new EasyGrid<>; // Compilation error: variable grid is already defined } However, you can declare a variable with the same name if it is in a different, nested scope (such as inside an inner block or a loop) [1][3]. In this case, the inner declaration shadows the outer one, meaning the outer variable becomes inaccessible by its simple name within that inner block [1][5][3]. void myMethod { EasyGrid grid = new EasyGrid<>; { EasyGrid grid = new EasyGrid<>; // This is allowed because it is in a new, nested scope } }

Citations:


Split the two Java examples or rename the second variable.

grid is declared twice in the same scope, preventing the code snippet from compiling. Rename the second instance to explicitGrid:

Proposed doc fix
 ```java
 // Auto-discover columns from bean properties
 EasyGrid<Person> grid = new EasyGrid<>(Person.class);
 grid.setItems(personService.findAll());
 add(grid);

 // Or specify columns explicitly
-EasyGrid<Person> grid = new EasyGrid<>(Person.class, "firstName", "lastName", "email");
-grid.setItems(personService.findAll());
-add(grid);
+EasyGrid<Person> explicitGrid = new EasyGrid<>(Person.class, "firstName", "lastName", "email");
+explicitGrid.setItems(personService.findAll());
+add(explicitGrid);
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 86 - 95, The snippet declares the same variable name
twice (grid) causing a compile error; rename the second EasyGrid instance and
its usages to a unique identifier such as explicitGrid: change the second
declaration EasyGrid<Person> grid to EasyGrid<Person> explicitGrid and update
subsequent calls grid.setItems(...) and add(grid) to explicitGrid.setItems(...)
and add(explicitGrid) to keep both examples in the same scope.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just a readme example, no need to fix anything.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

Understood, @paodb — README snippets are illustrative and don't need to compile. I'll dismiss this comment.

(ˆ ᴗ ˆ)っ


🏁 Script executed:

#!/bin/bash
# Resolve the review comment
gh pr view 10 --repo FlowingCode/EasyGridAddon --json reviews,comments

# List bot comments to find the one to resolve
gh api graphql -f query='
{
  repository(owner: "FlowingCode", name: "EasyGridAddon") {
    pullRequest(number: 10) {
      reviewThreads(first: 50) {
        nodes {
          id
          isResolved
          comments(first: 5) {
            nodes {
              id
              body
              author { login }
              url
            }
          }
        }
      }
    }
  }
}'

Length of output: 233729


@paodb Understood. I’ll treat this README snippet as illustrative and won’t push this change. Marking it non-blocking on my side.


✏️ Learnings added
Learnt from: paodb
Repo: FlowingCode/EasyGridAddon PR: 10
File: README.md:86-95
Timestamp: 2026-05-18T14:34:28.752Z
Learning: Repo: FlowingCode/EasyGridAddon
Context: README.md code snippets
Learning: README examples are illustrative and need not be strictly compilable; avoid nitpicks like duplicate local variable declarations within a single snippet unless the team explicitly requests such edits.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

```

## Special configuration when using Spring

Expand Down
Loading
Loading