Reusing component definitions

When you need to add (sets of) components to your form multiple times with small differences, it is better to use a (configurable) definition that can be reused, instead of copy-pasting the definitions and applying the differences in the copies.

Note that reuse of components can be within an app, but also between (similar) apps. When reusing functionality between apps it should be clearly documented what the reused functionality does, how it should be used and if the app using it must provide information to the reused code.

A couple of important things to note on components:

  • Component keys are easiest to use when they are unique. When you try to get the value of a component with a non-unique key, you may get the value of another component with the same key. A nested form or parent key can be used with the key to get the value of a specific component, when this creates a unique combination
  • All components must have a parent component and their ultimate ancestor must be the form object of the application. Components without a parent or without a connection to the form object will not be shown in the web app.

Using a function

Components can be added to the form or other components in other functions. These functions can be used multiple times in one app definition to add the same components (with some differences) to the app multiple times.

Use the intended parent component as input in the function, to immediately add the new component(s) to the form:

component.Number(key="number", parent=parent_component)

To ensure the values of the components can be found in the callback functions, you can do the following:

  • Give the function a key prefix or suffix input to give the components a unique key.

    component.Number(key=prefix + "_number_" + suffix, parent=parent_component)

  • Use the key of the parent as prefix or suffix

    component.Number(key=parent_component.key + "_number", parent=parent_component)

  • Use a container as parent to give the components a unique key - parent-key combination.

You can add additional input arguments to the function to allow for creating the components with different settings.

An example of adding sets of components (with component key prefix) via a function is the _fill_column function in the Form definition example.

def add_number(parent_component) -> None:
    # Add a Number component with unique key to the given parent component.
    new_nr = component.Number(parent_component.key + "_number", parent_component)
    new_nr.label = "Input value"

Using a JSON form definition

Components can be added to a form or parent component from a JSON form definition definition. These JSON form definitions can be created with the Simian Builder.

def gui_init(meta_data: dict) -> dict:
    form = Form()
    root_container = component.Container("parent_component_key", form)
    root_container.addFromJson("path/to/components_definition.json")

Note that component keys are hardcoded in the JSON form definition and may thus be used multiple times. When you use a Container as parent component, you can use the key of the container to find the value of the component you need.

Reusing initialization functions

When your own web app itself is defined in a JSON form definition, you can use (configurable) Component initialization functions to ensure the components are added to one of the components in the JSON form definition.

In the following example the web app is created from a JSON form definition, and the components that are created in the initialize_action_with_config initialization function are added to the containers identified with the parent_component_key, and other_parent_key key.

def gui_init(meta_data: dict) -> dict:
    Form.componentInitializer(
        parent_component_key=initialize_function,
        other_parent_key=initialize_function,
    )

    # Fill the form with components, including a Container to add other components to.
    form = Form(from_file="path/to/components_definition.json")

    return {"form": form}


def gui_event(meta_data: dict, payload: dict) -> dict:
    # Get the value of the number component that is in the Container.
    value, _ = utils.getSubmissionData(payload, "number", parent="parent_component_key")
    value2, _ = utils.getSubmissionData(payload, "number", parent="other_parent_key")
    return payload


# Potentially in other module / package:
def initialize_function(container_component: Component)
    # Initialization function to execute on the container_component.
    some_number = component.Number("number", parent=container_component)
function payload = guiInit(metaData)
    Form.componentInitializer( ...
        parent_component_key=@initializeFunction, ...
        other_parent_key=@initializeFunction,
    )

    % Fill the form with components, including a Container to add other components to.
    payload.form = Form(FromFile="path/to/components_definition.json")
end

function payload = guiEvent(metaData, payload)
    % Get the value of the number component that is in the Container.
    value = utils.getSubmissionData(payload, "number", parent="parent_component_key")
    value2 = utils.getSubmissionData(payload, "number", parent="other_parent_key")
end

% Potentially in other package:
function payload = initializeFunction(containerComponent)
    % Initialization function to execute on the containerComponent.
    someNumber = component.Number("number", containerComponent)
end

Reusing components in the Builder

The methods described in the previous sections describe methods that reuse definitions by calling the reusable functionality in the Python or MATLAB code.

However, when building an app in the Simian Form Builder it is not possible to add the reusable code to the form, since the builder is unaware of any code that has been written.

For this purpose the component.Composed component has been introduced, which allows adding a reusable component to the form by means of drag-and-drop.