Skip to main content

Building a Component

Form Builder provides the ability to create your own custom components. This page will discuss how to create a custom component that displays a HTML5 number input.

Registering the component

First, add the following to your CustomXFormsProBundle/Resources/config/services.yml file:

services:
custom_xfp.component.number_field:
class: Jadu\Custom\CustomXFormsProBundle\Component\NumberFieldComponent
arguments:
- "@custom_xfp.component.number_field_service"
- "@custom_xfp.component.number_field_settings_service"
tags:
- { name: xfp.form_component, label: 'Number Field', icon: 'icon-number' }

custom_xfp.component.number_field_service:
class: Jadu\Custom\CustomXFormsProBundle\Component\NumberFieldComponentService
arguments:
- "@doctrine.orm.entity_manager"

custom_xfp.component.number_field_settings_service:
class: Jadu\Custom\CustomXFormsProBundle\Component\NumberFieldComponentSettingsService
arguments:
- "@doctrine.orm.entity_manager"
note

The services: line is only needed once, at the beginning of the services.yml file.

Here we've added three classes:

  • NumberFieldComponent - the main component definition
  • NumberFieldComponentService - a service to handle displaying, and saving data from, the component on the form
  • NumberFieldComponentSettingsService - a service to handle displaying, and saving data from, the settings pane when adding the component to a Page Template in the Control Center.

NumberFieldComponent is tagged as xfp.form_component, so Jadu Central knows to treat it as a form component. The tag sets the name and icon for the component (this will display in the Control Center when building Page Templates).

NumberFieldComponent

Create a class at CustomXFormsProBundle/Component/NumberFieldComponent.php:

namespace Jadu\Custom\CustomXFormsProBundle\Component;

use Jadu\XFormsPro\Form\Builder\Component\BaseComponent;

class NumberFieldComponent extends BaseComponent
{
}

This is a really simple class. It extends BaseComponent, a base class for all components.

NumberFieldComponentService

Create a class at CustomXFormsProBundle/Component/NumberFieldComponentService.php with the following content:

namespace Jadu\Custom\CustomXFormsProBundle\Component;

use Jadu\XFormsPro\Form\Builder\Component\BaseComponentService;

class NumberFieldComponentService extends BaseComponentService
{
public function getTwigTemplate()
{
return 'number_field_component.html.twig';
}
}

Again, this is a really simple implementation. It extends BaseComponentService, which is the base class for all component services. It then declares the twig template which will be used to render the component on the form.

BaseComponentService provides a lot of other functionality, accessed by overriding different methods. These are listed below, and more detailed can be found in the docblocks for the methods:

| Method | Description | |-:-|-:-| | saveAnswer | Takes in data about an answered question, and is responsible for saving the answer to the database | | getTwigParameters | Calls getComponentTwigParameters, and then adds in parameters for any standard question settings e.g. if the field is required, and help text | | getComponentTwigParameters | Gets any twig parameters needed for custom settings on the component | | getPreviewTwigTemplate | Loads a different twig template for the component in the Control Center. Useful if the preview differs from what is actually shown on the form | | showLabel | Whether to show the question label when rendering on a form, defaults to true | | isComponentVisible | Whether the component is visible on the form | | savesComplexData | Whether the component saves a complex (JSON) answer, defaults to false | | getAnswerVariables | If savesComplexData is true, defines an array of keys in the saved JSON object | | isCompound | Whether the component contains multiple HTML input fields, defaults to false | | notifySubmission | If necessary, runs logic when submitting the form that relates to this question |

The getTwigTemplate method in the class points to a file inside CustomXFormsProBundle/Resources/views/Form/Component. Our twig file might look similar to this:

{% if question.value is defined %}
{% set value = question.value %}
{% elseif question.default is defined %}
{% set value = question.default %}
{% endif %}

<input class="form__field"
type="number"
id="{{ question.name }}"
name="{{ question.name }}"
value="{{ value }}"
/>

Saving the answer

saveAnswer appears to be a complex function, and it accepts many parameters. The implementation in BaseComponentService does the following:

  1. Formats the $data argument into a clean string, by encoding it to JSON if it is an array. $data contains the user's answer to the question
  2. Throws an exception if the question was visible ($inVlisibleSection is true), the question is required and $data is empty
  3. Throws an exception if the question has validation applied and $data does not match it.
  4. Throws an exception if the question restricts answer length and $data is too long.
  5. Finds existing answer by querying the database for an answer record that matches parameters $userFormID, $pageID, $pageVisit, $questionName, $sectionName and tableRow (if $tableRow is set - this shows we are working with a question in a repeatable question).
  6. If $inVisibleSection is true, either update the existing answer, or create a new one. If not, removes the existing answer from the database.

If you require different logic to save an answer, consider calling parent::saveAnswer from the override method. If this is not suitable, ensure that your method takes similar steps to those above, to avoid unexpected behaviour around in page branching.

NumberFieldComponentSettingsService

Create a class at CustomXFormsProBundle/Component/NumberFieldComponentSettingsService.php with the following content:

