Skip to content

Multi action: context dependent actions

01es edited this page Jun 7, 2023 · 9 revisions

There are situations where different actions need to be invoked in the same context depending on some conditions. Support for multi-action was developed specifically for this purpose (issues #1607 and #1964). This wiki describes how to use the multi-action support.

  1. Multi-Action Overview
  2. Action Selector
  3. Multi-Action Configuration

Multi-Action Overview

Multi-action is a mechanism to represent several actions, designed as primary / secondary / property action for Entity Centres (or property action for Entity Masters), as a single action. Each action would have its own configuration as per usual, including its action-entity type, its description, icon, etc.

A key concept for multi-actions is the action selector. Action selectors are responsible for selecting contextually-appropriate action, based on a custom logic that decides what action should be invoked for a corresponding entity. In an Entity Centre with a primary, secondary or property multi-action, the contextually-appropriate actions can be different for different rows depending on the instances represented by those rows and the logic of a corresponding action selector. Having different icons for different action configurations (applicable to primary and secondary actions) would provide the necessary visual cues to differentiate between contextually-appropriate actions "selected" for different rows. For property multi-actions, where no icon is displayed, the difference can be observed in a tooltip.

It is very important to understand, that an action selector performs selection based on the state of the data at the time of an Entity Centre's run or refresh (i.e., the latest known state). This means that the current action, selected for some entity instance (i.e., the one represented in a row) may not be the one that should really be invoked, if the underlying data had already changed. In other words, if the Entity Centre were refreshed, the action selector would present a different action. Realising this, it is important to ensure that each action used in a multi-action, works correctly even if the current entity passed to it is not suitable for that action.

The same applies to Entity Master property multi-action: a contextually-appropriate action could be different for different entities for the same Entity Master and icon / tooltip can provide visual cues. It is important to note, that Entity Master entity can even have unsaved changes and they may influence the multi-action selector logic. It is the developer's responsibility to implement selectors correctly. The entity can be re-fetched in the selector and only persisted data used for selecting the contextually-appropriate action.

As a working example, let's consider returns of expendable inventory parts issued to a WorkActivity. WorkActivityExpendable is an aggregating entity that summarises issues and returns of a certain expendable InventoryPart against a WorkActivity.

In a general case, where a user needs to return a certain InventoryPart, a complete list of issues and returns of that specific part against that specific Work Order would be displayed, allowing the user to choose a specific issue that should be returned. Action ReturnWorkActivityExpendableAction is responsible for performing returns.

However, in a simpler case where only one InventoryIssue was performed, the usability can be improved and the process can be streamlined by processing that single issue directly without presenting users with a dialog to choose 1 issue out of 1 available. Action ReturnInventoryIssueAction could be responsible for that.

So, there are 2 separate and completely independent actions – one is appropriate for returns in case of multiple issues, and another in case on a single issue. For the purpose of this wiki, let's assume that individual actions ReturnInventoryIssueAction and ReturnWorkActivityExpendableAction have already been implemented, and only the instructions on how to create a single multi-action that would encompass these 2 actions, are provided.

This is only an illustrative example, and generally speaking, multi-actions can contain more than 2 actions.

Action Selector

The first step in the implementation of a multi-action is the creation of an action selector. This should be a class that implements contract IEntityMultiActionSelector, which has only one method:

int getActionFor(final AbstractEntity<?> entity);

It should return the index of the action from a list of available multiple actions, starting with 0.

In the example being illustrated, action selector looks like this:

public class ReturnWorkActivityExpendableActionSelector implements IEntityMultiActionSelector {
    private final WorkActivityExpendableCo coWaExp;
    private final InventoryIssueCo coInventoryIssue;

    @Inject
    public ReturnWorkActivityExpendableActionSelector(final ICompanionObjectFinder coFinder) {
        this.coWaExp = coFinder.findAsReader(WorkActivityExpendable.class, true);
        this.coInventoryIssue = coFinder.findAsReader(InventoryIssue.class, true);
    }

    @Override
    public int getActionFor(final AbstractEntity<?> entity) {
        return coInventoryIssue.returnActionTypeFor(entity.getId(), coWaExp) == ReturnWorkActivityExpendableAction.class
            ? 0 /* ReturnWorkActivityExpendableAction */
            : 1 /* ReturnInventoryIssueAction */;
    }
}

Multi-Action Configuration

The next step is to create a multi-action configuration, using the selector created above in some *WebUiConfig class (use static factory method EntityMultiActionConfigBuilder.multiAction):

    public static EntityMultiActionConfig makeReturnWorkActivityExpendableActionConfig() {
        return multiAction(ReturnWorkActivityExpendableActionSelector.class)
            .addAction(
                action(ReturnWorkActivityExpendableAction.class)
                .withContext(context().withCurrentEntity().build())
                .icon("icons:undo")
                .withStyle(SECONDARY_ACTION_COLOUR)
                .shortDesc("Return Work Activity Expendable")
                .longDesc("Return current Work Activity Expendable.")
                .prefDimForView(mkDim(80, 80, Unit.PRC))
                .build()
            )
            .addAction(
                action(ReturnInventoryIssueAction.class)
                .withContext(context().withCurrentEntity().build())
                .icon("icons:undo")
                .withStyle(SECONDARY_ACTION_COLOUR)
                .shortDesc("Return Work Activity Expendable")
                .longDesc("Return current Work Activity Expendable.")
                .build()
            )
            .build();
    }

Naturally, the order of actions in the multi-action configuration must correspond to the order of indexes returned by the selector.

Please note that in this example the same icon icons:undo is used to represent both actions, because from the user's perspective both actions represent a "return action". In other cases, it could be beneficial to set different icons for different actions in a multi-action configuration in order to provide users with a visual cue.

Multi-actions can be added as Primary / Secondary / Property Actions on Entity Centre. The following example illustrates the use of makeReturnWorkActivityExpendableActionConfig, created above, as well as in-place multi-action configurations that start with calls to multiAction.

    private EntityCentre<WorkActivityExpendable> createCentre(final Injector injector, final IWebUiBuilder builder) {
       ...
        final EntityMultiActionConfig returnAction = makeReturnWorkActivityExpendableActionConfig();

        final EntityCentreConfig<WorkActivityExpendable> ecc = EntityCentreBuilder.centreFor(WorkActivityExpendable.class)
       ...
                .addProp("inventoryPart.unitOfMeasure").width(96).withMultiAction(multiAction(PropertyActionSelector.class)
                    .addAction(EDIT_ACTION.mkAction(UnitOfMeasure.class))
                    .addAction(action(ChangeUnitOfMeasure.class).longDesc("Change unit of measure").withNoParentCentreRefresh().build())
                    .build()
                ).also()
       ...
                .addPrimaryAction(multiAction(PrimaryActionSelector.class)
                    .addAction(EDIT_ACTION.mkActionWithIcon(WorkActivityExpendable.class, "editor:mode-edit", of("color:green")))
                    .addAction(EDIT_ACTION.mkActionWithIcon(WorkActivityExpendable.class, "editor:mode-edit", of("color:orange")))
                    .build()
                ).also()
                .addSecondaryAction(returnAction).also()
                .addSecondaryAction(multiAction(SecondSecondaryActionSelector.class)
                    .addAction(EDIT_ACTION.mkActionWithIcon(WorkActivityExpendable.class, "editor:mode-edit", of("color:green")))
                    .addAction(EDIT_ACTION.mkActionWithIcon(WorkActivityExpendable.class, "editor:mode-edit", of("color:orange")))
                    .build()
                ).
                .build();

        return new EntityCentre<>(MiWorkActivityExpendable.class, ecc, injector);
    }

Multi-actions are also supported as property actions in Entity Masters:

        ...
        final IMaster<Vehicle> masterConfig = new SimpleMasterBuilder<Vehicle>().forEntity(Vehicle.class)
            .addProp(Vehicle_.number()).asSinglelineText().withMultiAction(multiAction(InstallationChecklistActionSelector.class)
                .addAction(installationChecklistCheckAction)
                .addAction(installationChecklistAddAction)
                .build()
            ).also()
            .addProp(Vehicle_.regNo()).asAutocompleter().also()
            .addProp(Vehicle_.increaseToFleet()).asAutocompleter().also()
        ...
Clone this wiki locally