Deliver complex Manual Task customizations via MT Designer
In this article, you can find a runbook describing how to create complex custom configurations for Manual Tasks created from Task Designer Operation and to resolve related issues.
note
Before reading the topic, make sure you have read the instructions in Design Manual Tasks via Task Designer Operation.
Querying Data Stores
Data Stores are tables containing data in different formats. You can access them via Control Tower: go to Advanced → Data Stores.

If you click any item in the displayed Data Store list, you will see that each table has a different set of columns, for example, as shown below:

To query a Data Store, create a custom query. Custom queries are defined per Manual Task as described below:
Go to Manual Tasks and pick a task from the list.
Navigate to the Design tab.
Switch to Code Editor.
Click Allowed Data Store Queries.
In the displayed popup, click Create Query.
In the screen that appears next, set up a name to reference the query, select the associated Data Store, and add the query.

In the example above, the following query is used:
SELECT * from @this where cost_center=:value
The query comprises two variables:
@thisreferences the Data Store selected in the Data Store drop-down field in the figure above.:valueis the name of the field where you send data in the next step. Typically, multiple field names are used, but for simplicity, the example includes only one.
After the query is created, proceed as described below:
On the Design tab of the Manual Task, switch to Task Designer.
In the displayed layout, find the Text Field component that will send a request to the named query and click the cogwheel button to open its Settings page.

Navigate to the Validation tab and click Custom Validation.
In the custom validation code block, paste the following code:
// a general-purpose function to send a named query request function queryDataStore(queryName, parameters) { var nativeId = new URLSearchParams(window.location.search).get('submissionUUID'); var url = new URL(`${window.location.origin}/workfusion/api/v2/datastores/executeNamedQuery`); url.searchParams.set('queryName', queryName); url.searchParams.set('parameters', JSON.stringify(parameters)); url.searchParams.set('nativeId', nativeId); return fetch(url.toString()) .then(response => response.json()); } // the actual query queryDataStore('cost_center_query', {value: input}) .then(console.log);
The code above prints the query results to the console. You can see that the queryDataStore function uses the previously defined cost_center_query query name and a parameter object with the value field.
Restricting input to be selected from Data Store entries
To use a custom Data Store as the source of a component, follow the steps below:
Go to the Data tab of the component and set URL for Data Source Type.
To use a relative path, specify Data Source URL in the following format:
{{window.location.origin}}/workfusion/api/v2/datastores/autocomplete-data?datastore={datastore_name}&maxRows=300If you use a heavy Data Store, apply
maxRowsto define a maximum amount of records to return. In the example above, 300 records are used. If the parameter is not provided, the server uses 50 records by default.
note
The autocomplete-data endpoint requires a Data Store with at least the id and name columns. If the Data Store does not contain these columns, an error is thrown.
Let's consider some required fields:

The properties are as follows:
ID Path defines where to select the option's ID, for example, the
rowData[0]element in the data source URL JSON's response.Value Property defines where to get values for selection, for example, the
rowData[1]element in the data source URL JSON's response.
Item Template defines the HTML template to display items for selection, for example, the
rowData[1]element in the data source URL JSON's response.
To configure other fields, follow the best practices described for http://form.io.
Advanced rendering for HTML element
Content rendering in an HTML element has access to global variables, despite the fact it is not mentioned in the provided description. The variables available in a context are listed in the figure below:

Example:
To render a label with an icon when the control value=pass, use the following code:
{% const key = 'company_id_comment' %}
{%
let label, iconColor, iconName;
if (data[key] === 'pass') {
label = 'Passed';
iconColor = '#51c071';
iconName = 'check_circle';
}
%}
<div class="validation-message">
<font face="Material Icons" size="4" color="{{iconColor}}">{{iconName}}</font>
<span>{{label}}</span>
</div>
In the code above:
At line #1, there is an inline script block.
At line #2-#9, there is a multi-line script block.
At line #4, you access a global data object.
At #11 and #12, you can interpolate the variables defined in the script block.
Weird validation behavior
On the Validation tab, you can select a validation strategy for a form layout component via the Validate On field:

The following options are available:
Change: the custom logic is triggered after every keystroke.
Blur: the custom logic is triggered after the focus is not on a control anymore.
However, if you choose the Blur strategy and start typing in another component, you can notice that validation is triggered after every keystroke. This means that the validation strategy always behaves like it’s set to Change whenever you modify another component.
Select component
For a Select component, you can only set the validation strategy to Change. The Blur strategy will not work with it.
Triggering validation after component value change
Sometimes, you need to trigger validation only when a component value changes, for example, to send a Data Store request. There is no baked-in feature that supports the logic. To make it work, open the Validation tab, click Custom Validation, and add the script in the code block:
function hasInputChanged() {
if (instance.getValue() !== instance.previousValue) {
instance.previousValue = instance.getValue();
return true;
}
return false;
}
if (hasInputChanged() && instance.getValue()) {
// put a custom code here
console.log('input has changed')
}
Setting validation and values for components other than validated one
The example below illustrates the following validation case: once a component value is cleared, you clear other components as well.
function hasInputChanged() {
if (instance.getValue() !== instance.previousValue) {
instance.previousValue = instance.getValue();
return true;
}
return false;
}
// list of api keys of other components
const fieldsToClear = [
'gln',
'payment_bankgiro',
'payment_iban',
'organization_number',
'vat_number',
];
if (hasInputChanged() && !instance.getValue()) {
for (const fieldName of fieldsToClear) {
// get a schema object for a component
const schemaComponent = utils.getComponent(form.components, fieldName);
// get an instance of a component
const result = instance.root.getComponentById(schemaComponent.id);
// manually reset the value
result.setValue('')
}
}
Setting component validity after asynchronous action
In a custom validation code block, you can assign a valid variable, as mentioned in the instruction to the block:

So, you can set the following:
valid = false
However, you CANNOT do the following:
setTimeout(() => {
valid = false
}, 1000)
To make it work asynchronously, add more code to it. In the example below, you query a Data Store only when the input changes. Then, if the response is empty, you set an error message:
function queryDataStore(queryName, parameters) {
// function from data store example
}
function hasInputChanged() {
// function from validation trigger example
}
function setError(message) {
instance.setCustomValidity(message);
}
if (hasInputChanged() && instance.getData()) {
queryDataStore('spaas_3_mt_other_cost_center_v1_tagged_value', {value: input})
.then(response => {
if (response.length === 0) {
setError(`Value doesn't exist in a data store`)
}
})
.catch(error => {
setError(error.message);
});
}
Thanks to instance.setCustomValidity(message) present in the code above, you do not set a valid variable directly. Instead, you invoke a setCustomValidity method on a component instance itself.
Inaccessible variable in custom validation
The help section for the custom validation feature contains a list of available variables. However, the list is not accurate. See the correct one below:
