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 struct/dict with a form field that contains the form.
    • Optionally specify a navbar field to set the logo and title of the application.

Components are created by using:

comp = component.<name>(<key>, <parent>)

In this call:

  • name is the name of any class in component that is not the Component class. The Component is an abstract superclass of all implemented components, so it cannot be used directly. For a full list of available components, see Components and subsections.
  • key is a unique string by which the component can be recognized. In MATLAB it must be a valid structure field name, which can be checked using isvarname(<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.
  • parent is 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:

import simian.gui_v2_0_0.component.*
comp = <name>(<key>, <parent>);
from simian.gui.component import *
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 MATLAB table or Pandas DataFrame (in Python) by using the utils.addComponentsFromTable function. It takes two input arguments:

  • parent: a Form or Component that can have subcomponents (i.e. it has a components property),
  • table: a MATLAB table or Pandas DataFrame.

It returns the created components in a struct/dict.

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: Struct/dictionary with options, may contain any property that can be set to the component. Use missing/None to leave unspecified. If the column is not present, all options will be set to missing/None. 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 missing/None to leave unspecified. If the column is not present, all default values will be set to missing/None.
  • label: Label for the component. Use missing/None to leave unspecified. If the column is not present, all labels will be set to missing/None.
  • tooltip: Tooltip for the component. Use missing/None to leave unspecified. If the column is not present, all tooltips will be set to missing/None.

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 Columns component. In the options, a width field 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 Tabs component. The label will be shown on the tab.
  • tablerow for adding rows to a Table component. Can only be added to a Table.
  • tablecell for adding a TableCell to a tablerow. The TableCell can 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 struct/dict that is put in the table.

The struct/dict that is returned uses the keys specified in the table definition as fields/keys. 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 table/DataFrame. 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 struct/dict is used for multiple components, further reducing the amount of code required for building the form.

Form

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
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  ],
    ]

    component_table = DataFrame(
        data=component_specs,
        columns=["key", "class", "level", "options"],
        dtype=object,
    )

    return utils.addComponentsFromTable(form, component_table)


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" ],
    ]

    component_table = DataFrame(
        data=component_specs,
        columns=["key", "class", "level", "options", "defaultValue", "label"],
        dtype=object,
    )

    utils.addComponentsFromTable(column, component_table)

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(FromFile="/path/to/my/form.json");
form = Form(FromFile=mfilename("fullpath")); % The ".json" extension is added for convenience.
form = Form(from_file="/path/to/my/form.json")
form = Form(from_file=__file__)  # The extension is replaced with ".json" 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.

In MATLAB and Python this can be done by calling the static method Form.componentInitializer before building the form. Note that the input arguments can be repeated to register multiple initializers.

function payload = guiInit(metaData)
    Form.componentInitializer('name', @initializeName);
    form = Form(FromFile='form.json');
    payload = struct("form", form);
end

function initializeName(comp)
    arguments
        comp component.TextField
    end

    comp.defaultValue = getenv('USERNAME');
end
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()