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

Этот раздел объясняет, как определять и использовать фрагменты. См. также раздел События фрагмента.

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

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

AddressFragment.java
@FragmentDescriptor("address-fragment.xml")
public class AddressFragment extends Fragment<FormLayout> {
}
address-fragment.xml
<fragment xmlns="http://jmix.io/schema/flowui/fragment">
    <content>
        <formLayout>
            <textField id="cityField" label="City"/>
            <textField id="zipcodeField" label="Zipcode"/>
        </formLayout>
    </content>
</fragment>
Фрагмент можно создать с помощью мастера создания экранов в Студии. Выберите шаблон Blank fragment из списка шаблонов экранов.

Фрагмент можно включить в экран, используя элемент fragment с атрибутом class, в котором указан FQN класса фрагмента:

host-view.xml
<view xmlns="http://jmix.io/schema/flowui/view">
    <layout>
        <details id="addressDetails" summaryText="Address" opened="true" alignSelf="STRETCH">
            <fragment class="com.company.onboarding.view.address.var1.AddressFragment"/>
        </details>
    </layout>
</view>

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

В визуальном дизайнере Studio используйте действие Add Component, чтобы открыть палитру компонентов. Найдите в ней компонент Fragment и перетащите его в структуру компонентов или в XML.

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

Этот же фрагмент может быть включен в экран программно используя бин Fragments, например:

host-view.xml
<view xmlns="http://jmix.io/schema/flowui/view">
    <layout>
        <details id="addressDetails" summaryText="Address" opened="true" alignSelf="STRETCH"/>
    </layout>
</view>
HostView.java
@Route(value = "HostView", layout = MainView.class)
@ViewController("HostView")
@ViewDescriptor("host-view.xml")
public class HostView extends StandardView {

    @ViewComponent
    private Details addressDetails;

    @Autowired
    private Fragments fragments; (1)

    @Subscribe
    public void onInit(InitEvent event) {
        AddressFragment addressFragment = fragments.create(this, AddressFragment.class); (2)
        addressDetails.add(addressFragment); (3)
    }
}
1 Инжекция бина Fragments, который предназначен для создания фрагментов.
2 Создание экземпляра фрагмента по его классу.
3 Добавление экземпляра фрагмента в компонент Details.
Если фрагмент подписан на событие хост-экрана, то фрагмент должен быть создан и добавлен в экран перед тем, как это событие будет вызвано.

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

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

AddressFragment.java
@FragmentDescriptor("address-fragment.xml")
public class AddressFragment extends Fragment<FormLayout> {

    @ViewComponent
    private EntityComboBox<City> cityField;
    @ViewComponent
    private TypedTextField<String> zipcodeField;

    public void setCitiesContainer(CollectionContainer<City> citiesContainer) { (1)
        cityField.setItems(citiesContainer);
    }

    public void setZipcodePlaceholder(String placeholder) {
        zipcodeField.setPlaceholder(placeholder);
    }
}
1 Сеттеры позволяют передавать параметры во фрагмент.

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

host-view.xml
<view xmlns="http://jmix.io/schema/flowui/view">
    <data>
        <collection id="citiesDc"
                    class="com.company.onboarding.entity.City">
            <fetchPlan extends="_base"/>
            <loader id="citiesDl" readOnly="true">
                <query>
                    <![CDATA[select e from City e]]>
                </query>
            </loader>
        </collection>
    </data>
    <facets>
        <dataLoadCoordinator auto="true"/>
    </facets>
    <layout>
        <details id="addressDetails" summaryText="Address" opened="true" alignSelf="STRETCH">
            <fragment class="com.company.onboarding.view.address.var2.AddressFragment">
                <properties>
                    <property name="citiesContainer" value="citiesDc" type="CONTAINER_REF"/> (1)
                    <property name="zipcodePlaceholder" value="Zipcode"/> (2)
                </properties>
            </fragment>
        </details>
    </layout>
</view>
1 Передает контейнер данных методу setCitiesContainer().
2 Передает строковый параметр методу setZipcodePlaceholder().

