集合容器

CollectionContainer 接口用来容纳相同类型实例的集合。这个接口继承自 InstanceContainer

在 XML 描述中可以这样定义 CollectionContainer

<collection id="departmentsDc"
            class="com.company.onboarding.entity.Department">
    <fetchPlan extends="_base">
        <property name="hrManager" fetchPlan="_base"/>
    </fetchPlan>
    <loader id="departmentsDl">
        <query>
            <![CDATA[select e from Department e]]>
        </query>
    </loader>
</collection>

方法

CollectionContainer 定义了下列特殊方法:

  • setItems() - 为容器设置实体集合。

  • getItems() - 返回容器中保存的实体的不可变列表。可以用这个方法来遍历集合、获取 stream 或者根据实体位置获取单一实例。如果需要按照实体的 id 获取实例,使用 getItem(entityId) 方法。示例:

    @ViewComponent
    private CollectionContainer<Department> departmentsDc;
    
    private Optional<Department> findByName(String name) {
        return departmentsDc.getItems().stream()
                .filter(department -> Objects.equals(department.getName(), name))
                .findFirst();
    }
  • getMutableItems() - 返回容器中保存的实体的可变列表。所有对列表的改动,包括 add()addAll()remove()removeAll()set()clear() 方法都会产生 CollectionChangeEvent 事件,所以订阅了这个事件的可视化组件也会根据变化更新,示例:

    @ViewComponent
    private CollectionContainer<Department> departmentsDc;
    
    private void createDepartment() {
        Department department = metadata.create(Department.class);
        department.setName("Operations");
        departmentsDc.getMutableItems().add(department);
    }
    只有在需要更改集合的时候使用 getMutableItems(),否则应该使用 getItems(),防止意外改动。
  • setItem() - 为容器设置当前实例。如果提供的内容不是 null,则必须是集合中的一个对象。此方法会发送 ItemChangeEvent

    需要注意的是,类似 DataGrid 的可视化组件不会监听容器发送的 ItemChangeEvent 事件。所以如果需要在 DataGrid 中选中一行,需要使用 DataGridsetSelected() 方法,而不是容器的 setItem()。同时,容器的当前 item 也会更改,因为容器也监听了组件变化。示例:

    @ViewComponent
    private DataGrid<Department> departmentsTable;
    
    @ViewComponent
    private CollectionContainer<Department> departmentsDc;
    
    private void selectFirstRow() {
        departmentsTable.getSelectionModel()
                .select(departmentsDc.getItems().get(0));
    }
  • getItem() - 重写了 InstanceContainer 的同名方法,返回当前实例。如果当前实例没有设置,此方法会抛出一个异常。所以需要在确保容器有选中当前实例的时候才使用此方法,然后就不需要检查返回值是否为 null

  • getItemOrNull() - 重写了 InstanceContainer 的同名方法,返回当前实例。如果当前实例没有设置,此方法会返回 null。所以在使用此方法返回值之前总是需要先检查返回的是否是 null

  • getItemIndex(entityId) - 返回实例在 getItems()getMutableItems() 方法返回的列表中的位置。此方法接收 Object 对象,因此可以传给它 id 或者实体实例本身。容器的实现维护了一个 id 到索引的映射,所以这个方法即使在非常大的列表中也有很高效率。

  • getItem(entityId) - 根据实例的 id 返回此实例。这个是一个快捷方法,首先用 getItemIndex(entityId) 得到实例的位置,然后通过 getItems().get(index) 返回实例。所以如果需要找的实例不在集合中存在,则会抛出异常。

  • getItemOrNull(entityId) - 与 getItem(entityId) 类似,只不过在实例不存在的时候会返回 null。所以需要在使用前检查此方法的返回值是否是 null

  • containsItem(entityId) - 如果指定 id 的实体在集合中存在的话,返回 true。底层其实调用了 getItemIndex(entityId) 方法。

  • replaceItem(entity) - 如果在容器中有相同 id 的实例,则会被方法的输入参数的实例替换。如果不存在,则会添加新的实例到实例列表中。此方法会发送 CollectionChangeEvent 事件,根据具体的操作,事件类型可以是 SET_ITEM 或者 ADD_ITEMS

  • setSorter() - 设置此容器的排序器。Sorter 接口的标准实现是 CollectionContainerSorter。当容器关联到加载器时,会设置默认的排序器。如果需要,也可提供 自定义实现

  • getSorter() - 返回此容器当前设置的排序器。

事件

除了 InstanceContainer 的事件之外,还可以使用 CollectionContainer 接口的 CollectionChangeEvent 事件的监听器,该事件在容器内的实体集合改动时发送,比如,添加、删除和替换集合内元素。下面示例中订阅了该事件,容器在界面 XML 中使用 id departmentsDc 定义:

@Subscribe(id = "departmentsDc", target = Target.DATA_CONTAINER)
public void onDepartmentsDcCollectionChange(
        final CollectionContainer.CollectionChangeEvent<Department> event) {
    CollectionChangeType changeType = event.getChangeType(); (1)
    Collection<? extends Department> changes = event.getChanges(); (2)
    // ...
}
1 获取改动类型:REFRESHADD_ITEMSREMOVE_ITEMSSET_ITEM
2 从容器中添加或者删除的实体集合。如果改动类型是 REFRESH,框架不能确定具体是哪些实体添加或者删除,所以此时该集合为空。