Form Renderer

A technical deep-dive into the powerful Form.io JavaScript Renderer

Introduction

One of the most significant differences between Form.io and other form products is how forms are rendered within applications. Most form platforms render forms on the Server (using technologies like PHP, .NET, Java, etc) and then send the rendered Form HTML up to the browser to be used. This approach has many problems, especially when being utilized within a modern mobile-first web application. In order to service the new Progressive Web Applications being developed today, Form.io has completely redefined how forms should be rendered so that they are flexible, extensible, and performant. Forms created within the Form.io builder are actually represented as JSON schemas that are then rendered directly within the application using a JavaScript rendering engine that we call the "Form Renderer". Here is an image that illustrates how this rendering works within a mobile application.

How the Form.io JavaScript Renderer works

The library responsible for this rendering can be found on Github @ https://github.com/formio/formio.js and is also Open Source so that any developer can fork and extend the functionalities provided by this library.

Getting Started

In order to fully understand how the Form.io Renderer works, it is important to try out the JavaScript renderer using real examples that you can easily walk through. To start, we will first create a new Project within the Form.io Portal @ https://portal.form.io, and then create a new simple form that we will use to test out the JavaScript renderer. Here is just an example of what your form may look like.

We will also want to make sure we copy the Form API url path (circled in the picture above) and save this for later when we wish to render the form. Now that we have our form, we can test out how this form will be rendered.

To test our the JavaScript Renderer, you can use one of many online web application editors. The ones that we recommend are:

For this demonstration, we will just go to JSFiddle and first add the following Resources.

  • https://cdn.form.io/formiojs/formio.form.min.js

  • https://cdn.form.io/formiojs/formio.form.min.css

  • https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css

These urls are described as follows:

  • formio.form.min.js - This is the minified JavaScript source code for the Form.io Renderer

  • formio.form.min.css- This is the minified CSS style sheets for the Form.io Renderer

  • bootstrap.min.css - This is the Bootstrap CSS Framework which Form.io uses to render forms.

Your JSFiddle should now look like the following.

This is the exact same thing as if you were building an HTML application from scratch and had the following HTML code.

<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.form.io/formiojs/formio.form.min.css" crossorigin="anonymous">
</head>
<body>
<script src="https://cdn.form.io/formiojs/formio.form.min.js" crossorigin="anonymous"></script>
</body>
</html>

Next, we will add a place where we will render the form. We can do this by adding a single div tag into the HTML region and then giving it an "id" so that we can refer to it within our JavaScript initialization code (which we will write later). We can copy the following code into the HTML section of JSFiddle

<div id="formio"></div>

It should look like the following.

This is the exact same thing as if we did the following in raw HTML

<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.form.io/formiojs/formiojs.form.min.css" crossorigin="anonymous">
</head>
<body>
<div id="formio"></div>
<script src="https://cdn.form.io/formiojs/formiojs.form.min.js" crossorigin="anonymous"></script>
</body>
</html>

Rendering Form URL

And now for the fun part. We will now use the Form.io JavaScript SDK to instantiate the form that we just built within the Form.io Portal. We will do this by writing the following code within the JavaScript section of our JSFiddle.

Formio.createForm(document.getElementById('formio'), 'https://myproject.form.io/renderertest');

Make sure you replace the project name "myproject" with the name of your project, and assuming that you named the test form the same as our example, you will see the following when you run the application in JSFiddle.

Here is the JSFiddle link for you to try this yourself.

The Form.io renderer is very flexible and as such can be configured to achieve many different use cases. Because of this, we have built a dedicated application that is used to demonstrate many of the features that our form renderer has to offer. This can be found at the following url.

Form.io Examples Application

Rendering Form JSON

In addition to rendering a URL, the form renderer can also be used to render simple JSON passed into the renderer like so.

Formio.createForm(document.getElementById('formio'), {
components: [
{
type: 'textfield',
key: 'firstName',
label: 'First Name'
},
{
type: 'textfield',
key: 'lastName',
label: 'Last Name'
},
{
type: 'email',
key: 'email',
label: 'Email'
},
{
type: 'button',
key: 'submit',
label: 'Submit'
}
]
});

Which will render the form as follows.

