feat: pass clicked column to grid context menu dynamic content handler - #10331
totally-not-ai[bot] wants to merge 6 commits into
Conversation
…ntent handler Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a GridContextMenu#setDynamicContentHandler overload whose callback receives both the clicked item and the clicked column. Also update the context menu target item and column before running the dynamic content handler, so that they are no longer one click behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| @@ -276,6 +284,50 @@ public SerializablePredicate<T> getDynamicContentHandler() { | |||
| public void setDynamicContentHandler( | |||
There was a problem hiding this comment.
This should be deprecated for removal then, right?
There was a problem hiding this comment.
@Artur- Yes, I've deprecated it for removal now. Both setDynamicContentHandler(SerializablePredicate) and getDynamicContentHandler() have @Deprecated(since = "25.4", forRemoval = true), and the @deprecated tag points to the (item, column) overload. The getter has no replacement, because the two overloads can't share a getter name. Behavior is unchanged and the existing tests still cover the old setter.
| const columnId = eventContext.column?.id || ''; | ||
| return { key, columnId }; | ||
| const internalColumnId = eventContext.column?._flowId ?? ''; | ||
| return { key, columnId, internalColumnId }; |
There was a problem hiding this comment.
@Artur- columnId is the column's DOM id, which is only set when the application calls Column#setId. It is empty by default and isn't guaranteed to be unique. So it can't reliably be mapped back to a Grid.Column, and handlers for columns without an id would always get null. _flowId is the internal id that Grid assigns to every column, and the server keeps a map from it to the column. The item click and cell focus events already use it for the same reason. columnId stays in the event detail because GridContextMenuOpenedEvent#getColumnId() and the _contextMenuTargetColumnId property still expose it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| * @since 25.4 | ||
| */ | ||
| public void setDynamicContentHandler( | ||
| SerializableBiPredicate<T, Grid.Column<T>> dynamicContentHandler) { |
There was a problem hiding this comment.
Should probably pass a record here and not two separate items - if that had been done originally then this change would be backwards compatible.. but that wouldn't be possible now would it?
There was a problem hiding this comment.
@Artur- Right, it can't be done under the same method name. A setDynamicContentHandler(SerializablePredicate<Context<T>>) overload has the same erasure as the existing SerializablePredicate<T> one, so it doesn't compile. A dedicated functional interface that takes a record would compile, but it is also single-argument, so every existing item -> ... lambda would become ambiguous between the two overloads and existing code would stop compiling. That's why I used a two-argument BiPredicate.
A record-based API would need a new name, for example setDynamicContentProvider(context -> ...) with record DynamicContentContext<T>(T item, Grid.Column<T> column), and both setDynamicContentHandler overloads would be deprecated. That would let us add more context later (such as the clicked section or header row) without adding more overloads. I can switch the PR to that if you prefer it over the (item, column) overload.
vaadin-review-bot
left a comment
There was a problem hiding this comment.
Reviewed the changes — left 1 comment.
| Finding | |
|---|---|
| 📐 | New setter has no getter, and the only existing getter is deprecated for removal |
| * context menu, or {@code null} to remove it | ||
| * @since 25.4 | ||
| */ | ||
| public void setDynamicContentHandler( |
There was a problem hiding this comment.
📐 New setter has no getter, and the only existing getter is deprecated for removal
CONVENTIONS.md states: "Pair public setters with a matching getter."
After this change there is no way to read back a handler set through the new overload: getDynamicContentHandler() returns null for it and is itself marked forRemoval. Once the deprecated pair is gone, the dynamic content handler becomes write-only.
A separately named getter (for example getColumnDynamicContentHandler()) avoids the erasure clash that blocks reusing the name.
As a side effect today, getDynamicContentHandler() != null is no longer a reliable "has a dynamic handler" check.
GridContextMenu.java:336 · conventions
There was a problem hiding this comment.
I added getColumnDynamicContentHandler(), which returns the handler set through the (item, column) overload. It returns null when the item-only handler is set, the same way getDynamicContentHandler() returns null for the column-aware one. The deprecated getter's @deprecated tag now points to it. The tests now check that each getter returns the handler set through its own overload, and that setting one handler clears the other.
…andler Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|



Review risk: two-way door — additive API on
GridContextMenu; the only behavior change is that the context menu target (item key / column id) is updated before the dynamic content handler runs.Summary
The dynamic content handler of
GridContextMenuonly received the clicked item, and the clicked column could not be reliably read from inside it: the grid's_contextMenuTargetColumnIdproperty was updated by a separate server call that is processed after the handler runs, so the handler always saw the previously clicked column (or none on the first click).This adds a handler overload that receives the clicked column directly, which enables context menus whose items depend on the column that was right-clicked:
What changed
GridContextMenu#setDynamicContentHandler(SerializableBiPredicate<T, Grid.Column<T>>)with a matchinggetColumnDynamicContentHandler()getter. The column isnullwhen the click doesn't target an application column (e.g. the selection column), the item isnullwhen it doesn't target an item (e.g. a header). The column is resolved from its internal id, so it works withoutColumn#setId.setDynamicContentHandleroverloads replace each other;getDynamicContentHandler()returnsnullwhen the column-aware handler is set.setDynamicContentHandler(SerializablePredicate)andgetDynamicContentHandler()are deprecated for removal in favor of the new overload. Their behavior is unchanged.setDynamicContentHandler(null)call is now ambiguous and needs a cast.Test summary
nullfor non-application columns; the overloads replace each other.API Changes
Fixes #2258
🤖 Generated with Claude Code