3.5.3.4.3. Зависимости между компонентами данных
Иногда требуется загружать и отображать данные, которые зависят от других данных в том же экране. К примеру, на скриншоте ниже таблица слева отображает список заказов, а таблица справа - список строк выбранного заказа. Список справа обновляется каждый раз, когда меняется выбранный заказ в таблице слева.
В нашем примере сущность Order
содержит атрибут orderLines
, который является коллекцией с отношением one-to-many. Самый простой способ реализации экрана - загружать список заказов с представлением, содержащим атрибут orderLines
, и использовать property container для работы со списком зависимых строк. Затем мы связываем левую таблицу с родительским контейнером, а правую - с контейнером свойства.
Однако этот подход может иметь последствия для производительности, ведь мы загружаем все строки для всех заказов из левой таблицы, несмотря на то, что в один момент времени отображаются строки только для одного выбранного заказа. При этом чем длиннее список заказов, тем больше ненужных данных будет загружено, и вероятность того, что пользователь захочет просмотреть все строки, очень мала. Поэтому мы рекомендуем использовать контейнеры свойств и расширенные представления только тогда, когда нужно загрузить единственный экземпляр родительской сущности: например, в экране редактирования одного заказа.
Кроме того, родительская сущность может не иметь прямого атрибута, указывающего на зависимую сущности. В этом случае подход с использованием контейнера свойств совсем не подходит.
Наилучшей практикой организации отношений между данными в экране является использование запросов с параметрами. Зависимый загрузчик содержит запрос с параметром, который связывает данные с родительским контейнером, и когда меняется текущий экземпляр в родительском контейнере, мы передаём его в качестве параметра и вызываем зависимый загрузчик.
Рассмотрим пример экрана, в котором есть две зависимых пары контейнер/загрузчик и привязанные к ним таблицы для отображения данных.
<window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd">
<data>
<collection id="ordersDc" (1)
class="com.company.sales.entity.Order" view="order-with-customer">
<loader id="ordersDl">
<query>select e from sales_Order e></query>
</loader>
</collection>
<collection id="orderLinesDc" (2)
class="com.company.sales.entity.OrderLine" view="_local">
<loader id="orderLinesDl">
<query>select e from sales_OrderLine e where e.order = :order</query>
</loader>
</collection>
</data>
<layout>
<hbox id="mainBox" width="100%" height="100%" spacing="true">
<table id="ordersTable" width="100%" height="100%"
dataContainer="ordersDc"> (3)
<columns>
<column id="customer"/>
<column id="date"/>
<column id="amount"/>
</columns>
<rows/>
</table>
<table id="orderLinesTable" width="100%" height="100%"
dataContainer="orderLinesDc"> (4)
<columns>
<column id="product"/>
<column id="quantity"/>
</columns>
<rows/>
</table>
</hbox>
</layout>
</window>
1 | Родительский контейнер и загрузчик. |
2 | Дочерний контейнер и загрузчик. |
3 | Основная таблица. |
4 | Зависимая таблица. |
package com.company.sales.web.order;
import com.company.sales.entity.Order;
import com.company.sales.entity.OrderLine;
import com.haulmont.cuba.gui.model.CollectionLoader;
import com.haulmont.cuba.gui.model.InstanceContainer;
import com.haulmont.cuba.gui.screen.*;
import javax.inject.Inject;
@UiController("order-list")
@UiDescriptor("order-list.xml")
@LookupComponent("ordersTable")
public class OrderList extends StandardLookup<Order> { (1)
@Inject
private CollectionLoader<Order> ordersDl;
@Inject
private CollectionLoader<OrderLine> orderLinesDl;
@Subscribe
protected void onBeforeShow(BeforeShowEvent event) {
ordersDl.load(); (2)
}
@Subscribe(id = "ordersDc", target = Target.DATA_CONTAINER)
protected void onOrdersDcItemChange(InstanceContainer.ItemChangeEvent<Order> event) {
orderLinesDl.setParameter("order", event.getItem()); (3)
orderLinesDl.load();
}
}
1 | Класс контроллера экрана не содержит аннотации @LoadDataBeforeShow , поэтому загрузчики не будут вызваны автоматически. |
2 | Родительский загрузчик вызывается обработчиком BeforeShowEvent . |
3 | В обработчике родительского контейнера ItemChangeEvent передаём параметр в зависимый загрузчик и вызываем его. |
Фасет DataLoadCoordinator позволяет устанавливать связи между компонентами данных декларативно без написания кода на Java. |