Компоненты Filter

Filter может использовать специальные компоненты фильтра в качестве условий. Компоненты фильтра должны реализовывать интерфейс FilterComponent.

По умолчанию доступны три компонента фильтра:

Чтобы создать собственный компонент см. Регистрация компонента Filter.

PropertyFilter

Компонент PropertyFilter можно использовать внутри компонента Filter и отдельно.

Приведенный ниже пример показывает, как создать конфигурацию во время разработки с помощью PropertyFilter в XML-дескрипторе:

<data>
    <collection id="customersDc" class="ui.ex1.entity.Customer">
        <fetchPlan extends="_base">
            <property fetchPlan="_base" name="city"/>
            <property name="favouriteBrands" fetchPlan="_base"/>
        </fetchPlan>
        <loader id="customersDl">
            <query>
                <![CDATA[select e from uiex1_Customer e]]>
            </query>
        </loader>
    </collection>
</data>
<layout spacing="true" expand="customersTable">
    <filter dataLoader="customersDl"
            id="filterPropertyFilter"
            caption="PropertyFilter variations">
        <properties include=".*"/>
        <configurations>
            <configuration id="propertyConfiguration"
                           default="true"
                           name="PropertyFilter">
                <propertyFilter property="age"
                                operation="GREATER_OR_EQUAL"
                                operationEditable="true"/>
            </configuration>
        </configurations>
    </filter>
</layout>

Также конфигурацию времени разработки можно создать в классе контроллера экрана:

@Autowired
protected UiComponents uiComponents;

@Autowired
protected SingleFilterSupport singleFilterSupport;
@Autowired
protected Filter pfdtcFilter;
@Autowired
protected Filter jfdtcFilter;
@Autowired
protected JpqlFilterSupport jpqlFilterSupport;
@Autowired
protected Filter gfdtcFilter;
@Subscribe
protected void onInit(InitEvent event) {
    DesignTimeConfiguration javaDefaultConfigurationPF =
            pfdtcFilter.addConfiguration("javaDefaultConfiguration",
                    "Default configuration"); (1)
    DataLoader dataLoaderPF = pfdtcFilter.getDataLoader();

    PropertyFilter<City> cityPropertyFilter =
            uiComponents.create(PropertyFilter.NAME); (2)
    cityPropertyFilter.setConditionModificationDelegated(true);
    cityPropertyFilter.setDataLoader(dataLoaderPF);
    cityPropertyFilter.setProperty("city");
    cityPropertyFilter.setOperation(PropertyFilter.Operation.EQUAL);
    cityPropertyFilter.setOperationEditable(true);
    cityPropertyFilter.setParameterName(PropertyConditionUtils.generateParameterName(
            cityPropertyFilter.getProperty()));
    cityPropertyFilter.setValueComponent(singleFilterSupport.generateValueComponent(
            dataLoaderPF.getContainer().getEntityMetaClass(),
            cityPropertyFilter.getProperty(),
            cityPropertyFilter.getOperation())); (3)
    javaDefaultConfigurationPF.getRootLogicalFilterComponent()
            .add(cityPropertyFilter); (4)

    PropertyFilter<Level> levelPropertyFilter =
            uiComponents.create(PropertyFilter.NAME);
    levelPropertyFilter.setConditionModificationDelegated(true);
    levelPropertyFilter.setDataLoader(dataLoaderPF);
    levelPropertyFilter.setProperty("level");
    levelPropertyFilter.setOperation(PropertyFilter.Operation.EQUAL);
    levelPropertyFilter.setOperationEditable(true);
    levelPropertyFilter.setParameterName(PropertyConditionUtils.generateParameterName(
            levelPropertyFilter.getProperty()));
    levelPropertyFilter.setValueComponent(singleFilterSupport.generateValueComponent(
            dataLoaderPF.getContainer().getEntityMetaClass(),
            levelPropertyFilter.getProperty(),
            levelPropertyFilter.getOperation()
    ));
    javaDefaultConfigurationPF.getRootLogicalFilterComponent().add(levelPropertyFilter);

    pfdtcFilter.setCurrentConfiguration(javaDefaultConfigurationPF); (5)
}
1 Добавляет конфигурацию во время разработки с id javaDefaultConfiguration и именем Default configuration.
2 Создает компонент PropertyFilter и задает его свойства.
3 Генерирует компонент значения фильтра по заданному metaClass, свойству сущности и операции.
4 Добавляет созданный фильтр свойств в конфигурацию javaDefaultConfiguration.
5 Устанавливает конфигурацию javaDefaultConfiguration в качестве текущей.

