Form Templates

Describes the Form.io Form Template System

The Form.io template platform allows you to completely change the rendered HTML for any component within the Form.io platform. You can either create an entire template for all components, or simply override existing templates if you wish to just alter a few of the components.

There are several examples of templates that have been created which can be seen at the following URL's.

Setting a CSS Framework

When setting up an application, you can select which CSS Framework you want to render the output to. Form.io currently supports the following frameworks:

  • Bootstrap 3 (bootstrap3)

  • Bootstrap 4 (bootstrap)

  • Semantic UI (semantic)

  • Other - Contact us for help implementing another framework!

The default template is currently Bootstrap 4 so if you want to use that, you don't have to do anything else. In order to switch to a different framework, you will need to set it globally so that the renderer will use the framework's templates.

import { Templates } from 'formiojs';

Templates.framework = 'semantic';

If you are using a framework wrapper, you should import Templates from that wrapper instead of formio.js so that it will apply to the Formio instance within the wrapper as well. For example, if you are using react-formio you should do:

import { Templates } from 'react-formio';

Templates.framework = 'semantic';

Plugin registration

Templates can also be registered through the Plugin system, where an external template is provided to the renderer. For an example of how this is done, please see https://github.com/formio/semantic

While these examples represent complete templates, you can also create partial overrides as well.

Overriding templates

In addition to setting the global CSS Framework, you can also override specific templates within that framework. All templates within the renderer are pre-compiled templates. This means that instead of using HTML for each template, you will provide a template function, which simply takes a context variable and returns a string that is the HTML you would like to render. An example of a template function looks like the following.

const myInput = function(ctx) {
  return '<div class="' + ctx.component.type + '">' + ctx.value + '</div>';
};

Or if you are using ES6, it can be written easier as follows.

const myInput = (ctx) => `<div class="${ctx.component.type}">${ctx.value}</div>`;

You can override a bunch of template functions by setting the current template with the templates you wish to override. This allows you to change the html output of the component even as the component continues to function the same. In order to do this, simply set the template on the Templates object and the renderer will start using it.

You can do this one of two ways. First, by setting multiple templates:

Templates.current = {
  input: {
    form: (ctx) => '<div>My custom template</div>'
  },
  html: {
    form: (ctx) => '<div>My other custom template</div>'
  }
};

Or individually:

Templates.current.input.form = (ctx) => `<div>My custom template</div>`;

You can extend an existing template by first getting the current template function and then call that function in addition to your own. This allows you to provide wrappers around certain elements within the renderer. Here is an example of how to do this.

var inputTemplate = Templates.current.input.form;
Templates.current = {
  input: {
    form: (ctx) => `<div class="input-wrapper">${inputTemplate(ctx)}</div>`
  }
};

IMPORTANT NOTE: These examples are in ES6. Depending on where you are making these changes, you may need to have them written in ES5 so that they will work with all browsers. Example.

var inputTemplate = Templates.current.input.form;
Templates.current = {
  input: {
    form: function (ctx) {
      return '<div class="input-wrapper">' + inputTemplate(ctx) + '</div>';
    }
  }
};

Specific templates

Templates can be shared by multiple components. For example, the input template is used by many components to render the html input element. Sometimes you may want to override it for only certain components but not all components. You can do this by setting the template name to include the specific information. This can either be the component type or the component property name (API key). You can even do both. The renderer will search in this order for the first match to determine which component to select:

`${templateName}-${component.type}-${component.key}`,
`${templateName}-${component.type}`,
`${templateName}-${component.key}`,
`${templateName}`,

If you set a template for 'input-number' it will only override the input template for number components. If you set a template for 'input-number-total' it would only override the input template for a number component with the property name of 'total';

Render modes

In addition to templates, there are a few render modes that allow different representations of the same component. The default mode is form which will render all the components as form components. Another common mode is ```html`` which will render an html-only representation of the component without any form elements like inputs. This is useful for displaying data.

The built-in render modes are:

  • form - The default render to render as a form.

  • HTML - Render the data as generic html, instead of a form.

  • flat - Similar to form but will flatten out components like tabs and wizards that tend to hide data.

  • builder - A builder view of a component (not frequently used outside of the form builder).

To set the render mode, set it on the options when instantiating a form.

import { Form } from 'formiojs';

const form = new Form(document.getElementById('formio'), 'https://examples.form.io/example', {
  renderMode: 'html'
});

You can even create your own render modes and use them with your forms.

import { Form, Templates } from 'formiojs';

Templates.current = {
  input: {
    foo: (ctx) => '<div>bar</div>'
  }
};

const form = new Form(document.getElementById('formio'), 'https://examples.form.io/example', {
  renderMode: 'foo'
});

Custom Component Templates

You can create custom components using the same lifecycle methods.

An example can be found at https://github.com/formio/react-app-starterkit/blob/master/src/components/CheckMatrix.js

Template References (refs)

With the form.io templating functionality, the underlying DOM structure can be very different, even entirely custom. Because of this, adding events to the DOM necessitates being able to find the right part of the DOM to add the events. In order to do this, the formio.js library uses refs to refer to parts of the DOM. These can then be selected regardless of where they are in the DOM.

When rendering a template, you can use the ref attribute to set a reference string. Then in the attach phase, you can get any DOM elements that have that ref, regardless of where they are. In order to facilitate this, formio.js has a "loadRefs" function that can find all the refs and adds them to this.refs.

import { Templates, Components, Component } from 'formiojs';

Templates.addTemplate('mytemplate', {
    form: (ctx) => `
<div>
  <div ref="myref">
    ${ctx.foo}
    <div ref="mychild">1</div>
    <div ref="mychild">2</div>
    <div ref="mychild">3</div>
  </div>
</div>
`
});

class MyComponent extends Component {
  init() {
    // Init tasks here.
  }

  render() {
    // By calling super.render, it wraps in component wrappers.
    return super.render(this.renderTemplate('mytemplate', {
        foo: 'bar',
        data: 'these are available in the template'
    }));
  }

  attach(element) {
    this.loadRefs(element, {
      myref: 'single',
      mychild: 'multiple',
    });

    this.refs.myref; // This will be either null or the div with "myref" set on it.
    this.refs.mychild; // This will be either null or an array of divs with "mychild" set on them.
  }

  detach() {
    // Called on redraw or rebuild. The opposite of attach.
  }

  destroy() {
    // Called on rebuild. The opposite of init.
  }
}

Components.addComponent('mycomponent', MyComponent);

Layout component references

Layout components can contain other components within them including other components of the same type. For this reason, be careful with refs in layout components and be sure to append the component id to each key. This is so that loadRefs only selects the refs for that component and not any nested components.

Server Side Rendering

One of the goals of the 4.x branch was to make the formio.js renderer compatible with server side rendering. While this should be compatible with it due to the changes that were made, we haven't had a chance to fully test it out yet. We would greatly welcome some help in testing this out.

Here is how it is designed to work:

On the server side:

import { Form } from 'formiojs';

// Instead of a URL, you can also use a form definition JSON instead.
const form = new Form('https://examples.form.io/example');
form.ready.then(function(instance) {
  const html = instance.render();
  // At this point you can put the html in the page.
});

On the client side:

import { Form } from 'formiojs';

const form = new Form('https://examples.form.io/example');
// Find the DOM element wherever it is.
const element = document.getElementById('formio');
form.ready.then(function(instance) {
  instance.attach(element);
});

Last updated