3.5.1.4. Using Screen Fragments

In this section, we explain how to define and use screen fragments.



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)
        addressFragment.init(); (3)
        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 - initialize the screen fragment
4 - get the Fragment visual component from the controller and add it to a UI-container

Do not forget to initialize the screen fragment using the init() method after programmatic creation. If the fragment has parameters, you can set them via public setters prior to invoking init(). Then the parameters will be available in InitEvent and AfterInitEvent handlers of the fragment controller.

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.

Subscribing to host screen events

In a fragment controller, you can subscribe to events of the host screen by specifying the PARENT_CONTROLLER value in the target attribute of the annotation, for example:

@Subscribe(target = Target.PARENT_CONTROLLER)
private void onBeforeShowHost(Screen.BeforeShowEvent event) {
    //
}

Any event can be handled this way, including InitEntityEvent sent by entity editors.