使用 Fragments

本节介绍如何定义并使用 fragment。以及 Fragment 事件 部分。

声明式使用

假设有一个 fragment 用于输入地址:

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>
Studio 中可以通过 视图创建向导 创建 fragment,创建时,需选择 Blank fragment 模板。

然后可以在视图中使用 fragment 元素引入 fragment,其中 class 属性需要指定为 fragment 类的完全限定名(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 元素可以添加到视图中的任意布局组件中,包括根元素 layout

在 Studio 的 视图设计器 中,通过 Add Component 操作打开组件工具箱。找到 Fragment 组件,拖放至组件结构或 XML 中。

编程式使用

Fragment 可以使用 Fragments bean 通过编程的方式添加到视图中,示例:

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 bean 用于初始化 fragment。
2 使用 fragment 的类进行创建。
3 将 fragment 实例添加至 `Details`布局组件。
如果一个 fragment 订阅了父视图的 事件,则该 fragment 需要在相应的事件触发之前添加到视图中。

传递参数

Fragment 控制器可以有 public 的 setter,在打开视图时可以用来接收参数。示例:

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 setter 方法可以为 fragment 设置参数。

如果 fragment 是通过声明式的方式在 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 bean 进行转换。Setter 的参数类型必须匹配。

如需通过 Jmix Studio 添加参数,在视图 XML 或 Jmix UI 层级结构面板中选择 fragment,然后点击属性面板中的 Add→Properties→Property

如果 fragment 是以编程的方式创建的,那么可以直接调用 setter:

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 在添加到视图之前传参。

使用数据组件

一个 fragment 可以有自己的数据容器和加载器,在 XML 的 data 元素定义。同时,框架会为视图及其所有的 fragments 创建单一的 DataContext 实例。因此,所有加载出来的实体都合并在一个 context 中,父视图保存数据时一同保存。

下面的示例展示了如何在 fragment 中使用自有的数据容器和加载器。

假设有 City 实体,需要展示包含所有城市的下拉列表。与普通视图一样,可以在 fragment 的 XML 中定义数据组件:

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 fragment 订阅父视图的 BeforeShowEvent 事件,当父视图打开时加载 fragment 的数据。

使用父数据组件

下面的示例展示如何在 fragment 中使用父视图的数据容器。

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 将在下面 fragment 中使用的数据容器。
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" 表示这个 id 的数据容器必须在父视图或 fragment 中存在。
2 UI 与父数据容器关联。

在具有 provided="true" 的 XML 元素中,除了 id,其他的属性都会被忽略,但是也可以保留为设计时工具提供信息。

Fragment 也可以定义由父视图提供的数据加载器。其 id 必须与父视图的加载器 id 一致,并使用 provided="true" 属性。示例:

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