A generic filter created by the programmatic API becomes broken after navigating to the view a second time #5425#5504
Conversation
| parameterValue; | ||
| } | ||
|
|
||
| @SuppressWarnings({"rawtypes", "unchecked"}) |
There was a problem hiding this comment.
unchecked suppression no needed anymore
| @@ -118,8 +120,7 @@ protected void updateQueryParameters() { | |||
| @SuppressWarnings("unchecked") | |||
| protected void restoreStructure(Configuration configuration, | ||
| LogicalFilterComponent<?> logicalFilterComponent, | ||
| List<ComponentNode> structure) { | ||
| // Reconcile this logical component's own children to their initial set, in the original order: | ||
| // drop components added after initialization (e.g. from the URL) and restore those the user | ||
| // removed, recursing into nested groups so the whole configuration tree is rebuilt exactly. | ||
| // Component instances are preserved, so references handed to application code stay valid. | ||
| // For the empty configuration the initial structure is empty, so this clears the root. | ||
| for (FilterComponent component : List.copyOf(logicalFilterComponent.getOwnFilterComponents())) { | ||
| logicalFilterComponent.remove(component); | ||
| configuration.setFilterComponentModified(component, false); | ||
| } | ||
| for (ComponentNode node : structure) { | ||
| logicalFilterComponent.add(node.component()); | ||
| configuration.setFilterComponentModified(node.component(), node.modified()); | ||
| if (node.component() instanceof LogicalFilterComponent<?> nestedComponent) { | ||
| restoreStructure(configuration, nestedComponent, node.children()); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
applyInitialState() runs on every same-view re-navigation (View.java:228 → UrlQueryParametersFacetImpl.java:129), and restoreStructure (:289) unconditionally removes (:298) and re-adds (:302) every baseline component even with no user edits.
GroupFilter.add() registers a fresh operationChange → apply() listener (GroupFilter.java:283) whose Registration is discarded, and remove() (:306) never unregisters it. So a baseline PropertyFilter accumulates K+1 apply() listeners after K re-navigations, retained for the view's lifetime. Changing its operation then fires K+1 dataLoader.load() calls (K+1 DB queries + URL pushes) — a resource/perf regression, though the restored state stays correct.
This is newly introduced: the old removeAll() + setCurrentConfiguration() path never re-added baseline instances via add().
… the entry one Co-authored-by: Pavel Aleksandrov <aleksandrovpv@haulmont.com>
Fixes #5425 and #5487
Problem
A
GenericFilterbound via theurlQueryParametersfacet broke on a clean re-navigation to the same view. When Vaadin reuses the view instance,onInitis not called again, andapplyInitialState()wiped the current configuration instead of restoring it:Fix
On re-navigation the binder now restores the captured initial state instead of clearing it:
modifiedflags are restored, component instances are preserved.Supporting changes: the duplicated base-condition capture/compose logic in
GenericFilterandGroupFilterwas extracted into@Internal BaseConditionSupport(behaviour unchanged); the internalInitialState/ComponentNoderecords and the single-filter state helper are marked@Internal; generic-filter test views were renamed to the*TestViewconvention.Tests
Facet-level re-navigation tests driving the real event chain (
RestoreComponentsStateEvent→QueryParametersChangeEvent): programmatic-baseline survival, empty-configuration clearing, URL value/operation/condition reset, design-time value reset (#5487), nested-group restore, pure-user-action scope, and an event-storm regression. Also a regression test for removing a condition nested in a group (#5486).Commits
restore configuration state on same-view re-navigation (5425, 5487)— the fix and its tests.add a regression test for removing a condition nested in a group (5486)— the regression test.Backward compatibility
No
publicAPI contract is removed or changed: theHasInitialStateinterface is untouched,GenericFilter.updateDataLoaderInitialConditionis kept as@Deprecated(forRemoval = true)rather than removed, and the only new public types (BaseConditionSupport,SingleFilterComponentStateSupport) are@Internaland purely additive. The behavioural changes are the bug fixes themselves — re-navigation now restores the configuration instead of clearing it (#5425) and resets a URL-changed design-time value (#5487); both correct broken behaviour, so no application migration is required.However, the following
protectedmembers of the URL query-parameter binders changed shape. They are now marked@Internal, and all usages are confined to theflowuimodule (no references injmix-premiumor sample apps), but a subclass that referenced them would break at source/binary level. Worth a release-note line for anyone who extended these binders:PropertyFilterUrlQueryParametersBinder(sharpest break): theprotected record InitialState(Operation, Object)is removed, and the type of theprotectedfieldinitialStatechanged fromInitialStatetoSingleFilterComponentStateSupport.State. A subclass reading that field or the record no longer compiles.DataGridFilterUrlQueryParametersBinder.InitialState: record signature changed from(String key, String property, PropertyFilter.Operation operation, Object value)to(String key, String property, SingleFilterComponentStateSupport.State state).GenericFilterUrlQueryParametersBinder.InitialState: record signature changed from(Configuration configuration)to(Configuration configuration, List<ComponentNode> structure, Map<…> states, Map<…> defaultValues); a newComponentNoderecord was added.Mitigations already in place: all three records carry
@Internal, the publicHasInitialStatecontract does not expose them, and the newSingleFilterComponentStateSupportbean is resolved from the standardio.jmix.flowuicomponent scan (no manual wiring needed).