Filter data from a LookUpField? Or simplify the filter?

Hello, I’m developing an application and in one of the screens I want to be able to filter the rows of a table by selecting a reference value in a LookUpField. It’s there a way to achieve this? The documentation doesn’t specifies if an action is triggered after a value from the lookupfield is selected. I also have to do research about how to apply this selection to the table data, maybe you can give me a hint?

The other option is to simplify the filter, but this is something that I have not even gotten.

Thanks!!

PS: Sorry for my bad english.

2 Likes

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.
2 Likes

Would this same method work using a click listener on a pie chart for filtering?

Yes, if you subscribe on SliceClick event and reproduce all steps described above. To add listener for SliceClick event use addSliceClickListener method:


@Inject
private Chart pieChart;

@Override
public void init(Map<String, Object> params) {
    super.init(params);
    pieChart.addSliceClickListener(event -> {
        showNotification("Click on " + event.getItem(), NotificationType.HUMANIZED);
    });
}

Going back to your above example, the xml provided is

  <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>

What would I do if “Role” was an enumeration? When creating a dataSource, it isn’t letting me choose an enum in the entity row.

If “Role” is an enumeration and you have “role” attribute in the User entity, you don’t have to specify an optionsDatasource. Instead, use the LookupField.setOptionsEnum() method to specify your enumeration class.

Using LookupField.setOptionsEnum(Enum.class); causes changes to the value of selected items in the table when the value of the lookupField is changed.

LookupField.addListener() has be deprecated, and LookupField.addValueChangeListener() is called whenever a table row is selected.

I want to filter table data, not change table data. I’m a little stumped.

Probably your filtering LookupField is also connected to the datasource which is also used for a table. Make sure you don’t set “datasource” and “property” attributes for the field, as in the above example.
See the explanation of “datasource”, “optionsDatasource” attributes and setOptionsEnum() method in the docs.

Thank you! That’s very helpful.

How to do it on version 7.1. Please give me example set custom filter with LookupField on version 7.1