Implementing guiEvent

The guiEvent function serves as a gateway for all events. Therefore, a typical implementation will consist of switching logic that dispatches the events to other functions that actually provide functionality to handle the events. In this section the calling syntax, arguments and return values will be discussed, followed by a small example.

Syntax

function payload = guiEvent(metaData, payload)
    ...
end
def gui_event(meta_data: dict, payload: dict) -> dict:
    ...
    return payload

Arguments

metaData:

Meta data describing the client session. It is a struct/dict with fields:

  • session_id: unique ID for the session, can be used to differentiate between multiple instances of the application
  • namespace: package name of the application

payload:

When entering the function, this contains the event data containing all values as provided by the client. It is a struct/dict with fields:

  • action: 'event'
  • download: information regarding the download of a file. Empty when no file is being downloaded.
  • event: name of the event that was triggered.
  • followUp (optional): name of a follow-up event that will be triggered after the current one has completed.
  • formMap: Map representing the form definition.
  • key: identifier of the component that triggered the event
  • submission: struct/dict with fields:
    • data: struct/dict with fields:
      • eventCounter: incrementing counter; do not change.
      • <key>: multiple fields containing the values of the components identified by the keys.
      • <nested form key>: See Nested forms for more information. Struct/dict with fields:
        • data: struct/dict with fields:
          • <key>: multiple fields containing the values of the components identified by the keys.
  • updateForm: when true, the form definition is sent to the front end. The default value is false. Use this only when the form definition changes. Updating the form definition will be slower than only updating the submission data.
  • alerts (optional): array of struct/dict with fields:
    • type: message type, see Alerts
    • message: string

In the guiEvent function, the submission field may be altered before sending payload back to the front-end in order to present results to the user. Additionally, the followUp field can be changed, which is described below.

Key & Event

The key is unique for each component within the context of its parent and/or nested form, but event can be shared between components. This can be useful when multiple components (partially) share functionality. See Example.

Dispatching events

As the number of possible events in your application grows, your guiEvent function may contain an enormous switch/case or if/elif construction. In order to mitigate this, it is possible to automatically call functions based on event names without having to specify every single event. This can be achieved by using the dispatchEvent or getEventFunction utility functions:

payload = utils.dispatchEvent(metaData, payload);
caller = utils.getEventFunction(meta_data, payload)
payload = caller(meta_data, payload)

The dispatchEvent function calls a function equal to the name of the event that was triggered, whereas getEventFunction only returns the function object.

Note: In Python, breakpoints cannot be detected in functions executed via the dispatchEvent function. It is therefore recommended to use getEventFunction instead.

The functionality of the dispatching mechanism is illustrated with the example below. Consider the initialization code of a form with several buttons, each with their own event:

btn1 = component.Button("btn1", parent);
btn1.setEvent("ModelS.getResults");

btn2 = component.Button("btn2", parent);
btn2.setEvent("writeToDatabase");

btn3 = component.Button("btn3", parent);
btn3.setEvent("ModelX.getValue");

The following guiEvent functions handle these events, the first one using conditional logic and the second one using the dispatchEvent or getEventFunction functions:

function payload = guiEvent(metaData, payload)
    switch payload.event
        case "ModelS.getResults"
            payload = ModelS.getResults(metaData, payload);

        case "writeToDatabase"
            payload = writeToDatabase(metaData, payload);

        case "ModelX.getValue"
            payload = ModelX.getValue(metaData, payload);
    end
end

function payload = guiEvent(metaData, payload)
    % Use the automatic dispatch.
    payload = utils.dispatchEvent(metaData, payload);
end
def gui_event(meta_data, payload):
    if payload["event"] == "ModelS.getResults":
        ModelS.getResults(meta_data, payload)
    elif payload["event"] == "writeToDatabase":
        writeToDatabase(meta_data, payload)
    elif payload["event"] == "ModelX.getValue":
        ModelX.getValue(meta_data, payload)

def gui_event(meta_data, payload):
    # Use the automatic dispatch.
    caller = utils.getEventFunction(meta_data, payload)
    payload = caller(meta_data, payload)

Registering events explicitly

