feat: support parameters in event data and filter expressions - #25661
feat: support parameters in event data and filter expressions#25661totally-not-ai[bot] wants to merge 4 commits into
Conversation
Event data and filter expressions can take capture values instead of having the values concatenated into the expression text. The client then compiles one function per expression regardless of the values, and the values are sent per element instead of through the constant pool. The client identifies each evaluated expression by a key derived from the expression and its captures, so two entries that share an expression but use different captures stay apart and no JavaScript is sent back to the server when an event occurs. Part of #25658
The event type of an incoming event is defined by the client, so caching settings for a type that has no listeners let the client grow the cache without limit. Only the key format of the wire protocol is affected by the other changes. - Warn when several listeners for the same element and event type use the same event data name, since only one of the values is then available, and document the uniqueness requirement. - Declare the keys of the event settings once in JsonConstants instead of separately in each of the two client implementations. - Drop the IllegalArgumentException from the capture javadoc, since an unsupported capture type is reported by the encoder instead. Part of #25658
Settings are cached whenever they are updated, so the branch that recomputed them for an event type with listeners was unreachable. Assert the invariant instead of keeping a fallback that cannot run. Also cover the case of asking for an event data key from a registration without event data. Part of #25658
|
About the SonarCloud quality gate on this PR: the remaining uncovered new Those lines are tested, just not in a way Sonar can see:
The server side is covered normally — after the latest commit So the gate cannot be met by adding more tests; it needs either a waiver or |
|


Summary
Event data and filter expressions can now take values as parameters instead of building the values into the JavaScript text. You write the expression once with
$0,$1, … placeholders and pass the values separately, so the browser reuses one compiled function for all listeners, and you no longer have to escape values yourself.What changed
Behavior change (client/server wire format): the event settings that the server sends and the event data that the browser sends back changed shape. Server-side APIs stay source compatible, but the server and the client must be upgraded together, and any code or test that reads the raw settings JSON or builds event data keyed by the JavaScript expression must be updated.
ElementListenerMap.translateEventData). No JavaScript text travels from the browser back to the server any more (ModScurity blocks requests with enter #13834). Listener code is unaffected:addEventData("event.key")is still read asgetEventData().get("event.key").{expression: debounceSettings}to{key: {"e": expression, "d": debounceSettings, "c": captureCount}}. The keys are declared once inJsonConstantsand shared by the Java (GWT) and TypeScript clients.ParameterizedConstantPoolKey, encoded as[constantId, captures]), so two elements that use the same expression with different values still share one constant pool entry.DomListenerRegistrationgains two new abstract methods. Anyone implementing this interface outside Flow must implement them. The only implementation in Flow is internal.Use case
You have a table of rows and want a click on a row to tell the server which row was clicked, without adding a state node per row. The row id sits in a
data-attribute, and you only care about primary-button clicks.Every table instance uses the same compiled JavaScript function in the browser; only the selector and the button number are sent per element.
API Changes
com.vaadin.flow.dom.DomListenerRegistration
com.vaadin.flow.internal.ParameterizedConstantPoolKey
com.vaadin.flow.internal.nodefeature.ElementListenerMap
com.vaadin.flow.shared.JsonConstants
Test summary
addEventData(String)still delivers the value under the expression, even though the wire key differspreventDefault()combined with a capturing filter keeps the filter captures and the capture countgetFilter()returns the expression with placeholders;setFilter(null)clears it and its keynullname or expression throwsIllegalArgumentExceptionParameterizedConstantPoolKeyserializes into a map put change as[constantId, parameters]addEventData/setFilteris calledElementListenersTest.addEventDataWithCaptures_valueReportedUnderName→ 1ElementListenersTest.addEventDataWithoutCaptures_reportedUnderExpression→ 2ElementListenersTest.sameExpressionDifferentCaptures_separateValuesAndFilters→ 3ElementListenersTest.capturesAreSentOutsideTheConstantPool→ 4ElementListenersTest.preventDefaultWithCapturedFilter_keepsFilterCaptures→ 5ElementListenersTest.setFilterWithCaptures_getFilterReturnsExpression→ 6ElementListenersTest.nullNameOrExpression_throws→ 7ElementListenersTest.duplicateEventDataName_valueIsSharedByBothListeners→ 8ElementListenersTest.translateEventDataForUnknownEventType_nothingIsCached→ 9MapPutChangeTest.testParameterizedConstantPoolValueType→ 10GwtBasicElementBinderTest.testEventFiredWithCaptures→ 11SimpleElementBindingStrategyEventDataTests("evaluates a parameterized expression with its captures", "reports one value per set of captures for the same expression", "debounces a parameterized filter per set of captures", "decodes an element capture into a DOM node") → 11ComponentEventBusTest.addListener_eventDataExpressionsPresent_constantPoolKeyNotCreatedAfterEachExpression→ 12EventDataCapturesIT.capturedEventDataIsReportedUnderItsName,EventDataCapturesIT.capturedFilterOnlyMatchesItsOwnListener→ 13DomEventTest.assertSettings(helper, updated) — re-points the existing@DomEventfilter and debounce tests at the new settings shapeLeft untested on purpose: the key derivation itself (only observed through the keys the tests use), and the updated javadoc and the
@JsFunction-to-NativeFunctionrefactoring in the GWT client, which have no behaviour of their own.