If you wish to get the JSON of a form as you build it with the form builder, then we suggest that you check out the Form.io Builder Sandbox which can be found @ https://formio.github.io/formio.js/app/builder

Submission Data

Now that we have rendered a form, the next step will be to inject a submission into the form so that it will show data pre-populated within the form. One interesting thing to note about Form.io is that it completely separates the Form JSON from the Submission JSON and they are treated as separate JSON entities. Submission data will never be included as part of the form JSON. For example, the following Form JSON

{
"components": [
{
"type": "textfield",
"key": "firstName",
"label": "First Name"
},
{
"type": "textfield",
"key": "lastName",
"label": "Last Name"
}
]
}

will take the following submission data JSON.

{
"data": {
"firstName": "Joe",
"lastName": "Smith"
}
}

There are a couple of things to note here.

  1. The submission data for the form is contained within a "data" object.

  2. The "keys" for the submission data is determined by the "key" property of each component within the form.

You can alter the data structure by using dot-notation in the Form JSON which will allow you to alter the data construct of the submission. For example, if you wish to include the firstName and lastName fields within a "customer" data object, you could do the following.

{
"components": [
{
"type": "textfield",
"key": "customer.firstName",
"label": "Customer First Name"
},
{
"type": "textfield",
"key": "customer.lastName",
"label": "Customer Last Name"
}
]
}

The dot-notation for the "keys" in this form tell the renderer to structure the submission as follows.

{
"data": {
"customer": {
"firstName": "Joe",
"lastName": "Smith"
}
}
}

Data Components

Another way to alter the submission data construct is to use any of the Data Components within a form. These are special components that are used to not only visually show the data being collected in a data structured way, but will also change the data structure of the submissions being produced by the rendered form. For example, if you wish to produce the following submission data which is an array of children's first and last names, you can use the Data Grid component as follows.

{
"components": [
{
"type": "datagrid",
"label": "Children",
"key": "children",
"components": [
{
"type": "textfield",
"key": "firstName",
"label": "First Name"
},
{
"type": "textfield",
"key": "lastName",
"label": "Last Name"
}
]
}
]
}

Produces the following submission JSON.

