Form structure
The Hello world! example of the previous chapter follows the general structure of the form initialization code:
- Create an empty form:
form = simian.gui.Form() - Add components to it from top to bottom:
- Create the component with default property values.
- Change property values of the component where necessary.
- Return a
dict/structwith aformfield that contains the form.- Optionally specify a
navbarfield to set thelogoandtitleof the application.
- Optionally specify a
There are three ways of adding components to the form:
Adding components from constructor
Components are created by calling the Component constructor:
comp = component.<name>(<key>, <parent>)
In this call:
nameis the name of any class incomponentthat is not theComponentclass. TheComponentis an abstract superclass of all implemented components, so it cannot be used directly. For a full list of available components, see Components and subsections.keyis a unique string by which the component can be recognized. In MATLAB it must be a valid structure field name, which can be checked usingisvarname(<key>)(Python does not have this restriction). In order to prevent unexpected behavior, it is advised to use globally unique component keys within the form.parentis an optional parent to which the new component must be added. You can add the new component directly to the form just like in the Hello world! example. Alternatively, you could add the component to a parent component such as a panel or table. More information on nesting of components can be found in Component nesting.
You can choose to import the component package to prevent having to use it for the creation of every single component. The syntax then becomes:
from simian.gui.component import *
comp = <name>(<key>, <parent>)
import simian.gui_v3_2_0.component.*
comp = <name>(<key>, <parent>);
There are over 35 different components that follow the same pattern. Each of these is described in the Components section and subsections.
Adding components from table
When each component is constructed individually, the code can quickly become very lengthy. In order to keep the overview, components can be specified in a Pandas DataFrame or list of lists (in Python) or MATLAB table by using the utils.addComponentsFromTable function. It takes two input arguments:
parent: aFormorComponentthat can have subcomponents (i.e. it has acomponentsproperty),table: a Pandas DataFrame, list of lists, or MATLAB table.
It returns the created components in a dict/struct.
The table or DataFrame may have the following columns :
- key: Component key (mandatory). Must be a valid variable name and unique per level.
- type/class: Component type or class for creating the component (mandatory).
- level: Nesting level. Top level components (relative to parent) have level 1. Nested components, e.g. in a Panel, increment with 1. If the column is not present, all levels will be set to 1.
- options: Dictionary/struct with options, may contain any property that can be set to the component. Use
None/missingto leave unspecified. If the column is not present, all options will be set toNone/missing. Keys/fields 'defaultValue', 'label', 'tooltip', 'type', and 'key' are ignored, as these are columns of the table. - defaultValue: The default value for the component, must be of a valid data type for the component type. Use
None/missingto leave unspecified. If the column is not present, all default values will be set toNone/missing. - label: Label for the component. Use
None/missingto leave unspecified. If the column is not present, all labels will be set toNone/missing. - tooltip: Tooltip for the component. Use
None/missingto leave unspecified. If the column is not present, all tooltips will be set toNone/missing.
In case of a Python list of lists input these columns are assumed to be the fields of the inner lists, unless specified differently in the column_names input.
Although in general only components can be added to the table, there are some other type/class values that can be added:
- column for adding the actual columns to a
Columnscomponent. In the options, awidthfield with an integer value must be specified. The total of the column widths must add up to 12. - tab for adding the actual tabs to a
Tabscomponent. The label will be shown on the tab. - tablerow for adding rows to a
Tablecomponent. Can only be added to a Table. - tablecell for adding a
TableCellto atablerow. TheTableCellcan contain other components.
Validation, conditionals and logic can be added to the returned components as usual (see here), or they can be added via the options dict/struct that is put in the table.
The dict/struct that is returned uses the keys specified in the table definition as keys/fields. If keys in the table are not unique, then only the last component with the non-unique key will be in the output.
Example
This example shows a simple form built using a DataFrame/table. It consists of a panel that is added to the form and four components that are added to the panel, using the level column. The options dict/struct is used for multiple components, further reducing the amount of code required for building the form.

