Extension
The extension component can be used to write your own component using HTML and JavaScript.
Properties
The following properties can be specified in the form definition.
Name | Description | Datatype | Default |
---|---|---|---|
content | HTML content for the extension. | string | "<p>Hello, world!</p>" |
initializeFn | Initialize function, called once when the component is initialized. | string | None /"" |
updateFn | Update function, called when the value of the component is changed from outside. | string | None /"" |
event | Name of an event that can be emitted similar to a Button. | string | None /"" |
Initialize and update functions
The initialize and update functions are called with one input argument, the extension component, that has the following properties:
Name | Description | Datatype | Default |
---|---|---|---|
formioEvent | Event emitter for custom events | EventEmitter | |
initializeFn | The name of the initialize function provided in the form. | string | "" |
internal | Dictionary to store the internal state of the component. | Dictionary | {} |
key | The component key. | string | "<key>" |
updateFn | The name of the update function provided in the form. | string | "" |
value | The submission data of the component. | Dictionary<string, any> | {} 1 |
valueChange | Event emitter to notify other components the value has changed. | EventEmitter<Dictionary |
Can be changed by setting defaultValue
in the form definition.
Value change
Form.io compares values by equality when detecting changes.
When changing an object's fields or an array's elements, the change may not be detected.
For arrays this may be solved by slicing the array array.slice()
, for objects all fields can be copied to a new object { ...object }
.
For complex nested values, a trick might be to serialize and deserialize JSON JSON.parse(JSON.stringify(value))
.
Emitting events
When the event
property is set in the form definition, the following code can be used to emit the event and call gui_event
in the back-end.
component.formioEvent.emit({ "eventName": "customEvent" });
Example
In this example we create a custom number component, that has buttons to increase and decrease the value and keeps tracks of the minimum and maximum value. The example app also contains a text area that displays the current value of the custom component using calculateValue, and a button that gets and sets the submission data in the back-end.
def guiInit(_meta_data):
# Create the form and load the json builder into it.
form = Form()
## Custom extension component.
myNumber = component.Extension("myNumber", form)
# HTML definition of the custom component.
myNumber.content = """<div class="number">
<button class="number-min"><i class="fa fa-minus"></i></button>
<input class="number-input" type="number" disabled />
<button class="number-plus"><i class="fa fa-plus"></i></button>
</div>"""
# Setup the initialize and update functions that
# will determine the component's behavior.
myNumber.initializeFn = "initializeNumber"
myNumber.updateFn = "updateNumber"
# Generic properties.
myNumber.label = "Number"
myNumber.defaultValue = {"currentValue": 0}
## Display
display = component.TextArea("display", form)
display.setCalculateValue("value = JSON.stringify(data.myNumber)")
display.disabled = True
## Button
myButton = component.Button("myButton", form)
myButton.label = "Reset"
myButton.action = "event"
myButton.event = "Reset"
return {"form": form}
def guiEvent(meta_data, payload):
Form.eventHandler(Reset=resetValue)
callback = utils.getEventFunction(meta_data, payload)
payload = callback(meta_data, payload)
return payload
def resetValue(meta_data, payload):
value, _ = utils.getSubmissionData(payload, "myNumber")
print(value)
utils.setSubmissionData(
payload,
"myNumber",
{"minValue": 0, "currentValue": 0, "maxValue": 0},
)
return payload
function payload = guiInit(~)
% Create the form and load the json builder into it.
payload.form = Form();
%% Custom extension component.
myNumber = component.Extension("myNumber", payload.form);
% HTML definition of the custom component.
myNumber.content = "<div class=""number"">" ...
+ "<button class=""number-min""><i class=""fa fa-minus""></i></button>" ...
+ "<input class=""number-input"" type=""number"" disabled />" ...
+ "<button class=""number-plus""><i class=""fa fa-plus""></i></button>" ...
+ "</div>";
% Setup the initialize and update functions that
% will determine the component's behavior.
myNumber.initializeFn = "initializeNumber";
myNumber.updateFn = "updateNumber";
% Generic properties.
myNumber.label = "Number";
myNumber.defaultValue = struct('currentValue', 0);
%% Display
display = component.TextArea("display", payload.form);
display.setCalculateValue("value = JSON.stringify(data.myNumber)");
display.disabled = true;
%% Button
myButton = component.Button("myButton", payload.form);
myButton.label = "Reset";
myButton.action = "event";
myButton.event = "Reset";
end
function payload = guiEvent(meta_data, payload)
Form.eventHandler("Reset", @resetValue);
callback = utils.getEventFunction(meta_data, payload);
payload = callback(meta_data, payload);
end
function payload = resetValue(~, payload)
value = utils.getSubmissionData(payload, "myNumber");
disp(value);
value.minValue = 0;
value.currentValue = 0;
value.maxValue = 0;
payload = utils.setSubmissionData(payload, "myNumber", value);
end
// Initialize function for the custom number component.
function initializeNumber(component) {
// Get the container element.
let container = component.container;
// Get the sub-components by class.
let minMin = container.getElementsByClassName("number-min")[0];
let input = container.getElementsByClassName("number-input")[0];
let plusPlus = container.getElementsByClassName("number-plus")[0];
// Decrease the value when clicking the button.
minMin.onclick = (_) => changeValue(component, Number(input.value) - 1);
// Increase the value when clicking the button.
plusPlus.onclick = (_) => changeValue(component, Number(input.value) + 1);
// Store the input element for later use.
component.internal.input = input;
}
// Propagate the changed value to the submission data
// and emit an event to other components.
function changeValue(component, newValue) {
let input = component.internal.input;
// Change the value of the input element (in the user interface).
input.value = newValue;
// Value update works by testing equality,
// hence we need to create a new object to trigger a change.
value = { ...component.value };
// Change the value of the component (and thereby the submission data).
value.currentValue = newValue;
value.minValue = Math.min(newValue, value.minValue ?? Infinity);
value.maxValue = Math.max(newValue, value.maxValue ?? -Infinity);
component.value = value;
// Emit the value change for validation, calculateValue, etc.
component.valueChange.emit(value);
}
// Handle updated values. Called when the value changes in the submission data.
function updateNumber(component) {
// Get the container element and the input element.
let input = component.internal.input;
// Set the value in the user-interface.
input.value = component.value.currentValue;
component.value.minValue = Math.min(
input.value,
component.value.minValue ?? Infinity
);
component.value.maxValue = Math.max(
input.value,
component.value.maxValue ?? -Infinity
);
}
.number {
height: 2rem;
display: inline-block;
overflow: hidden;
border: 1px solid var(--gray);
border-radius: 0.25rem;
}
.number-min {
width: 2rem;
height: 2rem;
color: white;
outline: none;
border: 0;
border-right: 1px solid var(--gray);
background: var(--blue);
}
.number-input {
padding: 0 0.5rem;
outline: none;
border: 0;
background: white;
}
.number-plus {
width: 2rem;
height: 2rem;
color: white;
outline: none;
border: 0;
border-left: 1px solid var(--gray);
background: var(--red);
}