Form Evaluations
This section describes how the Form.io Renderer evaluates javascript code.
Last updated
This section describes how the Form.io Renderer evaluates javascript code.
Last updated
Within the renderer and builder code, it is possible write custom snippets of JavaScript to perform custom actions that would otherwise be difficult to configure. These snippets of JavaScript can be seen within the Form Builder interface under sections such as Custom Default Value, Calculated Values, Logic, as well as many others. The typical interface for such JavaScript snippets is shown within a special javascript editor within the form builder with a section above that showing the variables available within the execution context, such as the following image shows.
For every javascript evaluation that occurs, there are a number of variables that are presented within the execution sandbox. These variables are commonly referred to as the evalContext variables. Some of the evaluations provide their own evalContext, which will be documented below, but the following eval variables that are common across all evaluation types.
form
The complete Form JSON schema of the form.
{
"_id": "....",
"path": "user",
"name": "user",
"display": "form",
"components": [
{
...
}
]
}
submission
The complete Submission JSON of the current submission for the rendered form.
{
"data": {
"first": "Joe",
"last": "Smith"
},
"metadata": {}
}
component
The current Component JSON schema.
{
"type": "textfield",
"label": "Name",
"key": "name"
}
value
The current component value.
show = value === 5;
instance
This points to the component instance. This is helpful if you wish to gain access to the actual component instance object to perform special functions and execute certain methods of the component. Go to https://github.com/formio/formio.js/blob/master/src/components/_classes/component/Component.js to see what methods you can access.
valid = instance.checkValidity()
self
Alias of "instance"
instance.root
This always points to the form instance that contains the component. This is helpful if you wish to reference other components using the following code.
instance.root.getComponent('email') = 'joe@example.com';
options
The Form Renderer Options passed to the renderer.
{
"i18n": {
"language": "es",
"es": {
...
}
}
}
data
The root data context for the renderer. This will always point to the full data object from "submission.data"
{
"first": "Joe",
"last": "Smith",
"children": [
{
"first": "...",
"last": "..."
}
]
}
row
The "row" variable is a special "context" data object that points to the current data "context" of the component. This changes based on what component we are referring to. For example, if you are within a DataGrid component (which is an array of objects), the "row" will point to the current row object.
Let's suppose you have a DataGrid called Children, and you wish to write javascript to validate the birthday component within the children DataGrid. You would be able to use "row" to point to the "current" row's birthday field like the following.
row.birthday
rowIndex
This is the index for the current row you are on. For DataGrid and EditGrid components, this is a number where 0 means we are on the first row, 1 means we are on the second row, etc.
0 - first row
1 - second row
...
t
A function, which is used to translate certain strings using the Translation system.
value = t('First Name');
_
An instance of Lodash which can be used to simplify certain operations within your javascript code.
value = _.get(data, 'a.b')
utils
An instance of the Form Utilities.
utils.eachComponent(component.components, function(component) {
...
});
util
Alias of "utils"
user
The currently authenticated User object.
value = user.data.email;
token
The current JWT token for the authenticated user.
In addition to having the standard variables as shown above, it is possible to also introduce your own evaluation context variables that can be used within the evaluations. This is very helpful in case you have pre-defined methods for validations, etc. that you would like to expose to all of the evaluations. This can be achieved using either the Form Module found in your Project Settings, or through the embedding of the form.
Form Module Example
Within your Project Settings, click on Settings > Custom JS and CSS. Within this section, you will see a section called Form Module which is used to write a snippet of JSON that is able to dynamically configure the form as it is being embedded within an application. You can introduce a new context variable as follows.
With this example, there would now be a new method available in the evaluation context called "validatePhone" and could be used as follows within a Custom Validation block.
Form Embedding Example
You can also set custom evaluation context variables when you embed the form. The following shows an example of how this could be done.
This can then be used in the same way as described above.
Now that we have an understanding of evaluation contexts, let's discuss all the different places where javascript evaluations can be performed. There are many sections that allow for JavaScript evaluation. These sections are described as follows.
This provides a way to set the custom default value of the component you are currently configuring. The default value is the value that is used at the initialization of the component and provides you an opportunity to set the initial value of the component, but also provides a good point to place any initialization code you may wish to add to the component.
Additional Evaluation Context
For custom default values, there are a few additional evaluation context variables that are used.
value
The value to set as the custom default value.
value = 5;
Example 1: Set custom default value to the combination of other fields.
Example 2: Listen for change events of this component and set the value of another component
The calculated value snippet allows you to write custom pieces of javascript that set the value of a component. The value is set by setting the variable "value" within the snippet of javascript.
Additional Evaluation Context
For calculated values, there are a few additional evaluation context variables that are used.
value
The value to set as the calculated value.
value = 5;
Example 1: Perform a Total Amount calculation on values in a data grid
Assuming that there is a Data Grid component called "Scores" that contains a component within the data grid called "Score", you could have a component outside of the data grid, called "Total" with the following calculated value.
Example 2: Conditionally calculate a value
You can also choose to set the value within an if statement, and this would only set the value under certain conditions. For example, this could be used to force a "maximum" value.
Custom Validations allow you to write a snippet of JavaScript that decides how the component should be validated as well as what error to show when the evaluation is determined to be invalid. This can be achieved using the following additional context variables exposed to the custom validation section.
Additional Evaluation Context
For calculated values, there are a few additional evaluation context variables that are used.
input
The value that has been input into the component that is being compared for evaluation.
valid = input === 5;
valid
A special variable that determines if the component is valid. If the value is set to "true", then the component is valid. Otherwise you would set the value of "valid" to the string you would like to show the user when it is invalid.
valid = input === 5 ? true : 'The value must be 5!';
Example 1: Validate that this "validate password" field matches the "password" field.
Advanced conditions allow you to write a snippet of Javascript that determines the visibility/validation condition for the component. When the value of a conditional is set to false, the component effectively becomes "inactive" which means it is both not included visibly in the form, but also is not evaluated for validity. This is commonly used to present different sections of the form based on the answers provided by other fields.
Additional Evaluation Context
For advanced conditions, there are a few additional evaluation context variables that are used.
show
Determines if this component is visible or not. If show is equal to "false", then the component not only becomes invisible, but also is no longer evaluated for validity. For example, if the field is required, but is conditionally not visible, then it no longer is required.
show = value === 'Testing';
value
The current value of the component that is being compared for evaluation.
show = value === 5;
Within the Logic tab, there is an ability to add Logic to your forms to perform different operations such as hiding the component, making it required, etc under certain conditions. The triggers for logic determine how the current logic section is triggered.
Additional Evaluation Context
For calculated values, there are a few additional evaluation context variables that are used.
result
Determines if the logic section should be triggered. If result is set to "true" it will be triggered, if it is set to "false", then it will not
result = data.email === 'admin@form.io';
Example 1: Trigger the logic if the average grades are less than 70
Within the Logic, after a logic section has been triggered, it will now perform the "action" of that logic. This is the "do something" part of the logic section where it performs an operation. Within this section, it is possible to write your own javascript to perform the action you would like to perform within the action.
Example 1: Set the component value when the value executes.
Within every evaluation type, there is also the ability to configure the evaluations using JSON schemas. This is helpful if you wish to perform complex evaluations without the requirement of executing the JavaScript "eval" necessary to perform the javascript evaluations of the scripts shown above.
There are other options for not using "eval" such as using the protected eval plugin described below, but JSON Logic also serves as a good strategy for such protections.
The JSON Logic system uses the JSON Logic Library to perform the evaluations needed for each of the evaluation sections. Below are some examples of the different evaluation types on how this system can be used to create complex evaluations without using any JavaScript.
Example 1: Concatenate the value of two different components string values together.
Example 1: Sum together a multiple value number component
For custom validations, you need to always use the "if" parameter within JSON Logic. The first argument to the "if" statement is the "true" case, and the second should be the error that you show when the value is not set. The following shows examples of how this is used.
Example 1: Validate if a string is equal to a value.
Example 1: Validate if an email is from a certain domain
While the Form.io platform offers a large amount of flexibility with the evaluations and executions of scripts within the renderer, it also includes even more flexibility and extensibility through the use and modification of the Evaluator within the renderer.
The Evaluator is a static class object where all javascript executions pass through to be evaluated. The code for the basic Evaluator class can be found at the following code.
This class is used to perform any executions and 'eval' processes within the renderer from template rendering to the execution of javascript snippets within the renderer. The instance can also be referenced within your application by simply accessing the following property on the global Formio object.
Using this instance, you can easily configure how the Evaluator behaves such as the following describes.
There are many ways that you can use the instance of the Evaluator to modify how it behaves by default. Some of these ways are described below:
The first thing you may wish to do is completely disable all evaluations from occurring. You would want to do this if you are running the renderer within a tight security environment where you may not trust the form builders who created the form, and worry about the execution of malicious JavaScript within your application. This can be done by setting the noeval property to true like so.
The evaluator is also used to evaluate all templates and perform interpolations, such as the following.
This string may be contained within an HTML Element component within the form, but since it contains a token, it will be interpolated using the evalContext as described in the section above. It is possible to change the format of all of these templates to use a different syntax. For example, if you wish to change it to the following.
you could use the templateSettings property of the Evaluator to accomplish this goal.
These settings are described as follows.
evaluate: This is the template for string "evaluations" where it executes the javascript within. This requires that you have evaluations turned on by ensuring the "noeval" flag is not set or set to false.
interpolate: This is where you can provide "interpolated" replacement values based on the values provided by the evalContext. For example {{ data.email }}
will replace that token with the value from "data" with the key of "email"
escape: This configuration tells the interpolation how to find "escaped" tokens. For example, if you wish to actually display {{ data.email }}
and do not wish for it to be interpolated, then you will use the following string. \{\{ data.email \}\}
In addition to providing configurations for the Evaluator, it is also possible to perform overrides to inject your own custom code into how evaluations are performed. This can be done by injecting your own methods into the Evaluator that "inject" your own code into a method.
As a quick example of something that could be done, let's suppose you wish to console.log
all evaluations that were being performed within the renderer, the following code could be used to "inject" this into the renderer evaluation execution.
This method shows how you can save off the original function as a variable, then override the method, and then use that original saved function as the function you return with the evaluation. This allows you to inject your own console.log into the process so that you can understand all evaluations that are being executed.
The following methods can be overridden:
evaluator(func, ...params)
- Returns a method of evaluation. By default, this returns a new function that will be evaluated at a later time.
template(template, hash)
- Converts a string into a template function that will be executed later.
interpolate(rawTemplate, data, _options)
- Accepts a string template, along with data context variables, and returns the interpolated result string.
evaluate(func, args)
- Executes the evaluator function (above) with the arguments provided.
A very good example of how to override the Evaluator can actually be found within our Open Source server code which overrides the evaluator to perform all javascript evaluations within a VM on the server to protect against malicious code. This code in complete form can be found @ https://github.com/formio/formio/blob/master/src/util/util.js#L56
Here is a snippet that shows how the evaluator can integrate with VM2.
This code does a couple of things.
It turns off any normal evaluations by setting "noeval" to true.
It creates a new VM to perform any javascript executions within.
It overrides the "evaluator" method to execute the script within a VM and return the result. It also ensures that the ONLY variables that the script has access to within the VM are those provided by the evalContext.
In addition to overriding the base Evaluator, the renderer also enables the ability to create your own Evaluator class and register it as the new evaluator for the renderer, this documentation shows you how this can be done.
Very similar to overriding the methods described above, it is possible to completely write your own Evaluator class, and then register that Evaluator as part of the renderer. In order to accomplish this, you will need to provide implementation to a few methods that are called from the renderer, which is defined below.
Once you have implemented your own Evaluator class, you can then register this class using the registerEvaluator
method as follows.
It is also possible to create a Form.io module, and include your custom evaluator within the export of your module as follows.
Then, someone can implement your evaluator by simply using the module as follows.
As a good example of an Open Source module that implements a Custom Evaluator, but also serves a large benefit of providing a "protected" evaluator using JS Interpretor, you can take a look at the library https://github.com/formio/protected-eval
This library can be installed and used as follows.
There are some differences in using this evaluator that need to be understood, so please read more information about this evaluator by going to the following Github repo.