def gui_init(_meta_data: dict) -> dict:
form = Form()
component_dict = _create_layout(form)
_fill_column(component_dict["left"], "From")
_fill_column(component_dict["right"], "To")
return {"form": form}
def _create_layout(form: Form) -> dict:
column_options = {"width": 6}
component_specs = [
# key class level options
[ "locations", "Columns", 1, None ],
[ "left", "Column", 2, column_options ],
[ "right", "Column", 2, column_options ],
]
return utils.addComponentsFromTable(form, component_specs, ["key", "class", "level", "options"])
def _fill_column(column: component.Columns, label: str) -> None:
key = label.lower()
shared_options = {"labelPosition": "left-left"}
component_specs = [
# key class level options defaultValue label
[ key + "Panel", "Panel", 1, None, None, label ],
[ key, "Container", 2, None, None, None ],
[ "name", "TextField", 3, shared_options, "Breda", "Name" ],
[ "latitude", "Number", 3, shared_options, 51.5883621, "Latitude" ],
[ "longitude", "Number", 3, shared_options, 4.7760251, "Longitude" ],
]
column_names = ["key", "class", "level", "options", "defaultValue", "label"],
utils.addComponentsFromTable(column, component_specs, column_names)
function payload = guiInit(metaData)
form = Form();
componentStruct = createLayout(form);
fillColumn(componentStruct.left, "From");
fillColumn(componentStruct.right, "To");
payload.form = form;
end
function componentStruct = createLayout(form)
columnOptions.width = 6;
componentSpecs = {
% key class level options
"locations", "Columns", 1, missing
"left", "Column", 2, columnOptions
"right", "Column", 2, columnOptions
};
componentTable = cell2table(componentSpecs, 'VariableNames', ["key", "class", "level", "options"]);
componentStruct = utils.addComponentsFromTable(form, componentTable);
end
function fillColumn(column, label)
key = lower(label);
sharedOptions.labelPosition = "left-left";
componentSpecs = {
% key class level options defaultValue label
key + "Panel", "Panel", 1, missing, missing, label
key, "Container", 2, missing, missing, missing
"name", "TextField", 3, sharedOptions, "Breda", "Name"
"latitude", "Number", 3, sharedOptions, 51.5883621, "Latitude"
"longitude", "Number", 3, sharedOptions, 4.7760251, "Longitude"
};
componentTable = cell2table(componentSpecs, 'VariableNames', ["key", "class", "level", "options", "defaultValue", "label"]);
utils.addComponentsFromTable(column, componentTable);
end
Adding components from JSON
With the introduction of the Simian Form Builder,
it is possible to generate components from a JSON form definition file by providing a named argument to the Form constructor.
form = Form(from_file="/path/to/my/form.json")
form = Form(from_file=__file__) # The extension is replaced with ".json" for convenience.
form = Form(FromFile="/path/to/my/form.json");
form = Form(FromFile=mfilename("fullpath")); % The ".json" extension is added for convenience.
Additionally components that are able to hold other components, can be populated using the addFromJson method.
comp = component.Container("myContainer")
comp.addFromJson("/path/to/my/form.json")
comp = component.Container("myContainer");
comp.addFromJson("/path/to/my/form.json");
Initialization code
When components are generated using a JSON form, the component objects are added to the form tree. To modify a component programmatically without searching for it in the form, register a component initializer function.
This can be done by calling the static method Form.componentInitializer before building the form. The input consists of named arguments, where the keyword/name must match the key of the component and the value is an initialization function handle. The initialization function will be called with one input argument: the component object. Only one function can be registered per component.
Note that the initialization function must be registered before the component is created. Otherwise, the function is not applied to the component. As a result, you cannot register an initialization function from another initialization function.
def gui_init(meta_data: dict) -> dict:
Form.componentInitializer(name=initialize_name)
form = Form(from_file=__file__)
return {"form": form}
def initialize_name(comp: component.TextField):
comp.defaultValue = os.getlogin()
function payload = guiInit(metaData)
Form.componentInitializer('name', @initializeName);
form = Form(FromFile='form.json');
payload = struct("form", form);
end
function initializeName(comp)
comp.defaultValue = getenv('USERNAME');
end
Note that you can configure the behaviour of registered initializer functions by adding inputs to a function that wraps the actual function. In the example below a standard set_default function is used to set the default value of multiple components.
def gui_init(meta_data: dict) -> dict:
Form.componentInitializer(
name=set_default(os.getlogin()),
folder=set_default(os.getcwd()),
)
form = Form(from_file=__file__)
return {"form": form}
def set_default(value) -> Callable:
def inner(comp: component.TextField):
comp.defaultValue = value
return inner
function payload = guiInit(metaData)
Form.componentInitializer( ...
'name', set_default(getenv('USERNAME')), ...
'folder', set_default(cd()) ...
);
form = Form(FromFile='form.json');
payload = struct("form", form);
end
function func = set_default(value)
func = @(comp) inner(comp, value);
end
function payload = inner(comp, value)
comp.defaultValue = value;
end
In a wrapped initialization function it is possible to register other initialization functions. In the example below the initialization function for the folder TextField is added in the function wrapping the name TextField initializer.
A construction like this is useful when your web app is created from a JSON form definition and incorporates another JSON form definition with initializer functions that must be executed.
def gui_init(meta_data: dict) -> dict:
Form.componentInitializer(name=initialize_name()) # Function is executed!
form = Form(from_file=__file__)
return {"form": form}
def initialize_name() -> Callable:
# Add an initialize function for the 'folder' component.
Form.componentInitializer(folder=initialize_folder)
def inner(comp: component.TextField):
comp.defaultValue = os.getlogin()
return inner
def initialize_folder(comp: component.TextField):
comp.defaultValue = os.getcwd()
function payload = guiInit(metaData)
Form.componentInitializer('name', initializeName()); % Function is executed!
form = Form(FromFile='form.json');
payload = struct("form", form);
end
function func = initializeName()
% Add an initialize function for the 'folder' component.
Form.componentInitializer(folder=@initialize_folder)
func = @(comp) inner(comp);
end
function inner(comp)
comp.defaultValue = getenv('USERNAME');
end
function initialize_folder(comp)
comp.defaultValue = cd();
end