3.5.1.4. Использование фрагментов экранов

В данном разделе рассматриваются примеры определения и использования фрагментов экранов. См. также раздел События ScreenFragment для получения информации о событиях жизненного цикла фрагментов.



Декларативное использование фрагмента

Предположим, имеется фрагмент для ввода адреса:

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>

Он может быть включен в некоторый экран с помощью элемента fragment с атрибутом screen, указывающим на id фрагмента, который задан в аннотации @UiController:

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>

Элемент fragment может быть добавлен в любой UI-контейнер экрана, в том числе в корневой элемент layout.

Программное использование фрагмента

Тот же самый фрагмент может быть включен в экран программно в обработчике InitEvent или AfterInitEvent как показано ниже:

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()); (3)
    }
}
1 - инжекция бина Fragments, который предназначен для инстанциирования фрагментов
2 - создание экземпляра контроллера фрагмента по его классу
3 - получение визуального компонента Fragment из контроллера и добавление его в UI-контейнер

Если фрагменту нужны какие-либо параметры, установите их через публичные сеттеры перед добавлением фрагмента в экран. Тогда параметры будут доступны в обработчиках событий InitEvent и AfterInitEvent контроллера фрагмента.

Передача параметров в фрагменты

Контроллер фрагмента может иметь публичные сеттеры для получения параметров, как это делается при открытии экранов. Если фрагмент открывается программно, то сеттеры можно вызвать явно:

@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 - передача параметра перед добавлением фрагмента в экран.

Если фрагмент добавляется в экран декларативно в XML, для передачи параметров можно использовать элемент properties, например:

<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 - передача строкового параметра в метод setStrParam().
2 - передача контейнера данных в метод setDataContainerParam().
3 - передача компонента TextField в метод setComponentParam().

Атрибут value используется для указания значений, атрибут ref - для указания идентификаторов компонентов экрана. Сеттеры должны иметь параметры подходящего типа.

Компоненты данных в фрагментах

Фрагмент экрана может иметь свои собственные контейнеры и загрузчики данных, определенные в XML-элементе data. В то же время, фреймворк создает единственный экземпляр DataContext для экрана и всех его фрагментов. Поэтому все загруженные сущности помещаются в один контекст и их изменения сохраняются, когда экран выполняет коммит.

Далее рассматривается пример использования собственных компонентов данных в фрагменте.

Предположим, имеется сущность City, и во фрагменте вместо текстового поля необходимо отобразить выпадающий список с имеющимися городами. Во фрагменте можно определить компоненты данных точно так же, как в обычном экране:

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>

Для того, чтобы загрузить данные в момент открытия включающего экрана, необходимо подписаться на событие экрана:

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 - подписка на BeforeShowEvent включающего экрана

Аннотация @LoadDataBeforeShow в фрагментах экранов не действует.

Контейнеры данных, предоставляемые экраном

Следующий пример демонстрирует использование контейнеров данных, предоставляемых включающим экраном.

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 - контейнер данных, который используется фрагментом ниже
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" означает, что контейнер с таким же id должен существовать во включающем экране или фрагменте, т.е. должен быть предоставлен извне
2 - UI-компоненты соединены с предоставленным контейнером данных

В XML-элементе, имеющем provided="true", все атрибуты за исключением id игнорируются, но могут присутствовать для обеспечения работы инструментов разработки.