Composed components

With the term composed component we mean a component that is composed of multiple other components. The main purpose of composed components is to provide a standardized method for reusing code and integrating it with the Simian Form Builder.

Currently, Simian includes one predefined composed component: StatusIndicator

Generic component

The generic class component.Composed can be used to add a reusable group of components as if they are one component. In its className property, the fully qualified name of a class can be provided.

If the class exists, its contructor will be called with a single input argument: the instance of component.Composed.

Example

shared/components.py:

class Name:
    def __init__(self, parent: component.Composed):
        # Add components to parent.
        first = component.TextField("first", parent)
        first.label = "First name"

        last = component.TextField("last", parent)
        last.label = "Last name"

app.py:

# In gui_init:
# - Initialize the container with key "name" and add it to the form.
my_name = component.Composed("name", form)

# - Set the className property to call the constructor of shared.components.Name
#   with the object as input argument.
my_name.className = "shared.components.Name"

# In gui_event:
# - Get the value of the composed component.
name, _ = utils.getSubmissionData(payload, "name")
# {'first': 'John', 'last': 'Smith'}

+shared/+components/Name.m:

classdef Name < handle
    methods
        function obj = Name(parent)
            import simian.gui_v3_0_1.*;

            % Add components to parent.
            first       = component.TextField("first", parent);
            first.label = "First name";

            last        = component.TextField("last", parent);
            last.label  = "Last name";
        end
    end
end

+app/guiInit.m:

% Initialize the container with key "name" and add it to the form.
myName = component.Composed("name", form);

# Set the className property to call the constructor of shared.components.Name
# with the object as input argument.
myName.className = "shared.components.Name";

+app/guiEvent.m:

% Get the value of the composed component.
name = utils.getSubmissionData(payload, "name");
% struct("first", "John", "last", "Smith")

Reusable component

When the specified className cannot be resolved, or when calling the constructor results in an error, a placeholder is shown instead.

Placeholder

Parametrization

A reusable component that can be used as-is, is nice to have, but when a component can be parametrized it may be used in different contexts. Instead of hardcoding the labels of the text fields in the contructor of the reusable component, a component initializer can be used to make the labels configurable.

shared/components.py

class Name:
    def __init__(self, parent: component.Composed):
        # Add components to parent.
        first = component.TextField("first", parent)
        first.label = "First name"

        last = component.TextField("last", parent)
        last.label = "Last name"

    @staticmethod
    def create(key, parent, first_label=None, last_label=None):
        # Create the composed component.
        composed = component.Composed(key, parent)
        composed.className = "shared.components.Name"

        Name._initialize_component(composed, first_label, last_label)

        return composed

    @staticmethod
    def get_initializer(first_label=None, last_label=None):
        # Return the initializer with the enclosed parameters.
        return lambda comp: Name._initialize_component(comp, first_label, last_label)

    @staticmethod
    def _initialize_component(comp: component.Composed, first_label, last_label):
        # Set the labels on the text fields.
        [first, last] = comp.components

        if first_label:
            first.label = first_label

        if last_label:
            last.label = last_label

app.py:

# In gui_init:
# - Create two Name components. One with default labels and one customized.
Name.create("name1", form)
Name.create("name2", form, first_label="Forename", last_label="Surname")

+shared/+components/Name.m:

classdef Name < handle
    methods
        function obj = Name(parent)
            import simian.gui_v3_0_1.*;

            % Add components to parent.
            first = component.TextField("first", parent);
            first.label = "First name";

            last = component.TextField("last", parent);
            last.label = "Last name";
        end
    end

    methods (Static)
        function composed = create(key, parent, options)
            arguments
                key
                parent
                options.FirstLabel  (1, 1) string = missing
                options.LastLabel   (1, 1) string = missing
            end

            import simian.gui_v3_0_1.*;
            import shared.components.*;

            % Create the composed component.
            composed            = component.Composed(key, parent);
            composed.className  = "shared.components.Name";

            Name.initializeComponent(composed, options.FirstLabel, options.LastLabel);
        end

        function initializer = getInitializer(options)
            arguments
                options.FirstLabel  (1, 1) string = missing
                options.LastLabel   (1, 1) string = missing
            end

            import shared.components.*;

            % Return the initializer with the enclosed parameters.
            initializer = @(comp) Name.initializeComponent(comp, options.FirstLabel, options.LastLabel);
        end
    end

    methods (Static, Hidden)
        function initializeComponent(comp, firstLabel, lastLabel)
            % Set the labels on the text fields.
            [first, last] = comp.components{:};

            if ~ismissing(firstLabel)
                first.label = firstLabel;
            end

            if ~ismissing(lastLabel)
                last.label  = lastLabel;
            end
        end
    end
end

+app/guiInit.m:

% Create two Name components. One with default labels and one customized.
Name.create("name1", form);
Name.create("name2", form, FirstLabel="Forename", LastLabel="Surname");

Parametrized component

Builder

Composed components can also be added in the Simian Builder. The component type can be found in the miscellaneous category.

Composed component in Simian Builder

Just like any other component it can be dragged into the form. The settings allow to specify the Class of the composed component and the Display height for the placeholder. The preview will always show the placeholder, since the Builder is unaware of the component's implementation.

Composed component settings

Composed component preview

To set parameters, Form.componentInitializer can used to setup the initialization before creating the form.

Form.componentInitializer(
    myComposedComponent=Name.get_initializer("Forename", "Surname")
)
Form.componentInitializer(...
    myComposedComponent=Name.getInitializer("Forename", "Surname"));