Дополнительную информацию см. в разделе PropertyFilter.

JpqlFilter

JpqlFilter – это компонент UI, используемый для фильтрации сущностей, которых возвращает DataLoader. Он содержит выражения JPQL, которые будут добавлены в секции from и where запроса загрузчика данных. Данный компонент может автоматически отображать правильную компоновку для установки значения условия. В общем случае компоновка JpqlFilter содержит надпись и поле для редактирования значения условия. Данный компонент можно использовать только внутри компонента Filter.

Приведенный ниже пример показывает, как создать конфигурацию во время разработки с помощью JpqlFilter:

<window xmlns="http://jmix.io/schema/ui/window"
        caption="msg://filterScreen.caption"
        xmlns:c="http://jmix.io/schema/ui/jpql-condition"> (1)
    <data>
        <collection id="customersDc" class="ui.ex1.entity.Customer">
            <fetchPlan extends="_base">
                <property fetchPlan="_base" name="city"/>
                <property name="favouriteBrands" fetchPlan="_base"/>
            </fetchPlan>
            <loader id="customersDl">
                <query>
                    <![CDATA[select e from uiex1_Customer e]]>
                </query>
            </loader>
        </collection>
    </data>
    <layout spacing="true" expand="customersTable">
        <filter id="filterJpqlFilter"
                dataLoader="customersDl"
                caption="JpqlFilter variations">
            <properties include=".*"/>
            <configurations>
                <configuration id="jpqlConfiguration"
                               default="true"
                               name="JpqlFilter">
                    <jpqlFilter caption="Name like"
                                parameterClass="java.lang.String">
                        <condition>
                            <c:jpql>
                                <c:where>{E}.firstName like ?</c:where> (2)
                            </c:jpql>
                        </condition>
                    </jpqlFilter>
                </configuration>
            </configurations>
        </filter>
    </layout>
</window>
1 Вы должны добавить пространство имен условий JPQL.
2 Определите условие JPQL с необязательным элементом join и обязательным элементом where.

Чтобы настроить условие JPQL, определите элемент condition внутри jpqlFilter с необязательным элементом join и обязательным элементом where. В приведенном ниже примере создадим jpqlFilter с элементами join и where:

<filter id="filterJpqlFilter"
        dataLoader="customersDl"
        caption="JpqlFilter variations">
    <properties include=".*"/>
    <configurations>
        <configuration id="jpqlConfigurationWithJoin"
                       name="JpqlFilter with Join">
            <jpqlFilter caption="Customers with brand"
                        parameterClass="ui.ex1.entity.Brand">
                <condition>
                    <c:jpql>
                        <c:join>join {E}.favouriteBrands i</c:join>
                        <c:where>i.id = ?</c:where>
                    </c:jpql>
                </condition>
            </jpqlFilter>
        </configuration>
    </configurations>
</filter>

Атрибуты jpqlFilter внутри компонента filter:

  • Используя атрибут caption можно задать имя условия, отображаемое в фильтре.

  • Используя атрибут defaultValue можно установить значение по умолчанию для условия фильтра.

  • Для атрибута hasInExpression должно быть установлено значение true, если выражение JPQL содержит условия in (?). В этом случае приложение будет использовать компонент ValuesPicker. Таким образом, пользователь сможет ввести несколько значений параметров условия.

    Ниже приведен пример jpqlFilter с атрибутом hasInExpression:

    <filter id="filterJpqlFilter"
            dataLoader="customersDl"
            caption="JpqlFilter variations">
        <properties include=".*"/>
        <configurations>
            <configuration id="jpqlConfigurationInExpr"
                           name="JpqlFilter with IN expression">
                <jpqlFilter caption="City in"
                            parameterClass="ui.ex1.entity.City"
                            hasInExpression="true">
                    <condition>
                        <c:jpql>
                            <c:where>{E}.city in ?</c:where>
                        </c:jpql>
                    </condition>
                </jpqlFilter>
            </configuration>
        </configurations>
    </filter>
  • parameterClass – обязательный атрибут; определяет класс Java параметра условия.

  • parameterName – имя параметра запроса. Это имя можно использовать для введения зависимостей между компонентами фильтра в конфигурации. Если значение не задано, то имя параметра генерируется случайным образом.

Также конфигурацию времени разработки можно создать в классе контроллера экрана:

@Autowired
protected UiComponents uiComponents;

