Hi Federico,
Let’s take an example with User and Role entities from the CUBA security subsystem. Suppose you have to display the users in a table and filter them by a role which is selected from a LookupField.
Place LookupField and Table to the screen, create an options datasource for LookupField and a datasource for Table. To filter data in table’s datasource, add a “where” clause which uses a “component$” parameter referencing the LookupField, as mentioned here. The screen XML will look like this:
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
caption="msg://caption"
class="com.company.sample.gui.Users"
messagesPack="com.company.sample.gui">
<dsContext>
<collectionDatasource id="usersDs"
class="com.haulmont.cuba.security.entity.User"
view="user.browse">
<query>
<![CDATA[select e from sec$User e left join e.userRoles ur
where (ur.role.id in (:component$rolesLookupField))]]>
</query>
</collectionDatasource>
<collectionDatasource id="rolesDs"
class="com.haulmont.cuba.security.entity.Role"
view="_minimal">
<query>
<![CDATA[select e from sec$Role e order by e.name]]>
</query>
</collectionDatasource>
</dsContext>
<layout expand="usersTable"
spacing="true">
<lookupField id="rolesLookupField"
optionsDatasource="rolesDs"/>
<table id="usersTable"
width="100%">
<columns>
<column id="login"/>
<column id="name"/>
</columns>
<rows datasource="usersDs"/>
</table>
</layout>
</window>
To refresh the table’s datasource when a user selects a new value in LookupField, create a listener in the screen controller:
public class Users extends AbstractWindow {
@Inject
protected CollectionDatasource<User, UUID> usersDs;
@Inject
protected LookupField rolesLookupField;
@Override
public void init(Map<String, Object> params) {
rolesLookupField.addListener(new ValueListener() {
@Override
public void valueChanged(Object source, String property, Object prevValue, Object value) {
usersDs.refresh();
}
});
}
}
You may want to refresh the table only when the user clicks a special “Apply” button. Then add the button to the screen XML and write a handler method in the controller:
public class Users extends AbstractWindow {
@Inject
protected CollectionDatasource<User, UUID> usersDs;
public void onApplyBtnClick(Component source) {
usersDs.refresh();
}
}
There is a problem with the solution above: when no role is selected in the LookupField, the table is empty, because the table’s datasource query is executed with null parameter and yields empty list. CUBA provides a declarative way of excluding null-valued parameters from datasource queries. Look at the following datasource definition:
<collectionDatasource id="usersDs"
class="com.haulmont.cuba.security.entity.User"
view="user.browse">
<query>
<![CDATA[select e from sec$User e]]>
<filter>
<c join="left join e.userRoles ur">ur.role.id in (:component$rolesLookupField)</c>
</filter>
</query>
</collectionDatasource>
Here the basic query does not have the dependency on the LookupField but defines a nested “filter” element, which adds conditions to the query only if their parameters are not null. Now when nothing is selected in the roles’ LookupField, the table displays all users instead of the empty list.
The filter element can be defined only manually in the XML. Studio does not show it.
Now about the second part of your question. As I understand, you are looking for a way to simplify the generic Filter component. It can be done to some extent:
- Add the Filter component to the screen, link it to the users datasource and assign the ID attribute (this is necessary).
- At runtime, add a custom condition with “Join” field set to “left join {E}.userRoles ur”, “Where” field set to “ur.role.id in (?)”, “Param type”=“Entity” and “Entity/Enum”=“Role”
- Save the filter and make it default (see the items in the filter settings dropdown). Now the filter will be selected when the user opens the screen.
- Then go to the screen XML and add editable=“false” attribute (this can also be done in Studio). Now the users will only be able to use the predefined filters stored in the database for this screen.