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

В данном разделе рассматриваются примеры определения и использования фрагментов экранов.



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

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

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

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

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

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

Подписка на события включающего экрана

В контроллере фрагмента можно подписаться на события включающего экрана путем указания значения PARENT_CONTROLLER в атрибуте target аннотации, например:

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

Таким способом можно обработать любое событие, в том числе InitEntityEvent, посылаемое экранами редактирования.