jmix icon indicating copy to clipboard operation
jmix copied to clipboard

Support facets in Fragment

Open Flaurite opened this issue 1 year ago • 2 comments

Flaurite avatar Feb 14 '25 11:02 Flaurite

Facets deeply integrated into views. To support Facets we need to make breaking changes.

glebfox avatar Feb 28 '25 07:02 glebfox

Example is: ability to use Timer facet in fragment. E.g. fragment displays some data and we want it to auto-update itself.

alexbudarov avatar Nov 26 '25 12:11 alexbudarov

Facets in Fragment

Now all previously existing facets are available for fragments as well.

General information

There are five facets available for use:

<fragment xmlns="http://jmix.io/schema/flowui/fragment"
          xmlns:dynattr="http://jmix.io/schema/dynattr/flowui">
    <!-- some code -->

    <facets>
        <timer id="timer" delay="1000" autostart="true" repeating="true"/>
        <fragmentSettings auto="true"/>
        <fragmentDataLoadCoordinator auto="true"/>
        <urlQueryParameters>
            <dataGridFilter component="usersDataGrid"/>
            <pagination component="simplePagination"/>
            <propertyFilter component="propertyFilter"/>
            <genericFilter component="genericFilter"/>
        </urlQueryParameters>
        <dynattr:dynamicAttributes/>
    </facets>

    <content>
        <!-- some code -->
    </content>
</fragment>
  • timer – a facet that creates a special timer element on the client-side, the ticks of which can be subscribed to
  • fragmentSettings – the same as the settings facet for the view, allows you to save UI component settings for a specific user
  • fragmentDataLoadCoordinator – allows you to control how data is loaded into a facet
  • urlQueryParameters – allows you to persist the state of UI components in a URL
  • dynamicAttributes – allows you to embed dynamic attributes into visual components

timer

The facet that creates a special timer element on the client-side.

The same implementation is used as in the case of view facet. Thus, all the properties and events for subscription remain the same and work in the same way for fragments. See: documentation link

Example of use:

<fragment xmlns="http://jmix.io/schema/flowui/fragment">
    <facets>
        <timer id="timer" delay="1000" repeating="true" autostart="true"/>
    </facets>
    <content>
        <vbox id="root">
            <textField id="displayField" readOnly="true"/>
        </vbox>
    </content>
</fragment>
@FragmentDescriptor("timer-fragment.xml")
public class TimerFragment extends Fragment<VerticalLayout> {

    @ViewComponent
    private TypedTextField<String> displayField;

    private int tick;

    @Subscribe("timer")
    public void onTimerTimerAction(final Timer.TimerActionEvent event) {
        displayField.setValue("Timer tick: " + tick++);
    }
}

Result:

https://github.com/user-attachments/assets/b0e6f6d9-f599-41db-9154-b57747c2e323

fragmentSettings

The facet is used to save settings for visual components. Saving occurs when the view is closed, as with the setting facet for a view.

The saved settings are applied at the ReadyEvent event for the view lifecycle when using ViewSettingsFacet. In case of fragmentSettings the saved settings are applied at the HostView.ReadyEvent event for the fragment lifecycle. In this way, the settings are applied simultaneously at the very latest moment in the lifecycle of both the view and the fragment.

Each fragment on the view stores its own set of settings. Therefore, a fragment with a fragmentSettings facet must have an ID.

A separate java implementation is used for both views and fragments, which is why a new XML tag for fragments was introduced: fragmentSettings.

Example of use:

<fragment xmlns="http://jmix.io/schema/flowui/fragment">
    <data>
        <collection id="usersDc"
                    class="com.company.facetsforfragments.entity.User">
            <fetchPlan extends="_base"/>
            <loader id="usersDl" readOnly="true">
                <query>
                    <![CDATA[select e from User e order by e.username]]>
                </query>
            </loader>
        </collection>
    </data>
    <facets>
        <fragmentSettings auto="true"/>
        <fragmentDataLoadCoordinator auto="true"/>
        <urlQueryParameters>
            <pagination component="pagination"/>
        </urlQueryParameters>
    </facets>
    <content>
        <vbox id="root">
            <details id="details" summaryText="Nested details">
                <avatar abbreviation="Jmix"/>
            </details>
            <genericFilter id="genericFilter"
                           dataLoader="usersDl">
                <properties include=".*"/>
            </genericFilter>
            <simplePagination id="pagination" dataLoader="usersDl"
                              itemsPerPageVisible="true" itemsPerPageItems="1, 2, 5, 10"/>
            <dataGrid id="usersDataGrid"
                      width="100%"
                      columnReorderingAllowed="true"
                      minHeight="20em"
                      dataContainer="usersDc">
                <columns resizable="true">
                    <column property="username" filterable="true"/>
                    <column property="firstName" filterable="true"/>
                    <column property="lastName" filterable="true"/>
                    <column property="email" filterable="true"/>
                    <column property="timeZoneId" filterable="true"/>
                    <column property="active" filterable="true"/>
                </columns>
            </dataGrid>
        </vbox>
    </content>
</fragment>
@FragmentDescriptor("settings-fragment.xml")
public class SettingsFragment extends Fragment<VerticalLayout> {
}

Result:

https://github.com/user-attachments/assets/5529deee-dae0-4250-8456-6e79c2700ae9

fragmentDataLoadCoordinator

A facet is used to control data loading into a fragment. For example, you can set a loading trigger on some fragment lifecycle event. Unlike the view, the facet has four lifecycle events:

  • Fragment.ReadyEvent
  • HostView.InitEvent
  • HostView.BeforeShowEvent
  • HostView.ReadyEvent

The rest of the functionality is similar to the original dataLoadCoordinator facet for view: See: documentation link

