3.5.1.4. Using Screen Fragments

In this section, we explain how to define and use screen fragments. See also ScreenFragment Events for how to handle fragment lifecycle events.



Declarative usage of a fragment

Suppose we have a fragment for entering an address:

AddressFragment.java
@UiController("demo_AddressFragment")
@UiDescriptor("address-fragment.xml")
public class AddressFragment extends ScreenFragment {
}
address-fragment.xml
<fragment xmlns="http://schemas.haulmont.com/cuba/screen/fragment.xsd">
    <layout>
        <textField id="cityField" caption="City"/>
        <textField id="zipField" caption="Zip"/>
    </layout>
</fragment>

Then we can include it to another screen using the fragment element with the screen attribute pointing to the fragment id, specified in its @UiController annotation:

host-screen.xml
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="Some Screen">
    <layout>
        <groupBox id="addressBox" caption="Address">
            <fragment screen="demo_AddressFragment"/>
        </groupBox>
    </layout>
</window>

The fragment element can be added to any UI-container of the screen, including the top-level layout element.

Programmatic usage of a fragment

The same fragment can be included in the screen programmatically in a InitEvent or AfterInitEvent handler as follows:

host-screen.xml
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="Some Screen">
    <layout>
        <groupBox id="addressBox" caption="Address"/>
    </layout>
</window>
HostScreen.java
@UiController("demo_HostScreen")
@UiDescriptor("host-screen.xml")
public class HostScreen extends Screen {

    @Inject
    private Fragments fragments; (1)

    @Inject
    private GroupBoxLayout addressBox;

    @Subscribe
    private void onInit(InitEvent event) {
        AddressFragment addressFragment = fragments.create(this, AddressFragment.class); (2)
        addressBox.add(addressFragment.getFragment()); (4)
    }
}
1 - inject the Fragments bean which is designed to instantiate screen fragments
2 - create the fragment’s controller by its class
3 - get the Fragment visual component from the controller and add it to a UI-container

If the fragment has parameters, you can set them via public setters prior to adding the fragment to the screen. Then the parameters will be available in InitEvent and AfterInitEvent handlers of the fragment controller.

Passing parameters to fragments

A fragment controller can have public setters to accept parameters as it is done when opening screens. If the fragment is opened programmatically, the setters can be invoked explicitly:

@UiController("demo_HostScreen")
@UiDescriptor("host-screen.xml")
public class HostScreen extends Screen {

    @Inject
    private Fragments fragments;

    @Inject
    private GroupBoxLayout addressBox;

    @Subscribe
    private void onInit(InitEvent event) {
        AddressFragment addressFragment = fragments.create(this, AddressFragment.class);
        addressFragment.setStrParam("some value"); (1)
        addressBox.add(addressFragment.getFragment());
    }
}
1 - pass a parameter before adding the fragment to the screen.

If the fragment is added to the screen declaratively in XML, use properties element to pass the parameters, for example:

<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="Some Screen">
    <data>
        <instance id="someDc" class="com.company.demo.entity.Demo"/>
    </data>
    <layout>
        <textField id="someField"/>
        <fragment screen="demo_AddressFragment">
            <properties>
                <property name="strParam" value="some value"/> (1)
                <property name="dataContainerParam" ref="someDc"/> (2)
                <property name="componentParam" ref="someField"/> (3)
            </properties>
        </fragment>
    </layout>
</window>
1 - pass a string parameter to setStrParam() method.
2 - pass a data container to setDataContainerParam() method.
3 - pass the TextField component to setComponentParam() method.

Use the value attribute to specify values and the ref attribute to specify identifiers of the screen components. Setters must have parameters of appropriate types.

Data components in screen fragments

A screen fragment can have its own data containers and loaders, defined in the data XML element. At the same time, the framework creates a single instance of DataContext for the screen and all its fragments. Therefore all loaded entities are merged to the same context and their changes are saved when the host screen is committed.

In the following example, we consider usage of own data containers and loaders in a screen fragment.

Suppose we have a City entity and in the fragment, instead of the text field, we want to show a drop-down list with available cities. We can define data components in the fragment descriptor as we would in a regular screen:

address-fragment.xml
<fragment xmlns="http://schemas.haulmont.com/cuba/screen/fragment.xsd">
    <data>
        <collection id="citiesDc" class="com.company.demo.entity.City" view="_base">
            <loader id="citiesLd">
                <query><![CDATA[select e from demo_City e ]]></query>
            </loader>
        </collection>
    </data>
    <layout>
        <lookupField id="cityField" caption="City" optionsContainer="citiesDc"/>
        <textField id="zipField" caption="Zip"/>
    </layout>
</fragment>

In order to load data in the fragment when the host screen is opened, we need to subscribe to the screen’s event:

AddressFragment.java
@UiController("demo_AddressFragment")
@UiDescriptor("address-fragment.xml")
public class AddressFragment extends ScreenFragment {

    @Inject
    private CollectionLoader<City> citiesLd;

    @Subscribe(target = Target.PARENT_CONTROLLER) (1)
    private void onBeforeShowHost(Screen.BeforeShowEvent event) {
        citiesLd.load();
    }
}
1 - subscribing to BeforeShowEvent of the host screen

The @LoadDataBeforeShow annotation does not work for screen fragments.

Provided data containers

The next example demonstrates how to use data containers of the host screen in the fragment.

host-screen.xml
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd"
        caption="Some Screen">
    <data>
        <instance id="addressDc" class="com.company.demo.entity.Address"/> (1)
    </data>
    <layout>
        <groupBox id="addressBox" caption="Address">
            <fragment screen="demo_AddressFragment"/>
        </groupBox>
    </layout>
</window>
1 - data container which is used in the fragment below
address-fragment.xml
<fragment xmlns="http://schemas.haulmont.com/cuba/screen/fragment.xsd">
    <data>
        <instance id="addressDc" class="com.company.demo.entity.Address"
                  provided="true"/> (1)

        <collection id="citiesDc" class="com.company.demo.entity.City" view="_base">
            <loader id="citiesLd">
                <query><![CDATA[select e from demo_City e]]></query>
            </loader>
        </collection>
    </data>
    <layout>
        <lookupField id="cityField" caption="City" optionsContainer="citiesDc"
                     dataContainer="addressDc" property="city"/> (2)
        <textField id="zipField" caption="Zip"
                   dataContainer="addressDc" property="zip"/>
    </layout>
</fragment>
1 - provided="true" means that the container with the same id must exist in a host screen or enclosing fragment, i.e it must be provided from outside
2 - UI-components are linked to the provided data container

In the XML element having provided="true", all attributes except id are ignored but can be present to provide information for design time tools.