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")
When the specified className cannot be resolved, or when calling the constructor results in an error, a placeholder is shown instead.
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");
Builder
Composed components can also be added in the Simian Builder. The component type can be found in the miscellaneous category.
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.
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"));