{
"data": {
"children": [
{
"firstName": "Joe",
"lastName": "Smith"
},
{
"firstName": "Mary",
"lastName": "Thompson"
]
}
}

And for the Data Grid component, it looks like the following when being rendered.

There are other kinds of data components as described as follows.

Component

Type

Description

Data Grid

datagrid

Spreadsheet UI that stores an array of objects

Edit Grid

editgrid

Table UI that stores an array of objects, with inline edit

Data Map

datamap

Key-value pair where string key can be provided for dynamic values

Data Table

datatable

Grid UI that stores an array of objects

Container

container

Hidden container UI that stores components inside an isolated object.

Hidden

hidden

Hidden UI that can store any data value in any data structure.

Rendering Submissions

In order to render a submission, you must first render the form, and then set the form submission to the submission data you wish to render within the form. You can either render the form as JSON or as a form URL as described above, and then the submission is set once the form is done rendering. As a simple example, you can provide the following to demonstrate how a submission can be rendered within a form.

Formio.createForm(document.getElementById('formio'), {
components: [
{
type: 'textfield',
key: 'firstName',
label: 'First Name'
},
{
type: 'textfield',
key: 'lastName',
label: 'Last Name'
}
]
}).then((form) => {
form.submission = {
data: {
firstName: 'Joe',
lastName: 'Smith'
}
};
});

Which will render as the following.

You can also render the submissions from a Form URL as the following demonstrates.

Formio.createForm(document.getElementById('formio'), 'https://examples.form.io/example')
.then((form) => {
form.submission = {
data: {
firstName: 'Mary',
lastName: 'Thompson',
}
};
});

which will render as follows.

Rendering Submission API URL

The last way to render a submission is to render the submission via the API endpoint of that submission. This Submission API is described as follows.

get
Retrieve a form submission

https://yourproject.form.io/:formName/submission/:submissionId
Retrieves a single submission within a form.
Request
Response
Request
Path Parameters
formName
required
string
The "alias" name of the form
submissionId
required
string
A valid MongoDb ID for the submission "_id"
Response
200: OK
An example response for a valid form submission.
{
"_id": "5fc7c4082a992abe68b247b9",
"owner": "5fc7beeb2a992abe68b2475c",
"roles": [],
"_vid": 0,
"_fvid": 1,
"state": "submitted",
"data": {
"firstName": "Joe",
"lastName": "Smith",
"email": "[email protected]",
"phoneNumber": "(123) 123-1234"
},
"access": [],
"form": "5fc7c25a2a992abe68b247b4",
"project": "5fc7befe2a992abe68b24765",
"externalIds": [],
"created": "2020-12-02T16:42:48.128Z",
"modified": "2020-12-02T16:42:48.129Z"
}

In order to utilize this rendering correctly, you will need to ensure that the user you are authenticated as has access to this submission. You can authenticate a user by setting a valid JWT token within the "formioToken" localStorage variable of your application.

This URL can then be added to the renderer to render a complete form with submission as follows.

Formio.createForm(
document.getElementById('formio'),
'https://examples.form.io/wizard/submission/5a542c9e2a40bf0001e0f8a9'
);

Which will render the form and submission as follows.

Controlling the Form with JavaScript

One of the most powerful concepts of Form.io rendered forms is that you can control the rendered form using JavaScript. In most cases, this is done within the section of code that executes once the form has finished rendering. This is commonly referred to as the "Form Controller" section of the form renderer and can be seen as follows.

Formio.createForm(document.getElementById('formio'), 'https://examples.form.io/example')
.then((form) => {
// This section of code is the "Form Controller"
});

The variable that is passed to this function can be called whatever you want, but form or instance are very common names. This variable is actually the Webform instance of the following source code.

https://github.com/formio/formio.js/blob/master/src/Webform.js - For regular webforms

https://github.com/formio/formio.js/blob/master/src/Wizard.js - For wizards

https://github.com/formio/formio.js/blob/master/src/PDF.js - For PDF forms

Because of this, any method within these classes (and their derived classes) can be executed by referencing them on the form or instance variable. So many things can be accomplished using these variables. Here are just a few use cases that be done.

Log change events

// This section of code is the "Form Controller"
form.on('change', (changed) => {
console.log('Data was changed!', changed);
});

Thank You Page after submission

// This section of code is the "Form Controller"
form.on('submitDone', function(submission) {
window.location = '/app/thanks.html';
});

Custom Wizard Controls

// This section of code is the "Form Controller"
/**
* This code assumes a "wizard" is rendered, and that there are buttons in the
* wizard form that emit the events "gotoNextPage", "gotoPreviousPage" and
* "wizardSave"
**/
form.on('gotoNextPage', function() {
form.nextPage();
});
form.on('gotoPreviousPage', function() {
form.prevPage();
});
form.on('wizardSave', function() {
form.submit().then(function() {
form.onChange();
form.nextPage();
});
});

This is such a powerful concept that there is actually a feature called the Form Controller where these controllers can be added to the form JSON and then will be executed in the same fashion as these indicate. This can be configured within the Form Settings of the form and you would use the variable name instance instead of form as shown above.

Form Renderer Options

In addition to rendering a form and providing submission data, you can also provide options to the renderer to control its behavior even further. The options are passed as the third parameter to the Formio.createForm method as shown below.

Formio.createForm(element, src|form, options)

The options available are documented as follows.

Option

Description

Default

readOnly

Disables all input is set to true

false

noDefaults

Do not establish default submission values. Leave unset unless the user interacts with the form.

false

language

The current language for the renderer

en

i18n

The i18n configurations for the renderer. See Form Renderer Translations section

{}

viewAsHtml

Boolean to tell the renderer to render this form in "html" mode.

false

renderMode

The mode that the form should render within. This picks different render modes within the templates section. See Form Templates section for more information.

form

highlightErrors

Highlight the errors for each field.

true

componentErrorClass

The default CSS class to be applied to the error dom element.

formio-error-wrapper

template

The current template name

templates

Ability to add custom templates to the renderer. See Form Templates section.

iconset

The iconset to use within the renderer.

buttonSettings

For wizards only. Controls the settings and visibility of the wizard button settings. These are configured as follows.

{

"buttonSettings": { "showCancel": true, "showNext": true,

"showPrevious": true

"showSubmit": true }

}

{}

components

Allows for overrides for certain components rendered.

Example: Adds a prefix to all textfield components rendered.

{

"components": { "textfield": { "prefix": "hello" } }

}

{}

disabled

Allows for component overrides for disabled fields.

Example: Disable the firstName component.

{

"disabled": { "firstName": true }

}

{}

showHiddenFields

Boolean that, when set to true, will show all the hidden fields regardless of conditionals.

false

hide

Force certain components to be hidden regardless of conditionals.

Example: Hide the firstName and lastName components.

{

"hide": {"firstName": true, "lastName": true}

}

show

Force certain components to be shown regardless of conditionals.

Example: Show the firstName and lastName components.

{

"show": {"firstName": true, "lastName": true}

}

formio

Your own instance of the Formio class.

decimalSeparator

Used for Number components. Determines the decimalSeparator. Defaults to the browser default setting.

thousandsSeparator

Used for Number components. Determines the thousands separator. Defaults to the browser default setting.

fileService

A custom File Service instance.

hooks

Allows you to implement certain hooks within the renderer. See Form Hooks section.

These options can be applied to the renderer like the following example shows.

Example: Render a submission in read only mode.

Formio.createForm(document.getElementById('formio'), 'https://examples.form.io/wizard/submission/5a542c9e2a40bf0001e0f8a9', {
readOnly: true
});

Form Properties

There are many different properties and methods that can be called on the form instance. Many of these methods are documented within the auto-generated SDK documentation found @ https://formio.github.io/formio.js/docs/

Within these documentations, you will see some of these methods described as follows.

While this documentation is helpful to understanding all the methods, here are few of the most used properties on the form instance.

form.form

The current form JSON that is loaded into the form.

form.submission

The current form submission JSON that is loaded into the form.

form.schema

A minified "schema" of the components loaded into the renderer. This is dynamically generated so use sparingly.

form.ready

A promise that is resolved when the form has finished rendered, submission data has been saturated, and the form is "ready".

form.ready.then(() => {
form.submission = {
data: {
firstName: 'Joe',
lastName: 'Smith'
}
};
});

form.loading

Boolean to indicate if the form is currently loading (true) or not (false).

form.src

The current form source that is loaded within the renderer.

form.language

The current language for this form.

Form Methods

Here are few of the most used methods on the form instance.

form.setForm(form, [flags])

Sets the JSON schema for the form to be rendered. It returns a promise that resolves when the form has been completely rendered and attached.

Parameter

Description

form

The JSON schema of the form

flags

Optional flags to control the behavior of the change event loop.

form.setForm({
components: [
{
type: 'textfield',
key: 'firstName',
label: 'First Name',
placeholder: 'Enter your first name.',
input: true
},
{
type: 'textfield',
key: 'lastName',
label: 'Last Name',
placeholder: 'Enter your last name',
input: true
},
{
type: 'button',
action: 'submit',
label: 'Submit',
theme: 'primary'
}
]
});

form.form = {...}

This is a "setter" alias for form.setForm. It can be used as follows.

form.form = {
components: [
{
type: 'textfield',
key: 'firstName',
label: 'First Name',
placeholder: 'Enter your first name.',
input: true
},
{
type: 'textfield',
key: 'lastName',
label: 'Last Name',
placeholder: 'Enter your last name',
input: true
},
{
type: 'button',
action: 'submit',
label: 'Submit',
theme: 'primary'
}
]
};

form.setSubmission(submission, [flags])

Sets a submission and returns the promise when it is ready.

Parameter

Description

submission

The submission JSON you wish to set.

flags

Flags to control the behavior of the change event loop

form.setSubmission({
data: {
firstName: 'Joe',
lastName: 'Smith',
}
});

form.submission = {...}

This is a "setter" alias for form.setSubmission. It can be used as follows.

form.submission = {
data: {
firstName: 'Joe',
lastName: 'Smith',
}
};

form.setSrc(src, [options])

Set's the "src" of the rendered form. This is the API endpoint for either the Form URL, or the Submission URL. If you provide just a form src, then it will only load the form. If you provide a Submission URL, then it will load both the form and then saturate that form with the submission data.

form.src = '...'

A "setter" alias for form.setSrc.

form.language = '...'

Sets the language of the renderer.

form.loading = true

Sets the form to start loading (showing the spinner icon).

form.saveDraft()

Saves a draft submission.

form.restoreDraft(userId)

Restores a draft submission for a specific user ID

form.redraw()

Force a redraw of the form.

form.resetValue()

Force a reset value on the form.

form.submit()

Submit the form.

form.checkData()

Performs a check on the submission data for calculations, conditionals, and validations.

form.everyComponent(fn)

Iterate through every component within the form.

Parameter

Description

fn

A callback that will be called for every component. The signature for this component is as follows.

fn(component, components, index)

  • component - The component instance for the current component.

  • components - An array of the components that are the "siblings" of the component

  • index - The index of the component within the components being triggered.

form.everyComponent((component) => {
if (component.component.key === 'firstName') {
component.setValue('Joe');
}
});

form.getComponent(path|key, [fn])

Retrieve a component from the form.

Parameter

Description

path|key

The key of the component you wish to fetch, or the data path of that component.

fn

Callback function to be called when the component is found.

const email = form.getComponent('email');
email.setValue('[email protected]');

form.checkValidity([data], [dirty], [row], [silent])

Checks the form validity and updates the errors if the validity fails.

Parameter

Description

data

The data to check against. If no data is provided, then the submission data in context will be used.

dirty

If this should force the "dirty" flag on the components when performing the checks. If "true" this will highlight all invalid form fields as red on the form.

row

The row data to check against. If no data is provided, then the contextual row data will be used.

silent

If "true", then this will perform a passive check for validity and will not affect the visuals of the form and highlight any fields red if they are invalid.

if (!form.checkValidity(null, false, null, true)) {
alert('The form is invalid!);
}

Form Events

Within the Form.io renderer, there are a number of events that are fired that allow you to respond to these events and then do something once they fire. A very common example of this is to listen for anytime someone changes a field within the form, log that change for audit reasons.

The Form.io renderer uses the EventEmitter3 library to manage all of the event handling that occurs within the renderer, and because of this, any method that this library includes can also be used within the renderer, as the following example illustrates.

// Listen for change events and log the changes as they occur.
form.on('change', (changed) => {
console.log(changed);
});

The following events are triggered within the Form.io renderer.

Event

Description

Arguments

change

A value has been changed within the rendered form

  • changed: The changes that occurred, and the component that triggered the change. See "componentChange" event for description of this argument

  • flags: The change loop flags.

  • modified: Flag to determine if the change was made by a human interaction, or programatic

error

An event that fires when errors have occurred within the renderer

  • errors: An array of errors that occurred.

formLoad

The form json has finished loading

  • form: The form json that was loaded

submit

A form has been submitted

  • submission: The submission json object.

  • saved: Boolean to indicate if the submission was saved via API or not.

submitDone

Similar to "submit" but only fires when the submission is "saved" via API

  • submission: The submission json object.

submitError

Called when a submission error has occurred

  • error: The error that was fired.

render

The form is done rendering and has completed the attach phase.

  • element: The root element of the renderer

initialized

Called when the form has completed the render, attach, and one initialization change event loop

requestDone

Called when the Action url is provided to the renderer to submit to a custom action url. This is fired when the request is finished.

languageChanged

Called when the language has been changed.

saveDraftBegin

Called when a save draft has started

saveDraft

Called when a save draft operation has finished

  • submission: The submission that was saved as draft.

restoreDraft

Called when a draft submission has been restored into the renderer.

  • draft: The draft submission that was restored.

submissionDeleted

Called when a submission has been deleted.

  • submission: The submission that was deleted.

redraw

Triggered when a component redraws

focus

Triggered when an input component has received focus

  • instance: The component instance

blur

Triggered when an input component has been blurred

  • instance: The component instance.

componentChanged

Triggered when a specific component changes

An object containing the following properties.

  • instance: The component instance

  • component: The component json

  • value: The value that was changed

  • flags: The flags for the change event loop.

componentError

Triggered when an error occurs within a specific component

  • error: The error that has occurred.

submitButton

Triggered for button components configured as a Submit action, when they are clicked.

customEvent

Triggered for button components configured as Event action. This is fired when they are clicked.

An object containing the following properties.

  • type: The configured event type.

  • component: The component json

  • data: The contextual data for this button.

  • event: The click event

editGridAddRow

For EditGrid components, fired when a row has been added

An object containing the following properties.

  • component: The component json

  • row: The edit grid contextual row object.

editGridSaveRow

For EditGrid components, fired when a row has been saved.

An object containing the following properties.

  • component: The component json

  • row: The edit grid contextual row object.

editGridDeleteRow

For EditGrid components, fired when a row has been deleted.

An object containing the following properties.

  • index: The index of the row that was deleted.

fileUploadingStart

For File components, fired when a file upload has started

The file upload promise.

fileUploadingEnd

For File components, fired when the file upload had completed.

The file upload promise.

nextPage

For Wizards, this is triggered when the next page is navigated to.

An object that contains the following properties.

  • page: The current page

  • submission: The current submission.

prevPage

For Wizard, this is triggered when the previous page is navigated to.

An object that contains the following properties.

  • page: The current page

  • submission: The current submission

pagesChanged

For Wizard, this is triggered when the pages of the wizard changes.

wizardPageClicked

For Wizard, this is triggered when the wizard navigation page has been clicked.

  • page: The page that was clicked.

wizardPageSelected

For Wizard, this is triggered when the wizard page has been selected and is done rendering.

  • page: The page that was selected

  • index: The index of the page that was selected.

Hooks

Hooks allow you to alter the behavior of the form and block the execution of certain functionalities in favor of providing your own logic. A good example of this is to provide a beforeSubmit hook where you can block the submission and alter the the submission or even perform your own validations. Each hook is provided using the options of the renderer like so.

Formio.createForm(document.getElementById('formio'), 'https://examples.form.io/example', {
hooks: {
beforeSubmit: (submission, next) => {
// Alter the submission
submission.data.email = '[email protected]';
// Only call next when we are ready.
next();
}
}
})

Here is a list of all available hooks within the renderer.

beforeSubmit(submission, next)

Allows you to hook into the submit handler before the submission is being made to the server. Each parameter is described as follows.

Param

Description

submission

The submission data object that is going to be submitted to the server. This allows you to alter the submission data object in real time.

next

Called when the beforeSubmit handler is done executing. If you call this method without any arguments, like next(), then this means that no errors should be added to the default form validation. If you wish to introduce your own custom errors, then you can call this method with either a single error object, or an array of errors like the example below.

Custom Errors

It is a very common use case to provide your own custom errors to the submit handler. To achieve this, you can call the next callback with either a single error object, or an array of errors you wish to introduce to the error handler. Here is an example of how to introduce some custom errors.

Formio.createForm(document.getElementById('formio'), 'https://examples.form.io/example', {
hooks: {
beforeSubmit: (submission, next) => {
// Make a custom ajax call.
$.ajax({
url: 'https://myserver.com/validate',
method: 'POST',
data: submission,
complete: (errors) => {
let submitErrors = null;
if (errors) {
submitErrors = [];
errors.forEach((error) => {
submitErrors.push({
message: error.toString()
});
});
}
next(submitErrors);
}
});
}
}
})

Saving and Restoring submissions

This hook can also be used to ensure the integrity of submission data so that if any error occurs, the submission can be restored. The following code illustrates how this can be done.

Formio.createForm(document.getElementById('formio'), 'https://examples.form.io/example', {
hooks: {
beforeSubmit: (submission, next) => {
localStorage.setItem('currentData', JSON.stringify(submission.data);
next();
}
}
}).then(function(form) {
var currentData = localStorage.getItem('currentData');
if (currentData) {
form.submission = {data: JSON.parse(currentData)};
}
form.on('submitDone', function() {
localStorage.removeItem('currentData');
});
});

beforeNext(currentPage, submission, next)

Allows you to hook into the submit handler before the switching to next page. Each parameter is described as follows.

Param

Description

currentPage

The current page data object. This allows you to use page data for your submissions on each page.

submission

The submission data object that is going to be submitted to the server. This allows you to alter the submission data object in real time.

next

Called when the beforeNext handler is done executing. If you call this method without any arguments, like next(), then this means that no errors should be added to the default form validation. If you wish to introduce your own custom errors, then you can call this method with either a single error object, or an array of errors like the example below.

Custom Errors

It is a very common use case to provide your own custom errors to the submit handler before user switching to next page. To achieve this, you can call the next callback with either a single error object, or an array of errors you wish to introduce to the error handler. Here is an example of how to introduce some custom errors.

Formio.createForm(document.getElementById('formio'), 'https://examples.form.io/example', {
hooks: {
beforeNext: (currentPage, submission, next) => {
// Make a custom ajax call.
$.ajax({
url: 'https://myserver.com/validate',
method: 'POST',
data: submission,
complete: (errors) => {
let submitErrors = null;
if (errors) {
submitErrors = errors.map(error => ({
message: error.toString()
}));
}
next(submitErrors);
}
});
}
}
})

beforePrev(currentPage, submission, next)

Called before the previous page has been navigated.

Param

Description

currentPage

The current page data object. This allows you to use page data for your submissions on each page.

submission

The submission data object that is going to be submitted to the server. This allows you to alter the submission data object in real time.

next

Called when the beforePrev handler is done executing. If you call this method without any arguments, like next(), then this means that no errors should be added to the default form validation. If you wish to introduce your own custom errors, then you can call this method with either a single error object, or an array of errors like the example below.

customValidation(submission, next)

Provides a hook to inject custom validations into the submission process.

Formio.createForm(document.getElementById('formio'), 'https://examples.form.io/example', {
hooks: {
customValidation: (submission, next) => {
// Make a custom ajax call.
$.ajax({
url: 'https://myserver.com/validate',
method: 'POST',
data: submission,
complete: (errors) => {
let submitErrors = null;
if (errors) {
submitErrors = errors.map(error => ({
message: error.toString()
}));
}
next(submitErrors);
}
});
}
}
})

Param

Description

submission

The submission data object that is going to be submitted to the server. This allows you to alter the submission data object in real time.

next

Called when the beforeSubmit handler is done executing. If you call this method without any arguments, like next(), then this means that no errors should be added to the default form validation. If you wish to introduce your own custom errors, then you can call this method with either a single error object, or an array of errors like the example below.

attachWebform(element, instance)

Called once the webform has started its attach phase.

Parameter

Description

element

The DOM element that the webform is attaching to.

instance

The webform instance object.

The method should either return the "element" or another DOM element that will be used to attach the webform object to.

beforeCancel()

Called before a form is canceled which provides the ability to abort the cancel process.

Example: If you wish to reject a form cancel process.

Formio.createForm(document.getElementById('formio'), 'https://examples.form.io/example', {
hooks: {
beforeCancel: () => {
// Return false to abort the cancel process.
return false;
}
}
})

component()

Triggered when a component has been instantiated. The "this" pointer will point to the current component instance.

attachComponent(element, instance)

Triggered when a component has been attached to an element.

Parameter

Description

element

The DOM element that the component is attaching to.

instance

The component instance object.

Returns the "element" or another element this component should be attached to.

setDataValue(value, key, data)

Hook into the setting of a data value for a specific component.

Parameter

Description

value

The value that is being set.

key

The component key

data

The components contextual data object.

Returns the new value that should be made to that component.

addComponents(components, instance)

Called before a NestedComponent instantiates and adds its components provided by the JSON of those components.

Parameter

Description

components

An array of the components JSON that is getting added.

instance

The nested component instance.

Returns the new array of components that should be added.

addComponent(component, data, before)

Called just before a component is going to be instantiated.

Parameter

Description

component

The component JSON that is going to be added.

data

The component data context for this component

before

The DOM element to insert the component 'before'

Returns the new component JSON that should be used when adding a component to the renderer.

Example: Change all rendered fields to be required.

Formio.createForm(document.getElementById('formio'), 'https://examples.form.io/example', {
hooks: {
addComponent: (component) => {
component.required = true;
return component;
}
}
})

attachComponents(element, components, container, instance)

Called just before a nested component attaches its children to their elements.

Parameter

Description

element

The DOM element that contains the children

components

An array of component instances.

container

The JSON array of components

instance

The nested component instance.

onCalendarOpen()

Triggered when the calendar widget has been opened.

onCalendarClose()

Triggered when the calendar widget closes.

Overriding Behavior

In many cases, you will need to override certain behaviors of the renderer that neither a configuration, hook, or form event will be able to achieve the behavior you are looking for. When this occurs, you can override any behavior of the form renderer and form builder by hooking into specific methods and defining your own behavior or routines. Any overrides to the Form.io renderer can be achieved through the Formio global object. This object can be seen if you inspect the console within the Form.io Portal by just typing Formio. into the console and will then autocorrect the following variables available to you.

The Formio class interface

Each one of these objects allow access to the underlying classes that define the behavior of the renderer. They are described as follows.

Interface

Description

Formio.Builders

Interface to the form builder classes

Formio.Components

Interface to the form component classes.

Formio.Displays

Interface to the form renderer classes (form, wizard, pdf)

Formio.Providers

Interface into the form provider classes (file uploads, address providers)

Formio.Rules

Interface into the form validation rules.

Formio.Templates

Interface to the form template.

Each one of these introduces a list of classes that can be overridden such as the following. A great example of this are the different components that can be instantiated within a form, which can be seen by typing Formio.Components.components into the console.

Each of these are a separate class that will instantiate when the component is created. This is very useful because we can access the prototype of each of these classes to override certain behavior. The methods that can be overridden can be found by first navigating to the source code of that specific component and viewing the methods available to that class as well as any class that it derives from.

For example, let's suppose that you wish to override the behavior of the Button component to trigger a browser "alert" asking the user if they wish to submit the form before it is submitted. We can achieve this by first viewing the source code behind the Button component found at https://github.com/formio/formio.js/blob/master/src/components/button/Button.js

Within this code, we will see that there is a method called onClick that is executed when the button is clicked. We can inject our alert by first saving this function to a variable, then override the method with our own implementation that will call the original onClick when the user accepts the window.confirm.

var onClick = Formio.Components.components.button.prototype.onClick;
Formio.Components.components.button.prototype.onClick = function(event) {
if (window.confirm('Are you sure you want to press this button?')) {
onClick.call(this, event);
}
};

We can actually test this within the Form.io portal by copying and pasting the following within the Developer console when you are on the Form.io portal as the following illustrates.

Now, we just need to render the form using the "Use" tab (which will execute our override when it renders the button component), and then try to submit the form to get the following.

This code can now be copied into your application where any form rendered within your application will execute the your override. An example of what this method override would look like in your application would be as follows.

import { Formio } from 'formiojs';
const onClick = Formio.Components.components.button.prototype.onClick;
Formio.Components.components.button.prototype.onClick = function(event) {
if (window.confirm('Are you sure you want to press this button?')) {
onClick.call(this, event);
}
};

Using this method, it is now possible to override any aspect of the renderer or builder to achieve the kind of custom behavior that you are looking for. The best thing to do is to investigate the source code found @ https://github.com/formio/formio.js to see all the different kinds of methods that can be overridden to create the behaviors you are looking for.

Overriding GET and SET methods

In some situations, you may also need to override a "getter" or a "setter" method. These methods look like the following within the base classes.

export default class HTMLComponent extends Component {
...
get content() {
return ...;
}
...
}

These methods can be overridden in a similar way described above, but they do require some different syntax. In order to achieve this, we need to use the Object.getOwnPropertyDescriptor and Object.defineProperty methods. For example, if we wish to append some HTML code to every HTMLComponent using the content method, you could write the following logic which will override the content getter method.

const contentProps = Object.getOwnPropertyDescriptor(Formio.Components.components.html.prototype, 'content');
const contentGet = contentProps.get;
contentProps.get = function() {
return contentGet.call(this) + '<span>Additional Content!</span>';
}
Object.defineProperty(Formio.Components.components.html.prototype, 'content', contentProps);

Overriding Base Classes

In some cases, you may also need to override the base classes which are used by all components. To do this correctly is tricky since it does require that you override the base class methods BEFORE the other component classes are declared. This can be done by adding some logic that is imported into your application BEFORE the renderer is imported like so.

// Import just the base component.
import Component from 'formiojs/components/_classes/component/Component';
const baseInit = Component.prototype.init;
Component.prototype.init = function() {
// Do something custom here!!
return baseInit.call(this);
}
// Now import formiojs so all components will use the overridden base component.
import { Formio } from 'formiojs';

This will now allow you to alter the behaviors of all components that are instantiated within the form.io renderer.