@Autowired
protected SingleFilterSupport singleFilterSupport;
@Autowired
protected Filter pfdtcFilter;
@Autowired
protected Filter jfdtcFilter;
@Autowired
protected JpqlFilterSupport jpqlFilterSupport;
@Autowired
protected Filter gfdtcFilter;
@Subscribe
protected void onInit(InitEvent event) {
    DesignTimeConfiguration javaDefaultConfigurationJF =
            jfdtcFilter.addConfiguration("javaDefaultConfiguration",
                    "Default configuration"); (1)
    DataLoader dataLoaderJF = jfdtcFilter.getDataLoader();

    JpqlFilter<Brand> jpqlFilter =
            uiComponents.create(JpqlFilter.NAME); (2)
    jpqlFilter.setFrame(getWindow());
    jpqlFilter.setConditionModificationDelegated(true);
    jpqlFilter.setDataLoader(dataLoaderJF);
    jpqlFilter.setCondition("i.id = ?", "join {E}.favouriteBrands i");
    jpqlFilter.setParameterClass(Brand.class);
    jpqlFilter.setCaption("Select the brand");
    jpqlFilter.setParameterName(jpqlFilterSupport.generateParameterName(
            jpqlFilter.getId(),
            jpqlFilter.getParameterClass().getSimpleName()));
    jpqlFilter.setValueComponent(singleFilterSupport.generateValueComponent(
            dataLoaderJF.getContainer().getEntityMetaClass(),
            jpqlFilter.hasInExpression(),
            jpqlFilter.getParameterClass())); (3)

    javaDefaultConfigurationJF.getRootLogicalFilterComponent()
            .add(jpqlFilter); (4)
    jfdtcFilter.setCurrentConfiguration(javaDefaultConfigurationJF); (5)
}
1 Добавляет конфигурацию во время разработки с id javaDefaultConfiguration и именем Default configuration.
2 Создает компонент JpqlFilter и задает его свойства.
3 Генерирует компонент значения фильтра по заданному metaClass и типу значения.
4 Добавляет созданный фильтр JPQL в конфигурацию javaDefaultConfiguration.
5 Устанавливает конфигурацию javaDefaultConfiguration в качестве текущей.

Фильтр без параметров

Можно определить запрос без параметров. Для этого установите значение parameterClass равным java.lang.Void и используйте java.lang.Boolean как значение:

  • true означает, что выражения JPQL из секций where и join будут добавлены в запрос загрузчика данных.

  • false не будет загружать выражения из секций where и join.

Например:

<filter id="parameterlessFilter"
        dataLoader="customersDl"
        caption="JPQL Filter without Parameters">
    <properties include=".*"/>
    <configurations>
        <configuration id="jpqlConfigurationNoParams"
                       name="JPQL Filter without Parameters">
            <jpqlFilter caption="Customers from London"
                        parameterClass="java.lang.Void"
                        defaultValue="true"> (1)
                <condition>
                    <c:jpql>
                        <c:join>join {E}.city c</c:join> (2)
                        <c:where>c.name = 'London'</c:where>
                    </c:jpql>
                </condition>
            </jpqlFilter>
        </configuration>
    </configurations>
</filter>
1 parameterClass="java.lang.Void" определяет, что фильтр не имеет параметров, а defaultValue="true" определяет, что выражения join и where будут добавлены к запросу загрузчика данных по умолчанию.
2 Определите условие JPQL с необязательным элементом join и обязательным элементом where.

Ниже приведен пример создания фильтра JPQL без параметров в классе контроллера экрана:

@Autowired
private Filter parameterlessFilter;

@Subscribe
protected void onInit(InitEvent event) {
    DesignTimeConfiguration javaConfiguration = parameterlessFilter
            .addConfiguration("javaConfiguration", "Java configuration");
    DataLoader dataLoader = parameterlessFilter.getDataLoader();

    JpqlFilter<Boolean> jpqlFilterNoParams = uiComponents.create(JpqlFilter.NAME);
    jpqlFilterNoParams.setFrame(getWindow());
    jpqlFilterNoParams.setConditionModificationDelegated(true);
    jpqlFilterNoParams.setDataLoader(dataLoader);
    jpqlFilterNoParams.setCondition("{E}.age > 21", null);
    jpqlFilterNoParams.setParameterClass(Void.class);
    jpqlFilterNoParams.setCaption("Customer's age > 21");
    jpqlFilterNoParams.setParameterName(jpqlFilterSupport
            .generateParameterName(jpqlFilterNoParams.getId(),
            jpqlFilterNoParams.getParameterClass().getSimpleName()));
    jpqlFilterNoParams.setValueComponent(singleFilterSupport
            .generateValueComponent(dataLoader.getContainer().getEntityMetaClass(),
            jpqlFilterNoParams.hasInExpression(),
            jpqlFilterNoParams.getParameterClass()
    ));

    jpqlFilterNoParams.setValue(true);
    javaConfiguration.setFilterComponentDefaultValue(
            jpqlFilterNoParams.getParameterName(),
            jpqlFilterNoParams.getValue());

    javaConfiguration.getRootLogicalFilterComponent().add(jpqlFilterNoParams);
}

