Tokenlist in editable table

Hi,

I’m trying to get an editable table that contains a tokenlist (to be set per row). However, I’m unable to specify the datasource for the tokenlist appropriately such that it operates on the actual row content rather than some superset (as it appears to work now).

The table data refers to a list of authorisations related to a single information system and is included on the edit screen of the information system.

Relevant screen XML:


....
<datasource id="informationSystemDs"
            class="com.axxemble.base27.entity.InformationSystem"
            view="informationSystem-view">
    <collectionDatasource id="sysAuthorisationsDs"
                          property="authorisations">
        <collectionDatasource id="sysAuthGroups" property="groups"/>
        <collectionDatasource id="sysAuthUsers" property="users"/>
    </collectionDatasource>
</datasource>
....
<table id="sysAuthorisationsTable"
       editable="true"
       width="100%">
    <actions>
        <action id="add" invoke="addAuthorisation"/>
        <action id="remove" invoke="removeAuthorisation" trackSelection="true"/>
    </actions>
    <columns>
        <column id="name" editable="true"/>
        <column id="description" editable="true"/>
        <column id="groups" caption="msg://com.axxemble.base27.entity/Authorisation.groups" editable="true"/>
        <column id="users" caption="msg://com.axxemble.base27.entity/Authorisation.users" editable="true"/>
    </columns>
    <rows datasource="sysAuthorisationsDs"/>
    <rowsCount/>
    <buttonsPanel>
        <button icon="font-icon:PLUS" action="sysAuthorisationsTable.add" caption=""/>
        <button action="sysAuthorisationsTable.remove"
                caption="msg://Remove"/>
    </buttonsPanel>
</table>

Relevan controller code:


....
@Inject
private Table<Authorisation> sysAuthorisationsTable;

@Inject
private CollectionDatasource<Authorisation, UUID> sysAuthorisationsDs;

@Inject
private Metadata metadata;

@Inject
private CollectionDatasource<Group, UUID> groupsDs;
@Inject
private CollectionDatasource<Staff, UUID> staffsDs;

@Inject
private CollectionDatasource<Group, UUID> sysAuthGroups;
@Inject
private CollectionDatasource<Staff, UUID> sysAuthUsers;
@Inject
private ComponentsFactory componentsFactory;

@Override
public void init(Map<String, Object> params) {
    // Set controls for authorisation table
    sysAuthorisationsTable.addGeneratedColumn("groups", p -> {
        TokenList group = componentsFactory.createComponent(TokenList.class);
        group.setDatasource(sysAuthGroups);
        group.setOptionsDatasource(groupsDs);
        group.setInline(true);
        group.setClearButtonCaption("");
        group.setClearButtonIcon("font-icon:CLOSE");
        return group;
    });
    sysAuthorisationsTable.addGeneratedColumn("users", p -> {
        TokenList user = componentsFactory.createComponent(TokenList.class);
        user.setDatasource(sysAuthUsers);
        user.setOptionsDatasource(staffsDs);
        user.setInline(true);
        user.setClearButtonCaption("");
        user.setClearButtonIcon("font-icon:CLOSE");
        return user;
    });
}
 .....
// General save/cancel buttons for the information system
public void onBtnSaveClick() {
    saveAuthorisations();
    super.commitAndClose();
}

public void onBtnCancelClick() {
    close(CLOSE_ACTION_ID);
}

// Actions for the authorisations
public void addAuthorisation() {
    Authorisation auth = metadata.create(Authorisation.class);
    auth.setInfoSystem(getItem());
    sysAuthorisationsDs.addItem(auth);
}

public void removeAuthorisation() {
    if (sysAuthorisationsDs.getItem() != null) {
        sysAuthorisationsDs.removeItem(sysAuthorisationsDs.getItem());
    }
}

private void saveAuthorisations() {
    if (validateAll()) {
        sysAuthorisationsDs.commit();
        //showNotification(getMessage("changesSaved"), NotificationType.HUMANIZED);
    }
}

@Override
public boolean validateAll() {
    for (Authorisation auth : sysAuthorisationsDs.getItems()) {
        if (StringUtils.isEmpty(auth.getName())) {
            showNotification(getMessage("fillRequiredFields"), NotificationType.TRAY);
            return false;
        }
    }
    return super.validateAll();
}

When running the application, all works well but the values on the tokenlists are a superset of all selected items and are applied at each row in the table.

Any help appreciated.

Sorry, in case of TokenList this approach is not applicable since TokenList requires CollectionDatasource. I’ll try to prepare a sample project with TokenList generated column.

Thanks for getting back. I’m very interested in seeing your sample project.

Are there any other options to consider for such a multi-select control in a table?

Finally, I’ve found out that TokenList cannot be correctly rendered inside of editable Table.

In fact, I do not recommend including any multi select controls to an editable Table. I think it will be more convenient to use Master-Detail schema with Table and editing panel. In this editing panel you will be able to use any multi select component: OptionsGroup, OptionsList, TokenList, TwinColumn.

You can see how to create such an additional form for Table if you generate screen using Entity Browser/Editor Studio template.

If your options list is small enough than you can easily create OptionsGroup with HORIZONTAL orientation in generated cell using the recipe mentioned above.

1 Like

In my case, the options to select from might be rather large but the actual selected options only a few. So I needed to have the combined browser/editor. But I also wanted to show the selected options in the table.

As a suggestion for others, I finally found an acceptable solution (for my part that is) by ‘simulating’ a TokenList in the table and then use a TokenList in the details editor (combined with the browser screen). Main elements of the solution below.

Simulate tokenlist in table by creating a flowbox with labels inside:


            // Add a generated column into the table
            myTable.addGeneratedColumn("groups", r -> {
            FlowBoxLayout glist = componentsFactory.createComponent(FlowBoxLayout.class);
            glist.setHeightAuto();
            for (Group g : r.getGroups()){
                Label glabel = componentsFactory.createComponent(Label.class);
                glabel.setValue(g.getName());
                glabel.setStyleName("lblOptionList");
                glist.add(glabel);
            }
            return glist;
        });

With the css:


.lblOptionList {
	background-color: #fafafa;
    border-radius: 4px!important;
    padding-left: 6px;
    padding-right: 6px;
    white-space: nowrap;
    overflow: hidden;
    display: inline-block;
    line-height: 35px;
    font-size: 15px;
	margin-bottom: 3px;
	margin-right: 3px;
}

And add a custom control to the fieldGroup:


        fieldGroup.addCustomField("groups", (datasource, propertyId) -> {
            TokenList tokenField = componentsFactory.createComponent(TokenList.class);
            tokenField.setDatasource(sourceGroupsDs);
            tokenField.setOptionsDatasource(allGroupsDs);
            tokenField.setClearEnabled(false);
            return tokenField;
        });