Используйте атрибут value для указания значений и необязательный атрибут type для указания того, что строковое значение должно быть преобразовано одним из подключаемых бинов PropertyParser. Сеттеры должны иметь параметры соответствующих типов.

Для добавления параметра в Jmix Studio выберите fragment в XML-дескрипторе экрана или в панели структуры Jmix UI и нажмите кнопку Add→Properties→Property в панели инспектора Jmix UI.

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

HostView.java
@ViewComponent
private CollectionContainer<City> citiesDc;

@Autowired
private Fragments fragments;

@Subscribe
public void onInit(InitEvent event) {
    AddressFragment addressFragment = fragments.create(this, AddressFragment.class);
    addressFragment.setCitiesContainer(citiesDc); (1)
    addressFragment.setZipcodePlaceholder("Zipcode");
    getContent().add(addressFragment);
}
1 Перед добавлением в экран в созданный фрагмент передается параметр.

Использование компонентов данных

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

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

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

address-fragment.xml
<fragment xmlns="http://jmix.io/schema/flowui/fragment">
    <data>
        <collection id="citiesDc"
                    class="com.company.onboarding.entity.City">
            <fetchPlan extends="_base"/>
            <loader id="citiesDl" readOnly="true">
                <query>
                    <![CDATA[select e from City e]]>
                </query>
            </loader>
        </collection>
    </data>
    <content>
        <formLayout id="addressForm">
            <entityComboBox id="cityField" label="City" itemsContainer="citiesDc"/>
            <textField id="zipcodeField" label="Zipcode"/>
        </formLayout>
    </content>
</fragment>
AddressFragment.java
@FragmentDescriptor("address-fragment.xml")
public class AddressFragment extends Fragment<FormLayout> {

    @ViewComponent
    private CollectionLoader<City> citiesDl;

    @Subscribe(target = Target.HOST_CONTROLLER)
    protected void onHostBeforeShow(View.BeforeShowEvent event) { (1)
        citiesDl.load();
    }
}
1 Фрагмент подписывается на BeforeShowEvent включающего экрана, поэтому данные фрагмента будут загружены при открытии экрана.

Предоставляемые компоненты данных

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

host-view.xml
<view xmlns="http://jmix.io/schema/flowui/view">
    <data>
        <instance id="addressDc"
                  class="com.company.onboarding.entity.Address"> (1)
            <fetchPlan extends="_base"/>
            <loader/>
        </instance>

        <collection id="citiesDc"
                    class="com.company.onboarding.entity.City">
            <fetchPlan extends="_base"/>
            <loader id="citiesDl" readOnly="true">
                <query>
                    <![CDATA[select e from City e]]>
                </query>
            </loader>
        </collection>
    </data>
    <facets>
        <dataLoadCoordinator auto="true"/>
    </facets>
    <layout>
        <details id="addressDetails" summaryText="Address" opened="true" alignSelf="STRETCH">
            <fragment class="com.company.onboarding.view.address.var4.AddressFragment"/>
        </details>
    </layout>
</view>
1 Контейнер данных, который используется во фрагменте ниже.
address-fragment.xml
<fragment xmlns="http://jmix.io/schema/flowui/fragment">
    <data>
        <instance id="addressDc"
                  class="com.company.onboarding.entity.Address"
                  provided="true"/> (1)

        <collection id="citiesDc"
                    class="com.company.onboarding.entity.City"
                    provided="true"/>
    </data>
    <content>
        <formLayout id="addressForm" dataContainer="addressDc">
            <entityComboBox id="cityField" itemsContainer="citiesDc" property="city"/> (2)
            <textField id="zipcodeField" property="zipcode"/>
        </formLayout>
    </content>
</fragment>
1 provided="true" означает, что контейнер с тем же идентификатором должен существовать во включающем экране или фрагменте.
2 Компонент UI связан с предоставленным контейнером данных.

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

Фрагмент может определять и загрузчики, предоставляемые включающим экраном. Предоставленный загрузчик должен иметь идентификатор, равный идентификатору загрузчика экрана, и атрибут provided="true". Например:

<loader id="addressDl" provided="true"/>