Конфигурация фильтра JPQL без параметров также может быть создана во время выполнения:

jpql parameterless

GroupFilter

Компонент GroupFilter представляет собой композитный компонент, который имеет GroupBoxLayout с ResponsiveGridLayout в качестве корневого контейнера. Этот компонент необходим для объединения нескольких условий в логическую группу с использованием логических операторов (AND или OR). Данный компонент можно использовать только внутри компонента Filter.

Приведенный ниже пример показывает, как создать конфигурацию во время разработки с GroupFilter:

<filter id="filterGroupFilter"
        dataLoader="customersDl"
        caption="GroupFilter variations">
    <properties include=".*"/>
    <configurations>
        <configuration id="groupFilter"
                       name="Simple groupFilter"
                       default="true">
            <groupFilter operation="OR">
                <propertyFilter property="age"
                                operation="GREATER_OR_EQUAL"
                                operationEditable="true"/>
                <propertyFilter property="city"
                                operation="EQUAL"
                                operationEditable="true"/>
            </groupFilter>
        </configuration>
    </configurations>
</filter>

Атрибут operation является обязательным. Доступны две логические операции:

  • AND – это операция по умолчанию.

  • OR.

Также конфигурацию времени разработки можно создать в классе контроллера экрана:

@Autowired
protected UiComponents uiComponents;

