jmix-framework / jmix

Jmix framework
https://www.jmix.io
Apache License 2.0
625 stars 122 forks source link

Support Items fetch callback #1923

Closed glebfox closed 12 months ago

glebfox commented 1 year ago

Consider implementing com.vaadin.flow.data.provider.CallbackDataProvider instead of separate component

Solution

The setItemsFetchCallback method is added for the following components:

XML

All components support an optional itemsQuery element that enables defining a query for selecting suggested values.

The itemsQuery element for the EntityComboBox has the following attributes:

The itemsQuery element for the EntityComboBox has the following nested elements:

<entityComboBox id="assigneeField" metaClass="User" width="25em">
    <actions>
        <action id="entityLookup" type="entity_lookup"/>
        <action id="entityClear" type="entity_clear"/>
    </actions>
    <itemsQuery class="com.company.demo.entity.User"
                searchStringFormat="(?i)%${inputString}%"
                escapeValueForLike="true">
        <fetchPlan extends="_instance_name"/>
        <query>
            <![CDATA[select e from User e where e.username like :searchString escape '\' order by e.username asc]]>
        </query>
    </itemsQuery>
</entityComboBox>

The itemsQuery element for the ComboBox has the following attributes:

The itemsQuery element for the EntityComboBox has the following nested element:

Note: there is no entityClass and fetchPlan because ComboBox assumes that scalar values will be loaded. In case if entities use EntityComboBox.

<comboBox id="nameField" property="name">
    <itemsQuery searchStringFormat="(?i)%${inputString}%"
                escapeValueForLike="true">
        <query>
            <![CDATA[select e.username from User e where e.username like :searchString escape '\' order by e.username asc]]>
        </query>
    </itemsQuery>
</comboBox>

The itemsQuery element for the MultiSelectComboBox and MultiSelectComboBoxPicker has the following attributes:

The itemsQuery element for the MultiSelectComboBox and MultiSelectComboBoxPicker has the following nested elements:

<multiSelectComboBox id="tagsField" property="tags">
    <itemsQuery class="com.company.demo.entity.Tag"
                searchStringFormat="(?i)%${inputString}%"
                escapeValueForLike="true">
        <fetchPlan extends="_instance_name"/>
        <query>
            <![CDATA[select e from Tag e where e.name like :searchString escape '\' order by e.name asc]]>
        </query>
    </itemsQuery>
</multiSelectComboBox>

Note: Because both MultiSelectComboBox and MultiSelectComboBoxPicker work with entities and scalar values itemsQuery has class attribute but its optional.

View Controller

If itemsQuery is not defined, you should programmatically set the list of options using setItemsFetchCallback.

<entityComboBox id="assigneeField" property="assignee">
    <actions>
        <action id="entityLookup" type="entity_lookup"/>
        <action id="entityClear" type="entity_clear"/>
    </actions>
</entityComboBox>
@Install(to = "assigneeField", subject = "itemsFetchCallback")
private Stream<User> assigneeFieldItemsFetchCallback(final Query<User, String> query) {
    return usersDc.getItems().stream().skip(query.getOffset()).limit(query.getLimit());
}

Note: Because the real work is delegated to com.vaadin.flow.data.provider.HasLazyDataView#setItems(com.vaadin.flow.data.provider.CallbackDataProvider.FetchCallback<T,F>), the QueryTrace instance is passed to the fetch callback. As a result, there are several limitations:

Flaurite commented 1 year ago

I've tried implement suggestions for JmixMultiSelectComboBox using CallbackDataProvider.FetchCallback similar to io.jmix.ui.component.TagField:

protected Stream<UserDetails> onFieldFetchCallback(Query<UserDetails, String> query) {
    int offset = query.getOffset(); // ignore value
    int limit = query.getLimit(); // ignore value

    String enteredValue = query.getFilter().orElse(null);

    return StringUtils.isNotBlank(enteredValue)
            ? (Stream<UserDetails>) userRepository.getByUsernameLike(enteredValue).stream()
            : Stream.empty();
}

But there is a problem with value conversion in JmixMultiSelectComboBox#onValueChange(). Loaded options are empty due to calling fetch with empty Query. So value conversion produces empty values.

knstvk commented 1 year ago

Needs some improvements:

  1. The variable inside searchStringFormat should be named inputString
  2. Ability to specify inline fetchPlan in nested fetchPlan element. Or maybe add also nested query element as in data loaders?
knstvk commented 1 year ago

Studio issue: https://youtrack.jmix.io/issue/JST-4346/Support-for-itemsQuery

MaxKatyshev commented 12 months ago

Jmix version: 2.1.999-SNAPSHOT Jmix Studio plugin version: 2.1.SNAPSHOT5666-232 IntelliJ version: IntelliJ IDEA 2023.2.3 (Ultimate Edition) verified