Фрагменты для GenericFilter в TabSheet

Здравствуйте!
Studio и Jmix 2.4.3.

Есть некая базовая сущность, допустим Entity, у сущности помимо прочего есть поле Status
Есть несколько экранов, предположим 8, для отображения списка сущностей по статусам. Каждый из этих экранов имеет genericFilter и dataGird. Было несколько неудобно, так как приходилось копировать один и тот же код фильтра и грида во все 8 экранов.

С выходом fragments мелькнула возможность решить этот вопрос, создав фрагмент с компонентом GenericFilter и фрагмент с компонентом DataGrid, указав для атрибута <collection> provided = true. И действительно оно сработало, xml экрана списка значительно похудел, когда его заменили этими двумя фрагментами, а для изменения/добавления пропертей фильтра или колонок грида достаточно поменять их во фрагменте.

Но тут дело дошло ещё до одного экрана, где есть элемент TabSheet, который содержит две вкладки Tab. У каждой вкладки Tab есть те же GenericFilter и DataGrid что и в остальных экранах. То есть, в xml экрана определены 2 коллекции со своим названием, например EntityDc1 и EntityDc2, со своими лоудерами EntityDl1 и EntityDl2. Соответственно надо каким-то образом указать фрагментам, что для вкладки Tab1 тебе нужен EntityDc1 и EntityDl1, а для вкладки Tab2 - EntityDc2 и EntityDl2.

Для фрагмента с dataGrid это удалось реализовать, устанавливая свой контейнер в каждый фрагмент по его id.

Спойлер
    @Subscribe
    public void onInit(final InitEvent event) {

        gridFragment1.setDataContainer(EntityDc1);
        gridFragment2.setDataContainer(EntityDc2);
        
    }
@FragmentDescriptor("common-grid-fragment.xml")
public class CommonGridFragment extends Fragment<DataGrid<Entity>> {

    @ViewComponent
    private DataGrid<Entity> entitiesDataGrid;

    public void setDataContainer(CollectionContainer<Entity> container) {

        entitiesDataGrid.setItems(container.getItems());

    }

}

Однако с фрагментом фильтра это никак сделать не удалось. С виду всё было просто, компонент genericFilter имеет метод setDataLoader(DataLoader dataLoader), то есть достаточно передать свои CollectionLoader<Entity> во фрагменты, но этот метод имеет следующую реализацию:

Спойлер
public void setDataLoader(DataLoader dataLoader) {
        checkState(this.dataLoader == null, "DataLoader has already been initialized");
        checkNotNull(dataLoader);

        this.dataLoader = dataLoader;
        this.initialDataLoaderCondition = dataLoader.getCondition();

        LogicalFilterComponent<?> rootLogicalFilterComponent = emptyConfiguration.getRootLogicalFilterComponent();
        rootLogicalFilterComponent.setDataLoader(dataLoader);
        rootLogicalFilterComponent.setAutoApply(autoApply);
    }

То есть при попытки установить переданный из экрана loader в genericFilter вылетала ошибка метода. Убрать из фрагмента контейнер нельзя, так как у компонента genericFilter атрибут dataLoader обязателен, как отменить инициализацию тоже неизвестно, из экрана loader пытался устанавливать в методе onInit.

Никакие попытки связать загрузчики/коллекции из экрана и из фрагмента не увенчались успехом, фильтр не работал, вылетали различные ошибки.

Подскажите, есть ли какое решение?

Ссылка на проект-заготовку:

Добрый день,

Самым простым способом будет создать фильтр программно, в момент, когда будет передан DataLoader, т.к. лоадер нужен для корректной инициализации компонента. Это касается всех фильтр компонентов, в т.ч. и propertyFilter, который указан у вас в конфигурации. Чтобы чуть-чуть сократить количество ручного кода, можно проигнорировать, что `dataLoader является обязательным атрибутом и тогда XML фрагмента будет выглядеть так:

<fragment xmlns="http://jmix.io/schema/flowui/fragment">
    <content>
        <genericFilter id="genericFilter">
            <properties include=".*"/>
        </genericFilter>
    </content>
</fragment>

но, конфигурацию все же придется делать руками, т.к. нужен dataLoader:

@FragmentDescriptor("common-filter-fragment.xml")
public class CommonFilterFragment extends Fragment<GenericFilter> {

    @ViewComponent
    private GenericFilter genericFilter;

    @Autowired
    private UiComponents uiComponents;

    public void setDataLoader(CollectionLoader<TestEntity> dataLoader) {
        genericFilter.setDataLoader(dataLoader);
        FilterUtils.updateDataLoaderInitialCondition(genericFilter, dataLoader.getCondition());

        DesignTimeConfiguration configuration = genericFilter.addConfiguration("forFilterConfig", "ForFilter");
        LogicalFilterComponent<?> rootLogicalFilterComponent = configuration.getRootLogicalFilterComponent();

        SingleFilterComponentBase<?> filterComponent = createFilterComponent(dataLoader);
        rootLogicalFilterComponent.add(filterComponent);
    }

    private SingleFilterComponentBase<?> createFilterComponent(CollectionLoader<TestEntity> dataLoader) {
        PropertyFilter<?> propertyFilter = uiComponents.create(PropertyFilter.class);
        propertyFilter.setDataLoader(dataLoader);
        propertyFilter.setProperty("enumForFilter");
        propertyFilter.setOperation(PropertyFilter.Operation.EQUAL);
        propertyFilter.setParameterName("enumForFilter");
        propertyFilter.setConditionModificationDelegated(true);

        return propertyFilter;
    }
}

Так же обратите внимание, что правильный способ связывания грида с контейнером как ниже:

testEntitiesDataGrid.setItems(new ContainerDataGridItems<>(container));

В противном случае он не узнает, что данные поменялись и не обновит строки.

Глеб

Спасибо, решение рабочее. Но тогда теряется смысл фрагмента для данного контекста, genericFilter и так можно было создать программно где-нибудь в сервисе и просто в onInit экранов делать его add в подготовленный контейнер (собственно как в документации).

Как говорил один известный человек, это не jmix-way))

Спасибо, возьму на вооружение.