| Version | Date | Notes | By |
|---|---|---|---|
| 1.0 | 2020-12-03 | Initial release | ROB |
| 1.1 | 2023-06-19 | Added information on frontend script processing | ROB |
This tutorial will demonstrate how to create a new component for WeFlow™ scripting engine. For a new component to be created it must be added both to the frontend, as a definition with sockets and options, and as a backend class that contains the code that turns inputs and options into outputs.
To add a component to weflow a new js file must be created in modules\processes-engine\custom-elements\weflow-editor\components with the name in kebab case + component at the end (for example arithmetic-mixed-component).
Inside, create a class with the same name as the file and extend Rete.Component.
In the constructor you must use the following structure:
constructor(editorManager)
{
// display name of component (Rete.Component handles that, but must be already translated)
super(editorManager.appContainer.i18n.tr('form.field.arithmetic-mixed'));
// component's propper name (will be referenced by backend as identity for this component in specific)
this.weflow_slug = 'arithmetic-mixed-component';
// reference to WeFlow editor manager (contains the editor and socket references / relations)
this.editorManager = editorManager;
// category of component, allows for organizing the components into separate tabs
this.category = 'arithmetic';
}
At the end of the class the following parameter must be defined and set (this informs the weflow designer component):
// This component can be created in backend and frontend and counts as a general component
ArithmeticMixedComponent.MODES = ComponentModes.MODES.BACKEND | ComponentModes.MODES.FRONTEND | ComponentModes.MODES.GENERAL;
The component has two more functions that must be added in, the builder function and the worker function. The worker function is to be left empty for use in the future (when a frontend WeFlow processing engine becomes necessary). For now lets look at the builder function:
/**
* Builder function receives a node instance, when dragging a new component to working area
* a node instance is created and passed through this builder in order to receive the inputs, outputs, data and controls
*/
builder(node)
{
// NOTE: the input and output classes need to be included, they are located in
// wemake-modules/processes-engine/custom-elements/weflow-editor/io-classes
// add inputs
node.addInput(new FlowInput(
'flow', // name of input - referenced by WeFlow execution engine
'form.field.input-flow', // display name of input - just for display purposes
this.editorManager._sockets.multipleVariableFlow // socket reference - controls which outputs can connect with this input
));
// add outputs
// outputs are defined just like inputs, using the same options
node.addOutput(new AttributeOutput('result', 'form.field.result', this.editorManager._sockets.float));
// add control
// NOTE: options for controls depend on the speficic control being used
// usually the first 3 options are allways the same:
node.addControl(new ArithmeticSignControl(
this.editorManager, // reference to editor manager
'sign', // name of control - will be referenced by WeFlow execution engine
'form.field.sign' // display name for control
));
// for now these last two lines are required, working on a way to make this automatic
node.weflow_type = 'operation'; // available options are input, output and operation
node.data.weflow_slug = this.weflow_slug;
}
If the function worker is defined then the component can be used in frontend processing
worker(node, inputs, outputs) {
// inputs and outputs are associative arrays, you can reference values by name
// inputs are prepared to receive multiple values (one for each connection), in weflow this is irrelevant, assume first connection
// is the only connection being used
outputs['result'] = inputs['input_1'][0] * inputs['input_2'][0]; // for example result = input_1 * input_2
}
Once this is done, this new component must be included in the editor manager so that it shows up at the bottom, inside the appropriate tab. Include the class in the editor manager wemake-modules/processes-engine/custom-elements/weflow-editor/index.js and add a line in the function initComponents:
initComponents()
{
// ...
this.editor.register(new ArithmeticMixedComponent(this));
// ...
}
This concludes the frontend portion of component creation.
Controls allow for values to be set directly in the component, without that value having to be retrieved from another component. WeFlow has many controls that cover a few cases, but it may be necessary to create new controls.
In order to create controls, three files are necessary inside the wemake-modules/processes-engine/custom-elements/weflow-editor\controls using kebab case for the control name + '-control.js', kebab case for the control name + '-component.js' and kebab case for the control name + '-component.html'.
For example a control for arithmethic sign may be defined as follows, for the control file arithmetic-sign-control.js, use the following code:
import Rete from "rete";
export class ArithmeticSignControl extends Rete.Control {
constructor(editorManager, key, name, readonly = true) {
super(key); // key is the data name where data[key] = value
this.editorManager = editorManager; // reference to editor manager
// component has to be a string that can be used by the <compose> to implemment the actual control
this.component = 'wemake-modules/processes-engine/custom-elements/weflow-editor/controls/arithmetic-sign-component';
// params to be passed to the actual control
this.params = { editorManager, editorManager, name: name, key: key, readonly: readonly, control: this };
// render provider (this must be allways 'aurelia')
this.render = 'aurelia';
}
}
As for the implementation of the arithmetic-sign-component.js and arithmetic-sign-component.html, this will vary wildly based on what the control is suposed to do. It can implement various html form components directly or use our form schema system or other unknown forms of control. In the end what must happen is that the data[key] = value.
Creating a backend component class is not required if the component is frontend only (not even an entry in the component definition is required).
In the backend, inside the folder modules\ProcessesEngine\Systems\WeFlow\Components create a new php file and class as bellow:
<?php
namespace WeMake\Sgi\Modules\ProcessesEngine\Systems\WeFlow\Components;
use WeMake\Sgi\Modules\ProcessesEngine\Models\TokenProcessObject;
class ArithmeticMixedComponent extends BaseComponent {
public function execute(TokenProcessObject $tokenProcessObject) {
// code that turns inputs, data and controls into outputs goes here
}
}
Your new class must extend the BaseComponent class, as demonstrated above. The base class will provide all the functions used inside the execute function.
Inside the execute function you can retrieve the values for inputs (these values will be sent by the outputs of previous components), the data values (as written by the frontend, think of data as component specific constants) and the control values (value selected / entered by the script creator using the control, and which are also written to data). See example bellow:
public function execute(TokenProcessObject $tokenProcessObject) {
// retrieve inputs
$input1 = $this->getInputValue(
'input_1', // name of input
true // input is required for execution of script (if this is true and no value exists, a scripting error is thrown)
);
$input2 = $this->getInputValue('input_2', true); // this function returns an WeFlowVariable class instance, this instance contains the value inside, it can be retrieved with getVariable function
// actual code for this component
$result = $input1->getVariable() + $input2->getVariable();
// set an output
$this->setOutputData('result', $result); // value can be passed directly to this function, it will be encapsulated inside a WeFlowVariable
}
Once you are happy with the new component you've created, it must be added to the component definition list. This list provides validation to user scripts and allows the WeFlow processing engine the ability to instance the component class. In the file modules\ProcessesEngine\Systems\WeFlow\WeFlowComponentDefinition.php inside the function retrieveDefinitions add a new element to the collection returned by it, with the following structure:
public static function retrieveDefinitions() {
return collect([
// ...
'arithmetic-mixed-component' => [ // index = name of component
'code-type' => 'operation', // code type one of following options ['input','output','operation']
'name' => trans('form.field.arithmetic-mixed'), // display name for component
'class' => \WeMake\Sgi\Modules\ProcessesEngine\Systems\WeFlow\Components\ArithmeticMixedComponent::class, // fqcn of class to instance
'required_inputs' => [ // any input name in this array will cause a validation error if there is no incomming connection
'input_1',
'input_2'
],
'required_data' => [ // any data name in this array will cause a validation error if there is no selected / entered data
'sign'
],
'required_outputs' => [ // any output name in this array will cause a validation error if there is no outgoing connection
'result'
]
],
// ...
]);
}