Skip to content

GenericFilter duplicates condition remove buttons when the filter is inside a Fragment #5656

Description

@syncro

Description

When a genericFilter is placed inside a Fragment, every refresh of the filter configuration appends one more "remove condition" (trash) button to each already existing condition. After adding three conditions the first one has three trash buttons, the second two, the third one — six in total. The same filter declared directly in a view keeps exactly one button per condition.

Image

Environment

Reproduced on Jmix 2.7.4, 3.0.0 and 3.0.1. The relevant code is unchanged on master (checked at 7ba4839), so the current codebase is affected as well.

Steps to reproduce

A minimal repro project (Jmix 3.0.1, HSQLDB, no add-ons) is attached as a ZIP.

  1. ./gradlew bootRun, open http://localhost:8082, log in as admin / admin.
  2. Open Users (filter in fragment) — route /user-fragment-host. It is a view whose only content is a fragment (UserFilterFragment) holding a genericFilter and a dataGrid.
  3. Add three conditions: Add search condition → select a property → Select. Repeat for Username, Email, First name.

Actual: trash buttons per condition — Username: 3, Email: 2, First name: 1.
Expected: one button per condition, as on route /users, where the same genericFilter is declared directly in the view descriptor.

Cause

GenericFilter.createConditionRemoveButton() assigns a real component id to the button (conditionRemoveButton.setId(removeButtonId)), while GenericFilter.updateSingleConditionRemoveButton() looks that button up with the two-argument UiComponentUtils.findComponent(component, id). That overload picks the id comparator itself:

// UiComponentUtils#findComponent(Component, String)
return UiComponentUtils.findComponent(component, id,
        findFragment(component) == null ? UiComponentUtils::sameId : FragmentUtils::sameId);

Inside a fragment the condition layout has a Fragment ancestor, so FragmentUtils::sameId is used — and that comparator comes from the fragment id stored in the component's data (ComponentUtil.getData(component, FragmentUtils.ID_KEY)), which is assigned only to components declared in the fragment XML at load time.

A button created at runtime carries a real Vaadin id and no fragmentId data, so the predicate is false for every child and findComponent() returns an empty Optional even though the button is a direct child of the layout (it is plainly visible in the components collection while debugging getComponentRecursively()). Consequently existingRemoveButton.isEmpty() is true and one more button is appended on every updateRootLogicalFilterComponent() pass, i.e. on every configuration refresh and on every operation change.

GenericFilter.updateGroupConditionButtons() performs the same kind of lookup and has the same defect. More generally: looking a runtime-created component up by its real id fails whenever the container lives inside a fragment.

Possible fix

Use the explicit comparator for the components the filter creates itself, in both methods:

Optional<Component> existingRemoveButton =
        UiComponentUtils.findComponent(singleFilterLayout, removeButtonId, UiComponentUtils::sameId);

Alternatively, let the two-argument UiComponentUtils.findComponent() fall back to UiComponentUtils::sameId when the fragment-id comparator finds nothing — that would also cover similar lookups of runtime-created components elsewhere.

Workaround

The attached project contains ExtGenericFilter, which overrides updateSingleConditionRemoveButton() and updateGroupConditionButtons() with the explicit comparator and is registered via

ComponentRegistrationBuilder.create(ExtGenericFilter.class)
        .replaceComponent(GenericFilter.class)
        .build();

The @Bean method is commented out so that the project reproduces the bug out of the box; uncommenting it makes the behaviour correct (verified on 2.7.4 and 3.0.1).

Sample Project

filter-fragment-bug-3x-min.zip

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions