Skip to end of metadata
Go to start of metadata

What are event actions

Event-responsive actions (a.k.a. event actions) are a type of integration components that are triggered in response to the occurrence of any event on a task form.

Event

There are two sources of events:

  1. task form - so called system events (e.g. changing the value of a variable, clicking on a button)
  2. event actions

Each event has a name and any number of properties of a defined type. The creator of an action can call any event from its action, on which any other action can be triggered. This solution gives more flexibility when creating processes compared to traditional form actions. The action creator has complete control over the timing and order of action execution.

The properties of an event can be mapped to action parameters in a manner analogous to the "context variables" of classic form actions.

Event actions vs. form actions

Form actions themselves define events in response to which they will be triggered. This way of triggering actions makes them less flexible (e.g. executing an action on another event requires a change in the implementation). In the case of event actions, it is the process creator who decides when the action should be triggered and with what parameters.

Order of execution of event actions

The diagram shows the order in which actions are executed in response to a change event. All 3 actions (Action1, Action2, Action3) are defined as follows (in the order shown):

EventAction
change
  1. Action1
  2. Action3
success (Action1)
  1. Action2

Actions are called in the order defined in the process map in depth, i.e. actions dependent on events of already called actions will be called first. Once this hierarchy is completed, the next action defined for the source event is called:

%%{init: {'theme':'default'}}%% sequenceDiagram autonumber participant Form participant EventsController participant Action1 participant Action2 participant Action3 Form->>EventsController: event:change activate EventsController EventsController->>Form: restore() deactivate EventsController activate Form Form->>EventsController: return context:1 deactivate Form   activate EventsController EventsController->>Action1: on(change): action1(context:1) deactivate EventsController activate Action1 Action1-->>EventsController: fireEvent("success") Action1->>EventsController: return context:2 deactivate Action1 activate EventsController EventsController->>Action3: on(action1_success): action3(context:2) deactivate EventsController activate Action3 Action3->>EventsController: return context:3 deactivate Action3 activate EventsController EventsController->>Action2: on(change): action2(context:3) deactivate EventsController activate Action2 Action2->>EventsController: return context:4 deactivate Action2 activate EventsController EventsController->>Form: apply(context:4) deactivate EventsController

Asynchronous actions

Asynchronous actions are a special case, i.e. actions that perform any asynchronous action (e.g. http request) before the call is completed. In such a case, all defined actions are called even if some of them went into asynchronous mode. Only after the asynchronous call ends, the action chain is resumed.

%%{init: {'theme':'default'}}%% sequenceDiagram autonumber participant Form participant EventsController participant Async1 participant Sync1 Form->>EventsController: event:change activate EventsController EventsController->>Form: restore() deactivate EventsController activate Form Form->>EventsController: return context:1 deactivate Form activate EventsController EventsController->>Async1: on(change): async1(context:1) deactivate EventsController activate Async1 Async1-->>EventsController: fireEvent("before_async") Async1-->>EventsController: start async(context:2) deactivate Async1 activate EventsController EventsController->>Sync1: on(async1_before_async): sync1(context:2) deactivate EventsController activate Sync1 Sync1->>EventsController: return context:3 deactivate Sync1 activate EventsController EventsController->>Form: apply(context:3) deactivate EventsController Async1 --> Async1: async done Async1 ->> EventsController: notify() activate EventsController EventsController->>Form: restore() deactivate EventsController activate Form Form->>EventsController: return context:4 deactivate Form activate EventsController EventsController->>Async1: resume(context:4) deactivate EventsController activate Async1 Async1-->>EventsController: fireEvent("after_async") Async1->>EventsController: return context:5 deactivate Async1 activate EventsController EventsController->>Sync1: on(async1_after_async): sync1(context:5) deactivate EventsController activate Sync1 Sync1->>EventsController: return context:6 deactivate Sync1 activate EventsController EventsController->>Form: apply(context:6) deactivate EventsController

 

System events

An overview of task form events for which event actions can be attached.

Global

EventDescriptionParameters
afterLoadFormEvent triggered when all form components are loaded
  • previousUrl: String
    The URL of the page from which the task form was navigated
afterAddDocument

Event raised after document has been attached to the process

  • documentId: String
    Document identifier
  • fileId: Integer
    File identifier
  • versionNo: Integer
    Version number of the attached file
  • docClassId: Integer
    Destination document class identifier
  • docClassName: String
    Destination document class name

Process variables

A form variable that is not part of a dynamic table

EventDescriptionParameters
changeEvent triggered when the value of a variable changes
  • oldValue: String
    The value of the field before the change

    Text value regardless of the type of the variable - this is due to the limitations associated with the type system and the lack of generic types

focusEvent triggered when a field receives focus 
blurEvent triggered when field loses focus 
showEvent triggered when a field is showed 
hideEvent triggered when a field is hidden 

Tabels

EventDescriptionParameters
selectEvent triggered when a row is selected in a table
  • tableId: String
    Dynamic table identifier

  • rowNo: Integer
    Number of the selected table row
cellClickEvent triggered when clicking on a table cell
  • tableId: String
    Dynamic table identifier

  • rowNo: Integer
    Number of the selected table row
  • fieldId: String
    Variable identifier of the clicked cell
afterAddRecordsEvent triggered when records are added to a table
  • tableId: String
    Dynamic table identifier

  • newRecords: TableStore
    Added records in TableStore format
afterRemoveRecordsEvent triggered when records are removed from table
  • tableId: String
    Dynamic table identifier

  • deletedRecords: TableStore
    Removed records in TableStore format
afterAddRecordsEvent triggered when records in table are changed
  • tableId: String
    Dynamic table identifier

  • changedRecords: TableStore
    Changed records in TableStore format
afterRenderEvent triggered after table is rendered 
showEvent triggered after table discovery 
hideEvent triggered when table is hidden 

Buttons

Buttons available on the form

EventDescriptionParameters
clickEvent triggered when the button is clicked

 

 

 

disableEvent triggered when button is locked 
enableEvent triggered when button is unlocked 
showEvent triggered when button is showed 
hideEvent triggered when button is hidden 

Implementation

In a similar way to classic form actions, event actions are provided by plugins.

suncode-plugin.xml must contain workflow-components module declarations

 

<workflow-components key="components" />

Action definition

Definition

@EventActions // indicate the component containing the event actions
@ActionsScript( "scripts/eventactions.js" ) // indicate the script with the action implementation
public class SetVariables
{
    @Define
    public void setVariables(EventActionDefinitionBuilder eventAction )
    {
        //@formatter:off
        eventAction
            .id( "test-action" )
            .name( "Test action" )
            .description( "eventaction.test-action.desc" )
            .icon( SilkIconPack.APPLICATION_FORM_EDIT )
            .category( Categories.GENERAL )
            .documentationLink( "link" )
            .parameter() // declaration of paremeters that the action takes
                .id( "param1" )
                .name( "param1" )
                .type( Types.STRING )
                .create()
            .event("success") // declaration of events that can be triggered by actions
                .description("")
                .property("foo") // properties of events
                    .name("Foo")
                    .description("Bar")
                    .type(Types.BOOLEAN)
        ;
        //@formatter:on
    }
}

Registration of action implementation on the form

Registration of the action is done by calling

 

    PW.EventActions.register(name, function)

where:

name: name of the registered action - it must match the name of the action from the component definition

function(context, ...params): function which will be called when an event occurs, to which the action is attached, it takes the following parameters:

context: the first parameter is the context of the form, used to effectively set the values of all form variables

<params>: subsequent parameters are the parameters for calling the action as declared, the order of the parameters follows the order in the component declaration

PW.EventActions.register("test-action", function(context, param1) {
    if(param1 == 'foo') {
        this.fireEvent("success", {"foo": true});
    }
});

Form context

The first parameter of each action is the context - the context of the form. It represents the current state of the form (values of all variables). This state is transferred to subsequent actions where it can be freely modified by them. When all actions are handled (within one form event), the final state is compared with the initial state of the form and all changes are applied to it.

Such a solution makes it very fast to change the value of form variables when the action is processed, because these changes are not propagated to the form at the moment of execution, but only when all actions from the so-called action chain are handled.

Changing values of variables

API reference: ContextAPI

Changing header variables:

PW.EventActions.register("test-action", function(context) {
    const value = context.variable("zmienna_1").get();
    context.variable("zmienna_2").set("zmienna_1:" + value);
});

Changing records in a table:

PW.EventActions.register("test-action", function(context) {
    const table = context.variableSet("table_1");
    table.variable("zmienna_1").add("new_value");
});

Asynchronous actions

Asynchronous actions are actions in which there is a need to call asynchronous code (e.g. http requests or displaying a modal with a callback). In order for such an action to be able to change the state of the form and trigger further actions in response to events emitted through this.fireEvent() it is necessary to return control to the action framework.

This is done by the function this.async(function), which takes the function of the callback that will be called after returning from the asynchronous callback and returns the same function enriched with the logic for resuming the execution of the action.

Before calling the function transferred by the developer to the async() function, the system will load the current context of the form and resume execution of the action string, and when it is finished, it will apply the changes to the task form.

An example of correct and incorrect implementation of an asynchronous action:

PW.EventActions.register("async-action", function(context) {
    context.set("var1", "before"); // change of context is allowed before going into asynchronous mode
	this.fireEvent("before_async"); // same as event triggering
	
    // Asynchronous callback (e.g. server response, modal)
    // the function transferred as callback should be prepared in advance using this.async() function
    setTimeout(this.async(() => {
            context.set("var1", "after");
            this.fireEvent("after_async");
    }), 1000);
 
    // w przypadku gdy nie użyjemy funkcji this.async() - Accessing the context or and triggering events is not possible and will end in an error
    setTimeout(() => {
            this.fireEvent("will throw");
    }, 1000);
});

Context API

 

Context
FunctionDescription

variable(id) : VariableContext

Returns the VariableContext of the variable with the given identifier
variableSet(tableId) : VariableSetContext

Returns the VariableSetContext of the table with the specified identifier

VariableContext
FunctionDescription

set(newValue): this

Changing the value of a variable
get(): ObjectReturns the current value of the variable
VariableSetContext
FunctionDescription

variable(id).add(newValue): this

 

Adding a new value (row)
variable(id).set(newValues): thisChanging the value of a variable in a table
variable(id).get(): Object[]Returns the current value of a variable (array)
addRecords(tableStore): thisAdds records described by the specified TableStore

Troubleshooting

Action call logging

If we need more information about the actions performed, their parameters or event properties, we can enable accurate logging at the Trace level. To do this, we need to:

  1. run the form in devMode (by adding &devMode=true to the end of the URL)
  2. make sure that the console displays messages at the Trace/Verbose  (in Google Chrome the Verbose level is disabled by default
Example of Trace logs
EventActions chain started: {name: 'afterLoadForm', props: constructor} Context {state: {…}}
EventActions: invoking action: tr {action: {…}, on: {…}} on event: {name: 'afterLoadForm', props: constructor} Context {state: {…}}
EventActions chain finished: {name: 'afterLoadForm', props: constructor} Context {state: {…}}

Known issues

EventAction: No active context found: most likely this code was invoked asynchronously, did you forgot 'this.async()'?

An error indicates an attempt to interact with an action, its context or parameters after the action has been executed or, in the case of asynchronous actions, without first using the this async() function. See Asynchronous actions

 

 

 

 context.variableSet("t").updateRecordAt(1, {"var1": "value1", "var2": 2});
  • No labels