namespace Jadu\Custom\CustomXFormsProBundle\Component;

use Jadu\XFormsPro\Form\Builder\Component\BaseComponentSettingsService;

class NumberFieldComponentSettingsService extends BaseComponentSettingsService
{
public function getTwigTemplate()
{
return 'CustomXFormsProBundle:Form:Component/number_field_component_settings.html.twig';
}
}

This is yet another really simple implementation. It extends BaseComponentSettingsService, which is the base class for all component setting services. It then declares the twig template which will be used to render the settings pane when adding the component to a page template.

As with BaseComponentService, BaseComponentSettingsService implements methods in a best fit way, and this default functionality can be changed by overriding relevant methods. The methods that can be overridden are:

| Method | Description | |-:-|-:-| | validateCustomSettings | Uses the passed in Symfony Request object, and should return an array of error strings, indexed by the id of the error field. The base class will handle errors for any standard settings | | saveCustomSettings | Is passed the question object and the Symfony Request object, and is responsible for creating QuestionSetting objects for any non-standard settings. This can be done by calling $this->createSetting, passing the question object, the setting name and the value to save| | getTwigParameters | Gets any twig parameters needed to render the settings pane | | canUseInRepeatableDetails | Can answers to this component be displayed in the summary table of a repeatable? Defaults to true |

The getTwigTemplate method in the class points to a file inside CustomXFormsProBundle/Resources/views/Form/Component. Our twig file might look similar to this:

{% import "XFormsProCCBundle:Form:Pages/QuestionSettings/settings_macros.html.twig" as settings %}

{% if questionSettings.required is defined %}
{{ settings.is_required_field(true) }}
{% else %}
{{ settings.is_required_field(false) }}
{% endif %}

{% if questionSettings.default is defined %}
{{ settings.prefill_default_field(true, questionSettings.default, false) }}
{% else %}
{{ settings.prefill_default_field(false, '', false) }}
{% endif %}

{% if questionSettings.help is defined %}
{{ settings.help_text_field(true, questionSettings.help) }}
{% else %}
{{ settings.help_text_field(false) }}
{% endif %}

{{ settings.visibility_fields(questionSettings) }}

Settings Macros

As shown in the file, Form Builder provides a set of macros, for easily defining common fields. This can be used by just importing the file, as shown.

Each macro will add a core field to the setting pane, These core fields are all supported by BaseComponentSettingsService, so there is no need to save or validate these settings in the service methods. Supported core fields are:

| Name | Field Identifier | Description | |-:-|-:-|-:-| | Validation | validation | Lets Administrators apply a validation routine to the question | | Required | required | Lets Administrators make the question required | | Internal Only | internal_only | Lets Administrators determine that the question should only appear in internal forms | | Placeholder | placeholder | Lets Administrators specify HTML5 placeholder text | | Empty option | empty_option | Lets Administrators specify an empty option, e.g. 'Please choose...' in a drop down | | Help Text | help | Lets Administrators add help text to the question | | Restrict Length | answer_length | Lets Administrators restrict the length of answers to a certain number of characters | | Default Answer Field | default | Lets Administrators specify some default text | | Default Answer Select | default | Lets Administrators specify one or more options to use as the default answer | | Value Field | value | Lets Administrators specify a value for the field | | Value Text Area | value | Lets Administrators specify a multi-line value for the field | | Dimensions | rows, cols | Lets Administrators specify dimensions of a text area | | Answers | answers | Lets Administrators specify predefined options that the user can select an answer from |

You should indicate in the constructor of your component's Settings Service which standard settings you expect. This allows Form Builder to validate and save them for you. To do this, call $this->showStandardSetting and pass the Field Identifier from the above table.

The following are enabled by default:

  • Validation
  • Restrict Length
  • Required
  • Default Answer
  • Placeholder
  • Help
  • Internal Only

If you need to add a custom question setting, you can use generic definitions from the set of macros. Definitions are provided for the following:

| Name | Macro | Description | |-:-|-:-|-:-| | Toggle Switch | toggle_switch | Toggling this on/off will show/hide the next field in the settings pane | | Toggle Field | toggle_field | An on/off toggle | | Dropdown | select | A dropdown list | | Select2 | multiple_select | A select2 dropdown | | Text | text | A text box without a label, useful in conjunction with a Toggle Switch if the label of the Toggle makes it clear what should go in the text box | | Text with Label | text_with_label | A text box with a label above it | | Text Area | textarea | A text area without a label, useful in conjunction with a Toggle Switch if the label of the Toggle makes it clear what should go in the text area | | Text Area with Label | textarea_with_label | A text area with a label above it | | Labelled Field | labelled_field | A text box with an appended label on the field |

Note that each generic macro accepts id and value attributes. The value of id is what will appear in the request when the user tries to save the question. This should be validated and saved in validateCustomSettings and saveCustomSettings. Any macros with a visible parameter can be used after a toggle switch to create fields that need to be completed when the toggle is enabled.