Skip to main content

Using code insertions

FormComposer allows using custom code insertions in these scenarios:

  • Specify lengthy content of an attribute (e.g. "instruction") in a separate HTML file
  • Define custom validators for form fileds in a JS file
  • Define custom triggers for form fileds, sections and submit button in a JS file

The inserted code must reside in separate files (called "insertion files") located in insertions subdirectory of your form config directory.

  • Remember that you can change default config directory path using --directory option of form_composer_config command

HTML content insertion

An HTML insertion file is specified as a file path that's relative to the form config. It can be inserted directly into form_config.json config, or via a token.

Insertion without token

Simply set entire value of an attribute to the insertion file's path. This is equivalent to setting value of that attribute to content of the HTML file (except now you don't have to stitch all HTML content into a single unreadable JSON line).

Attributes that support HTML insertions are the same ones that support tokens

Example in form_config.json:

{
...
"instruction": "insertions/some_content.html"
...
}

Insertion via token

Use an extrapolated token as usual, and set that token's value to the insertion file's path. Upon extrapolation, value of such token will be automatically replaced with content of the HTML file.

Example in token_sets_values_config.json:

[
{
"tokens_values": {
"some_long_content": "insertions/long_content.html"
}
}
]

Then embed this token into form_config.json:

{
...
"instruction": "Please see the below instructions: {{some_long_content}}"
...
}

JS validator insertion

You can define your own custom field validator as a Javascript function, and place it in a special file insertions/custom_validators.js inside your form config directory. When a Task is rendered in the browser, your validator function (let's call it myValidator) will be imported from this file.

In form config, the validator function is associated with a field by adding this key-value pair under "validators" attribute:

"myValidator": myValidatorArgument

In your custom code, each validator function must have the following signature:

  • Accept 2 required arguments field and value, and extra arguments as needed
    • field is a JS object representing a rendered form field
    • value is provided value of the field (format depends on the field type)
    • Extra arguments will be passed after the value argument (they come from the myValidatorArgument value you specified in the form config under "myValidator" key) :
      • If the value is a non-Array (Boolean, String, Number, or non-array Object), it will be passed as-is
      • If the value is an Array, the content of Array will be decostructed and passed as separate positional arguments.
  • Return value must be either:
    • null if validation passed successfully
    • String if validation failed
      • This value will be shown to user as an error message underneath the field, and in the error summary block

Example in custom_validators.js...

// You can import some functions from another file
import { someHelper } from "./helpers.js";

export function fieldContainsWord(field, value, word) {
someHelper();

if (value.includes(word)) {
return null;
}

return `Field ${field.name} must contain a word "${word}".`;
}

// This way you can separate all your validators into separate files, for convenience
export { phoneValidatorFunction } from "./phone_validator_code.js";

...and its usage in form_config.json:

{
...
"validators": {
"required": true,
...
"fieldContainsWord": "Mephisto"
},
...
}

JS trigger insertion

You can define your own custom trigger for a specific form element as a Javascript function, and place it in a special file insertions/custom_triggers.js inside your form config directory. When a Task is rendered in the browser, your validator function (let's call it myTrigger) will be imported from this file. NOTE: triggers are called synchronously in the current implementation and your code inside trigger funtion must be synchronous too.

In form config, the trigger function is associated with an element by adding this key-value pair under "triggers" attribute:

"myTrigger": [myTriggerEventType, myTriggerArgument]

In your custom code, each trigger function must have the following signature:

  • Accept 4 required arguments formData, updateFormData, element, fieldValue, and extra arguments as needed
    • formData is a React state with form data ({<fieldName>: <fieldValue>, ...})
      • This allows to lookup values of any form field by its "name" attribute
    • updateFormData is a callback that sets value of any form field in the React state
      • This allows to change values of any form field by its "name" attribute, e.g. updateFormData("name_first", "Austin"). Note that you will need change HTML-field value as well.
    • element is the form object that fired the trigger (i.e. "field", "section" or "submit button" object defined in form config)
    • fieldValue is the value of an element that fired the trigger, if it's a form field (otherwise it's null)
    • Extra arguments will be passed after the fieldValue argument (they come from the myTriggerArgument value you specified in the form config under "myTrigger" key) :
      • If the value is a non-Array (Boolean, String, Number, or non-array Object), it will be passed as-is
      • If the value is an Array, the content of Array will be decostructed and passed as separate positional arguments.

The myTriggerEventType parameter can take one of the following values, depending on the type of trigger element:

  • non-field elements:
    • "onClick"
  • "field" element:
    • checkbox field type:
      • "onChange"
      • "onClick" (triggered from any part of the entire field, not just the buttons)
    • file field type:
      • "onChange"
      • "onBlur"
      • "onFocus"
      • "onClick"
    • input field type:
      • "onChange"
      • "onBlur"
      • "onFocus"
      • "onClick"
    • radio field type:
      • "onChange"
      • "onClick" (triggered from any part of the entire field, not just the buttons)
    • select field type:
      • "onChange"
    • textarea field type:
      • "onChange"
      • "onBlur"
      • "onFocus"
      • "onClick"

Example in custom_triggers.js...

export function onClickSectionHeader(
formData, // React state for the entire form
updateFormData, // callback to set the React state
element, // "field", "section", or "submit button" element that invoked this trigger
fieldValue, // (optional) current field value, if the `element` is a form field
sectionName // Argument for this trigger (taken from form config)
) {
alert(`${sectionName} section was clicked!`);
}

...and its usage in form_config.json:

{
...,
"triggers": {
"onChange": ["onChangeName", [true, {"prefix": "_"}, 100]]
}
...
"triggers": {
"onClick": ["onClickSectionHeader", "Triggerable"]
}
...
}

JS trigger helpers

During development of your form config, you can use a few available helper functions.

  1. validateFieldValue - checks whether you're assigning a valid value to a field (in case you want to change them programmatically).

    Example of use:

  2. Add react-form-composer in webpack config

    resolve: {
    alias: {
    ...
    "react-form-composer": path.resolve(
    __dirname,
    "<relativePath>/packages/react-form-composer"
    ),
    },
    }
  3. Add import

    import { validateFieldValue } from "react-form-composer";
  4. Validate a value before assigning it to form field

    const valueIsValid = validateFieldValue(formFields.languageRadioSelector, {"en": true, "fr": false}, true);