Flow UI <select/> component setItems() method sets value to null

Jmix version: 2.2.1
Jmix Studio plugin version: 2.2.0-233
IntelliJ IDEA 2023.3.3 (Community Edition)
Build #IC-233.14015.106, built on January 25, 2024
Runtime version: 17.0.9+7-b1087.11 x86_64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
GC: G1 Young Generation, G1 Old Generation
Kotlin: 233.14015.106-IJ
java 17.0.4 2022-07-19 LTS
Java™ SE Runtime Environment (build 17.0.4+11-LTS-179)
Java HotSpot™ 64-Bit Server VM (build 17.0.4+11-LTS-179, mixed mode, sharing)
Operating System: macOS 14.4 (23E214)
Metal Rendering: ON
File System: Case-Sensitive Journaled HFS+ (APFS)
Browser: Safari - Version 17.4 (19618.1.15.11.12)
Database: PostgreSQL 13

Hello Jmix Team

I am in the process of migrating my application to the Flow UI and have discovered an unexpected behaviour with the setItems() method, that I believe is incorrect.

I am replacing the Jmix 1.5.x <comboBox/> component with the Flow UI <select/> component and since I am cascading the selections for multiple <select/> components, I need to call the setItems() method depending upon previous selections.

In both CUBA and Jmix 1.x.x calling the setOptionsList() method of a component did not change the value of the entity attribute defined in the component’s property in the XML descriptor. In Jmix 2.x.x calling the setItems() method of a <select/> component sets the value of the entity attribute defined in the component’s property to null. You can see this in the following images; the first two images are from Jmix 1.5.5, where the Style value is retained after calling setOptionsList():

Jmix155_ListScreen

Jmix155_EditorScreen

With Jmix 2.2.1 Style is set to null when calling setItems():

Jmix221_ListView

Jmix221_DetailView

Here are the two example projects used to generate the images above:

combobox.zip (95.4 KB)
select.zip (105.1 KB)

This new situation creates at least these two problems in the DetailView:

  1. You cannot open the DetailView screen for editing (after the initial instance creation) and programmatically set the items without changing the value. Therefore, since the attribute is set to null without any user interaction, exiting a view using the cancel button always asks if the user wants to save their changes, even when they did not make any.

  2. If you want to avoid this problem (point 1 above), you must remove the attribute from the XML component’s property, but then you must set the component’s label initially and always manually handle the setting of the value or programmatically set the ValueSource after setting the items.

e.g.

    @Subscribe
    public void onBeforeShow(final BeforeShowEvent event) {

        styleField.setLabel(messages.getMessage("com.company.select.entity", "MusicChoice.style"));
        styleField.setItems(getMusicStyleList(musicChoiceDc.getItem().getCategory()));
        styleField.setValueSource(new ContainerValueSource<>(musicStyleDc, "style"));
        styleField.setValue(musicChoiceDc.getItem().getStyle());
    }

I used setOptionsList() in CUBA, respectively, in Jmix 1.5.x extensively and this new behaviour in Flow will require me to rewrite many views during the migration.

Can you please consider changing/correcting this setItems() behaviour. Thanks in advance for your support.

Best regards
Chris

Greetings,

Problem

As you’ve researched, indeed, there are significant disparities between versions 1.x and 2.x. However, it’s important to note that these changes stem from Vaadin, not Jmix. In Jmix 1.5, we were utilizing Vaadin 8, whereas currently, we’re working with Vaadin 24. Consequently, we’re won’t to alter Vaadin’s behavior.

Alternate solution

Certainly, you can define a Util class to encapsulate custom logic for setItems.
Aslo, we have several Util classes to support back capability with Jmix 1.5, see:

  • io.jmix.flowui.component.UiComponentUtils
  • io.jmix.flowui.kit.component.ComponentUtils
  • io.jmix.flowui.kit.component.ComponentUtils#setItemsMap()
  • io.jmix.bpmflowui.util#io.jmix.bpmflowui.util
/**
     * Adds new value that not in component list items to already initialized combobox
     * @param comboBox - component that already has items but doesn't contain new item value that will put
     * @param value - new value that not contains in comboBox's items
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    // todo relocate with method into UiComponentUtils
    public static void setAbsentValueInItems(JmixComboBox comboBox, @Nullable Object value) {
        if (value != null) {
            List<Object> list = comboBox.getGenericDataView().getItems().toList();
            if (!list.contains(value)) {
                comboBox.setItems(ListUtils.union(list, List.of(value)));
            }
            comboBox.setValue(value);
        }
    }

    /**
     * Adds new value that not in component list items to already initialized select
     * @param select - component that already has items but doesn't contain new item value that will put
     * @param value - new value that not contains in select's items
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void setAbsentValueInItems(Select select, @Nullable Object value) {
        if (value != null) {
            List<Object> list = select.getGenericDataView().getItems().toList();
            if (!list.contains(value)) {
                select.setItems(ListUtils.union(list, List.of(value)));
            }
            select.setValue(value);
        }
    }

ETC

Conclusion

  1. We will not modify UI component logics if these logics are provided by Vaadin.
  2. We strive to support backward compatibility logics wherever possible. If you require code logic to maintain compatibility with Jmix 1.5, you can adopt our strategy by defining your own Util class within your project to handle the 1.5 logics.

Regards,
Dmitry

@d.cherkasov

Hello Dmitry

Thank you very much for the detailed explanation and your suggested alternate solution and code. I prefer a less exotic solution and will therefore first try to pass the necessary attribute values to the views before they are opened and call the setItems() method from within a separate new method after the onInit() handler has been called but before the view’s InstanceContainer is wired to the entity attributes, instead of calling setItems() in the onBeforeShow() handler.

I have used this approach before, noted here: https://forum.jmix.io/t/jmix-2-x-x-opening-dialog-windows-documentation-additional-information-needed/4417, and though it will require considerable extra work, it will be a standard documented approach.

Best regards
Chris

1 Like

Small change made to the previous post to correct the order of the actions.

1 Like