Example of use:

<fragment xmlns="http://jmix.io/schema/flowui/fragment">
    <data>
        <collection id="usersDc"
                    class="com.company.facetsforfragments.entity.User">
            <fetchPlan extends="_base"/>
            <loader id="usersDl" readOnly="true">
                <query>
                    <![CDATA[select e from User e order by e.username]]>
                </query>
            </loader>
        </collection>
    </data>
    <facets>
        <fragmentDataLoadCoordinator auto="true"/>
    </facets>
    <content>
        <vbox id="root">
            <details id="details" summaryText="Nested details">
                <avatar abbreviation="Jmix"/>
            </details>
            <genericFilter id="genericFilter"
                           dataLoader="usersDl">
                <properties include=".*"/>
            </genericFilter>
            <simplePagination id="pagination" dataLoader="usersDl"
                              itemsPerPageVisible="true" itemsPerPageItems="1, 2, 5, 10"/>
            <dataGrid id="usersDataGrid"
                      width="100%"
                      columnReorderingAllowed="true"
                      minHeight="20em"
                      dataContainer="usersDc">
                <columns resizable="true">
                    <column property="username" filterable="true"/>
                    <column property="firstName" filterable="true"/>
                    <column property="lastName" filterable="true"/>
                    <column property="email" filterable="true"/>
                    <column property="timeZoneId" filterable="true"/>
                    <column property="active" filterable="true"/>
                </columns>
            </dataGrid>
        </vbox>
    </content>
</fragment>
@FragmentDescriptor("data-load-coordinator-fragment.xml")
public class DataLoadCoordinatorFragment extends Fragment<HorizontalLayout> {
}

Result: Data will be loaded

Image

urlQueryParameters

A facet is used to store the state of components in a URL.

This is convenient when using the URL-first approach, when leaving the view and returning to it, it is necessary to save some information (for example, the state of filters).

The operating principle is similar to the original urlQueryParameters facet for view. For the facet to work, the fragment must have an ID, as its value is used for subsequent serialization.

Example of use:

<fragment xmlns="http://jmix.io/schema/flowui/fragment">
    <data>
        <collection id="usersDc"
                    class="com.company.facetsforfragments.entity.User">
            <fetchPlan extends="_base"/>
            <loader id="usersDl" readOnly="true">
                <query>
                    <![CDATA[select e from User e order by e.username]]>
                </query>
            </loader>
        </collection>
    </data>
    <facets>
        <fragmentDataLoadCoordinator auto="true"/>
        <urlQueryParameters>
            <propertyFilter component="propertyFilter"/>
            <genericFilter component="genericFilter"/>
            <dataGridFilter component="usersDataGrid"/>
            <pagination component="pagination"/>
        </urlQueryParameters>
    </facets>
    <content>
        <vbox id="root">
            <propertyFilter id="propertyFilter" property="username" operation="CONTAINS" dataLoader="usersDl"/>
            <genericFilter id="genericFilter"
                           dataLoader="usersDl">
                <properties include=".*"/>
            </genericFilter>
            <simplePagination id="pagination" dataLoader="usersDl"
                              itemsPerPageVisible="true" itemsPerPageItems="1, 2, 5, 10"/>
            <dataGrid id="usersDataGrid"
                      width="100%"
                      columnReorderingAllowed="true"
                      minHeight="20em"
                      dataContainer="usersDc">
                <columns resizable="true">
                    <column property="username" filterable="true"/>
                    <column property="firstName" filterable="true"/>
                    <column property="lastName" filterable="true"/>
                    <column property="email" filterable="true"/>
                    <column property="timeZoneId" filterable="true"/>
                    <column property="active" filterable="true"/>
                </columns>
            </dataGrid>
        </vbox>
    </content>
</fragment>
@FragmentDescriptor("url-query-parameters-fragment.xml")
public class UrlQueryParametersFragment extends Fragment<VerticalLayout> {
}

Result:

https://github.com/user-attachments/assets/cdc487a0-d425-413a-8ef6-f192dbef7d72

Take a closer look at the URL. It stores the filter's state: Image

dynamicAttributes

A facet allows you to embed dynamic attributes into visual components. Used as part of the dynamic attributes add-on.

Example of use:

Setup the dynamic attribute category with the category attribute:

Image

Then add the facet into the fragment xml:

<fragment xmlns="http://jmix.io/schema/flowui/fragment"  
          xmlns:dynattr="http://jmix.io/schema/dynattr/flowui">
    <data>
        <collection id="usersDc"
                    class="com.company.facetsforfragments.entity.User">
            <fetchPlan extends="_base"/>
            <loader id="usersDl" readOnly="true">
                <query>
                    <![CDATA[select e from User e order by e.username]]>
                </query>
            </loader>
        </collection>
    </data>
    <facets>
        <dynattr:dynamicAttributes/>
        <fragmentDataLoadCoordinator auto="true"/>
    </facets>
    <content>
        <vbox id="root">
            <dataGrid id="usersDataGrid"
                      width="100%"
                      columnReorderingAllowed="true"
                      minHeight="20em"
                      dataContainer="usersDc">
                <columns resizable="true">
                    <column property="username"/>
                    <column property="firstName"/>
                    <column property="lastName"/>
                    <column property="email"/>
                    <column property="timeZoneId"/>
                    <column property="active"/>
                </columns>
            </dataGrid>
        </vbox>
    </content>
</fragment>
@FragmentDescriptor("dyn-attr-fragment.xml")
public class DynAttrFragment extends Fragment<VerticalLayout> {
}

Result:

Image

KremnevDmitry avatar Dec 23 '25 09:12 KremnevDmitry