@Autowired
protected SingleFilterSupport singleFilterSupport;
@Autowired
protected Filter pfdtcFilter;
@Autowired
protected Filter jfdtcFilter;
@Autowired
protected JpqlFilterSupport jpqlFilterSupport;
@Autowired
protected Filter gfdtcFilter;
@Subscribe
protected void onInit(InitEvent event) {
    DesignTimeConfiguration javaDefaultConfigurationGF =
            gfdtcFilter.addConfiguration("javaDefaultConfiguration",
                    "Default configuration"); (1)
    DataLoader dataLoaderGF = gfdtcFilter.getDataLoader();

    GroupFilter groupFilter =
            uiComponents.create(GroupFilter.NAME); (2)
    groupFilter.setConditionModificationDelegated(true);
    groupFilter.setDataLoader(dataLoaderGF);
    groupFilter.setOperation(LogicalFilterComponent.Operation.OR); (3)

    PropertyFilter<Integer> pointsPropertyFilter =
            uiComponents.create(PropertyFilter.NAME); (4)
    pointsPropertyFilter.setConditionModificationDelegated(true);
    pointsPropertyFilter.setDataLoader(dataLoaderGF);
    pointsPropertyFilter.setProperty("rewardPoints");
    pointsPropertyFilter.setOperation(PropertyFilter.Operation.GREATER_OR_EQUAL);
    pointsPropertyFilter.setOperationEditable(true);
    pointsPropertyFilter.setParameterName(PropertyConditionUtils
            .generateParameterName(pointsPropertyFilter.getProperty()));
    pointsPropertyFilter.setValueComponent(singleFilterSupport.generateValueComponent(
            dataLoaderGF.getContainer().getEntityMetaClass(),
            pointsPropertyFilter.getProperty(),
            pointsPropertyFilter.getOperation()));

    groupFilter.add(pointsPropertyFilter); (5)

    PropertyFilter<Hobby> hobbyPropertyFilter =
            uiComponents.create(PropertyFilter.NAME);
    hobbyPropertyFilter.setConditionModificationDelegated(true);
    hobbyPropertyFilter.setDataLoader(dataLoaderGF);
    hobbyPropertyFilter.setProperty("hobby");
    hobbyPropertyFilter.setOperation(PropertyFilter.Operation.EQUAL);
    hobbyPropertyFilter.setOperationEditable(true);
    hobbyPropertyFilter.setParameterName(PropertyConditionUtils.generateParameterName(
            hobbyPropertyFilter.getProperty()));
    hobbyPropertyFilter.setValueComponent(singleFilterSupport.generateValueComponent(
            dataLoaderGF.getContainer().getEntityMetaClass(),
            hobbyPropertyFilter.getProperty(),
            hobbyPropertyFilter.getOperation()
    ));
    groupFilter.add(hobbyPropertyFilter); (6)
    javaDefaultConfigurationGF.getRootLogicalFilterComponent().add(groupFilter); (7)

    PropertyFilter<Integer> agePropertyFilter =
            uiComponents.create(PropertyFilter.NAME);
    agePropertyFilter.setConditionModificationDelegated(true);
    agePropertyFilter.setDataLoader(dataLoaderGF);
    agePropertyFilter.setProperty("age");
    agePropertyFilter.setOperation(PropertyFilter.Operation.GREATER_OR_EQUAL);
    agePropertyFilter.setOperationEditable(true);
    agePropertyFilter.setParameterName(PropertyConditionUtils.generateParameterName(
            agePropertyFilter.getProperty()));
    agePropertyFilter.setValueComponent(singleFilterSupport.generateValueComponent(
            dataLoaderGF.getContainer().getEntityMetaClass(),
            agePropertyFilter.getProperty(),
            agePropertyFilter.getOperation()
    ));

    javaDefaultConfigurationGF.getRootLogicalFilterComponent().add(agePropertyFilter);

    pointsPropertyFilter.setValue(1000);
    javaDefaultConfigurationGF.setFilterComponentDefaultValue(
            pointsPropertyFilter.getParameterName(), 1000); (8)

    hobbyPropertyFilter.setValue(Hobby.FISHING);
    javaDefaultConfigurationGF.setFilterComponentDefaultValue(
            hobbyPropertyFilter.getParameterName(), Hobby.FISHING);

    agePropertyFilter.setValue(30);
    javaDefaultConfigurationGF.setFilterComponentDefaultValue(
            agePropertyFilter.getParameterName(), 30);
}
1 Добавляет конфигурацию во время разработки с id javaDefaultConfiguration и именем Default configuration.
2 Создает компонент GroupFilter и задает его свойства.
3 Задает логическую операцию OR для groupFilter.
4 Создает компонент PropertyFilter и задает его свойства.
5 Добавляет pointsPropertyFilter к groupFilter.
6 Добавляет hobbyPropertyFilter к groupFilter.
7 Добавляет созданный групповой фильтр в конфигурацию javaDefaultConfiguration.
8 Задает значение по умолчанию pointsPropertyFilter для конфигурации javaDefaultConfiguration по имени параметра.

Регистрация компонента Filter

Чтобы создать и зарегистрировать UI-компонент фильтра во фреймворке, нужны следующие объекты:

  • Класс компонента – компонент UI, который будет отображаться внутри компонента Filter. Класс компонента должен расширять класс FilterComponent. В качестве примера рассмотрим класс PropertyFilter.

  • Модель – неперсистентный класс, который содержит состояние компонента фильтра. Модель используется для сохранения состояния компонента фильтра в базе данных и отображения и изменения этого состояния во время выполнения. Класс модели должен расширять класс FilterCondition. В качестве примера рассмотрим класс PropertyFilterCondition.

  • Класс конвертера необходим для конвертирования между компонентом и моделью. Класс конвертера должен реализовывать интерфейс FilterConverter.

  • Экран редактирования – экран редактирования модели. Если идентификатор не указан, то будет использоваться идентификатор по умолчанию (например, modelName.edit, PropertyFilterCondition.edit).

Пример регистрации PropertyFilter:

@Bean
public FilterComponentRegistration registerPropertyFilter() {
    return FilterComponentRegistrationBuilder.create(PropertyFilter.class,
            PropertyFilterCondition.class,
            PropertyFilterConverter.class)
            .withEditScreenId("ui_PropertyFilterCondition.edit")
            .build();
}

Все зарегистрированные компоненты фильтра отображаются по нажатию всплывающей кнопки в диалоговом окне Add Condition.

Зарегистрированный во фреймворке Jmix компонент фильтра можно заменить своей собственной реализацией, указав аннотацию @Order в бине FilterComponentRegistration (например, для расширения набора атрибутов модели, сохраняемых фильтром).

Все XML-атрибуты

Просматривать и редактировать атрибуты, применимые к компоненту, можно с помощью панели инспектора Jmix UI в конструкторе экранов Studio.

XML-атрибуты PropertyFilter