3.5.3.4.3. Dependencies Between Data Components
Sometimes you need to load and display data which depends on other data in the same screen. For example, on the screenshot below the left table displays the list of orders and the right one displays the list of lines of the selected order. The right list is refreshed each time the selected item in the left list changes.
In this example, the Order
entity contains the orderLines
attribute which is a one-to-many collection. So the simplest way to implement the screen is to load the list of orders with a view containing the orderLines
attribute and use a property container to hold the list of dependent lines. Then bind the left table to the master container and the right table to the property container.
But this approach has the following performance implication: you will load lines for all orders from the left table, even though you display the order lines only for a single order at a time. And the longer the list of orders is, the more unneeded data is loaded, because there is a little chance that the user will go through all orders to see their lines. This is why we recommend using property containers and wide views only when loading a single master item, for example in an order editor screen.
Also, the master entity may have no direct property pointing to the dependent entity. In this case, the above approach with property container would not work at all.
The common approach to organize relations between data in a screen is to use queries with parameters. The dependent loader contains a query with a parameter which links data to the master, and when the current item in the master container changes, you set the parameter and trigger the dependent loader.
Below is an example of the screen which has two dependent container/loader pairs and the tables bound to them.
<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 | Master container and loader. |
2 | Dependent container and loader. |
3 | Master table. |
4 | Dependent table. |
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 | The screen controller class has no @LoadDataBeforeShow annotation, so the loaders will not be triggered automatically. |
2 | The master loader is triggered in the BeforeShowEvent handler. |
3 | In the ItemChangeEvent handler of the master container, a parameter is set to the dependent loader and it is triggered. |