Sometimes it is undesirable to name an event after a function or vice versa. For this purpose, event handler functions can be registered explicitly.

In MATLAB and Python this can be achieved by calling the static method Form.eventHandler in the guiEvent function before event dispatching. Note that the eventHandler input arguments can be repeated to register multiple events.

function payload = guiEvent(metaData, payload)
    Form.eventHandler("SayHello", @sayHello);
    payload = utils.dispatchEvent(metaData, payload);
end

function payload = sayHello(metaData, payload)
    payload = utils.setSubmissionData(payload, "display", "Hello, world!");
end
def gui_event(meta_data: dict, payload: dict) -> dict:
    Form.eventHandler(SayHello=say_hello)
    callback = utils.getEventFunction(meta_data, payload)
    return callback(meta_data, payload)

def say_hello(meta_data: dict, payload: dict) -> dict:
    utils.setSubmissionData(payload, "display", "Hello, world!")
    return payload

Follow-up Event

When the followUp field is added to payload, and its value is the name of another event, the follow-up event will be triggered after completion of the current one. The component key will be identical for both events. The most common use case of this feature is to present the user with intermediate results of a calculation that consists of multiple stages. Consider combining this with the StatusIndicator component.

An example of the follow-up event is given below:

function payload = guiInit(metaData)
    form            = Form();
    payload.form    = form;

    btnLoadData         = component.Button("run", form);
    btnLoadData.label   = "Run";
    btnLoadData.setEvent("Run");

    txtId       = component.TextField("id", form);
    txtId.label = "ID";
end

function payload = guiEvent(metaData, payload)
    switch payload.event
        case "Run"
            pause(1);
            payload = utils.setSubmissionData(payload, "id", "Running phase 1");
            payload.followUp = "RunNext";

        case "RunNext"
            pause(1);
            payload = utils.setSubmissionData(payload, "id", "Finalizing...");
            payload.followUp = "RunFinal";

        case "RunFinal"
            pause(2);
            payload = utils.setSubmissionData(payload, "id", "Done!");
    end
end
import time


def gui_init(meta_data: dict) -> dict:
    form = Form()
    payload = {"form": form}

    btnLoadData = component.Button("run", form)
    btnLoadData.label = "Run"
    btnLoadData.setEvent("Run")

    txtId = component.TextField("id", form)
    txtId.label = "ID"

    return payload


def gui_event(meta_data: dict, payload: dict) -> dict:
    if payload["event"] == "Run":
        time.sleep(1)
        utils.setSubmissionData(payload, "id", "Running phase 1")
        payload["followUp"] = "RunNext"
    elif payload["event"] == "RunNext":
        time.sleep(1)
        utils.setSubmissionData(payload, "id", "Finalizing...")
        payload["followUp"] = "RunFinal"
    elif payload["event"] == "RunFinal":
        time.sleep(2)
        utils.setSubmissionData(payload, "id", "Done!")

    return payload

Example

The following example shows an implementation of guiEvent that uses the event names and component keys to dispatch the event to the correct function.

function payload = guiEvent(metaData, payload)
    % Switch on event name.
    switch payload.event
        case "calculate"
            % The calculate event has an application-specific preprocessing step.
            % Preprocess the data entered in the form before performing calculations.
            preprocessed_data = preprocess(payload);
            
            % Switch on the component key.
            % Each component starts a different calculation.
            switch payload.key
                case "stage1StartButton"
                    payload = runStage1(payload, preprocessed_data);
                    
                case "stage2StartButton"
                    payload = runStage2(payload, preprocessed_data);
            end
            
        case "estimate"
            payload = estimate(payload);
    end
end
def gui_event(meta_data, payload):
    # Switch on event name.
    if payload['event'] == 'calculate':
        # The calculate event has an application-specific preprocessing step.
        # Preprocess the data entered in the form before performing calculations.
        preprocessed_data = preprocess(payload)

        # Switch on the component key. Each component starts a different calculation.
        if payload['key'] == 'stage1StartButton':
            runStage1(payload, preprocessed_data)

        elif payload['key'] == 'stage2StartButton':
            runStage2(payload, preprocessed_data)

    elif payload['event'] == 'estimate':
        estimate(payload)
        
    return payload