Variants 

Basics 

A variant is an "alternative" form definition section that allows you to change properties of form elements, validators, and finishers. Variants are activated by conditions. This allows you to:

  • translate form element values depending on the frontend language
  • set and remove validators from one form element depending on the value of another form element
  • hide entire steps (form pages) depending on the value of a form element
  • set finisher options depending on the value of a form element
  • hide a form element in particular finishers and on the summary step

Form element variants can be defined statically in form definitions or created programmatically through an API. The variants defined in a form definition are applied to a form based on their conditions at runtime. Programmatically defined variants can be applied at any time.

Variant conditions can be evaluated programmatically at any time. However, some conditions are only available at runtime, for example, checking a form element value.

Custom conditions and operators can be easily added.

Only the form element properties listed in a variant are applied to the form element, all other properties are retained. An exception to this rule are finishers and validators. If finishers or validators are not defined in a variant, the original finishers and validators will be used. If at least one finisher or validator is defined in a variant, the original finishers and validators are overwritten by the finishers and validators in the variant.

Variants defined in a form definition are all processed and applied in the order of their matching conditions. This means if variant 1 sets the label of a form element to "X" and variant 2 sets the label to "Y", then variant 2 is applied, i.e. the label will be "Y".

Rendering option enabled 

The rendering option enabled is available for all finishers and form elements except the root form element and the first form page. The option accepts a boolean value ( true or false).

Setting a form element to enabled: true renders it in the frontend and enables processing of its values, including property mapping and validation. Setting enabled: false disables it in the frontend. All form elements and finishers except the root form element and the first form page can be enabled or disabled.

Setting a finisher to enabled: true executes it when the form is submitted. Setting enabled: false skips the finisher.

By default, enabled is set to true.

See examples below to learn more.

Definition of variants 

Variants are defined at the form element level in YAML. Here is an example of a text form element variant:

type: Text
identifier: text-1
label: Foo
variants:
  -
    identifier: variant-1
    condition: 'traverse(formValues, "checkbox-1") == 1'
    # If the condition matches, the label property of the form
    # element is set to the value 'Bar'
    label: Bar
Copied!

The identifier must be unique at the form element level.

Each variant has a single condition which applies the variant if the condition is satisfied. The properties in the variant are applied to the form element. In the example above the label of text-1 is changed to Bar if the checkbox checkbox-1 is checked.

The following properties can be overwritten by Form (the topmost element) variants:

  • label
  • renderingOptions
  • finishers
  • rendererClassName

The following properties can be overwritten by all other form element variants:

  • enabled
  • label
  • defaultValue
  • properties
  • renderingOptions
  • validators

Conditions 

The form framework uses the Symfony component expression language for conditions. An expression is a one-liner that returns a boolean value, for example, applicationContext matches "#Production/Local#". For further information see the Symfony docs. The form framework extends the expression language with variables to access form values and environment settings.

formRuntime (object) 

You can access every public method of \TYPO3\CMS\Form\Domain\Runtime\FormRuntime . Learn more here.

For example:

formRuntime.getIdentifier() == "test".

renderable (VariableRenderableInterface) 

renderable contains the instance of renderable that the condition is applied to. This can be used e.g. to access the identifier of the current renderable without having to duplicate it.

For example:

traverse(formValues, renderable.getIdentifier()) == "special value".

formValues (array) 

formValues holds all the submitted form element values. Each key in the array represents a form element identifier.

For example:

traverse(formValues, "text-1") == "yes".

stepIdentifier (string) 

stepIdentifier is set to the identifier of the current step.

For example:

stepIdentifier == "page-1".

stepType (string) 

stepType is set to the type of the current step.

For example:

stepType == "SummaryPage".

finisherIdentifier (string) 

finisherIdentifier is set to the identifier of the current finisher or an empty string (if no finishers are executed).

For example:

finisherIdentifier == "EmailToSender".

site (object) 

You can access every public method in \TYPO3\CMS\Core\Site\Entity\Site . The following are the most important ones:

  • getSettings() / The site settings array
  • getDefaultLanguage() / The default language object for the current site
  • getConfiguration() / The whole configuration of the current site
  • getIdentifier() / The identifier of the current site
  • getBase() / The base URL of the current site
  • getRootPageId() / The ID of the root page of the current site
  • getLanguages() / An array of available languages for the current site
  • getSets() / Configured site sets of a site (new in TYPO3 v13+)

For example:

site("settings").get("myVariable") == "something". site("rootPageId") == "42".

More details on the Site object can be found in Using site configuration in conditions.

siteLanguage (object) 

You can access every public method in \TYPO3\CMS\Core\Site\Entity\SiteLanguage . The most important ones are:

  • getLanguageId() / The sys_language_uid.
  • getLocale() / The language locale, for example 'en_US.UTF-8'.
  • getTypo3Language() / The language key for XLF files, for example, 'de' or 'default'.
  • getTwoLetterIsoCode() / Returns the ISO-639-1 language ISO code, for example, 'de'.

For example:

siteLanguage("locale").getName() == "de-DE".

applicationContext (string) 

applicationContext is set to the application context (@see GeneralUtility::getApplicationContext()).

For example:

applicationContext matches "#Production/Local#".

contentObject (array) 

contentObject contains the data of the current content object or an empty array if no content object is available.

For example:

contentObject["pid"] in [23, 42].

Working with variants programmatically 

Create a variant with conditions through the PHP API:

/** @var TYPO3\CMS\Form\Domain\Model\Renderable\RenderableVariantInterface $variant */
$variant = $formElement->createVariant([
    'identifier' => 'variant-1',
    'condition' => 'traverse(formValues, "checkbox-1") == 1',
    'label' => 'foo',
]);

Copied!

Get all the variants of a form element:

/** @var TYPO3\CMS\Form\Domain\Model\Renderable\RenderableVariantInterface[] $variants */
$variants = $formElement->getVariants();

Copied!

Apply a variant to a form element regardless of its conditions:

$formElement->applyVariant($variant);

Copied!

Examples 

Here are some more complex examples to show you what is possible with the form framework.

Translation of form elements 

In this example, form, page and text elements have variants so that they are translated differently depending on the frontend language (whether it is German or English).

type: Form
prototypeName: standard
identifier: contact-form
label: Kontaktformular
renderingOptions:
  submitButtonLabel: Senden
variants:
  -
    identifier: language-variant-1
    condition: 'siteLanguage("locale").getName() == "en-US"'
    label: Contact form
    renderingOptions:
      submitButtonLabel: Submit
renderables:
  -
    type: Page
    identifier: page-1
    label: Kontaktdaten
    renderingOptions:
      previousButtonLabel: zurück
      nextButtonLabel: weiter
    variants:
      -
        identifier: language-variant-1
        condition: 'siteLanguage("locale").getName() == "en-US"'
        label: Contact data
        renderingOptions:
          previousButtonLabel: Previous step
          nextButtonLabel: Next step
    renderables:
      -
        type: Text
        identifier: text-1
        label: Vollständiger Name
        properties:
          fluidAdditionalAttributes:
            placeholder: Ihre vollständiger Name
        variants:
          -
            identifier: language-variant-1
            condition: 'siteLanguage("locale").getName() == "en-US"'
            label: Full name
            properties:
              fluidAdditionalAttributes:
                placeholder: Your full name
Copied!

Adding validators dynamically 

In this example, the email-address field has a variant that adds validators if checkbox-1 is checked.

type: Form
prototypeName: standard
identifier: newsletter-subscription
label: Newsletter Subscription
renderables:
  -
    type: Page
    identifier: page-1
    label: General data
    renderables:
      -
        type: Text
        identifier: email-address
        label: Email address
        defaultValue:
        variants:
          -
            identifier: validation-1
            condition: 'traverse(formValues, "checkbox-1") == 1'
            properties:
              fluidAdditionalAttributes:
                required: required
            validators:
              -
                identifier: NotEmpty
              -
                identifier: EmailAddress
      -
        type: Checkbox
        identifier: checkbox-1
        label: Check this and email will be mandatory
Copied!

Hide form elements 

In this example, the form element email-address has been enabled explicitly but this can be left out as this is the default state. The form element text-3 has been disabled to (temporarily) remove it from the form. The field text-1 has a variant that hides it in all finishers and on the summary step. The EmailToSender finisher contains form values ( email-address and name). The EmailToSender finisher is only enabled if checkbox-1 has been checked by the user, otherwise it is skipped.

type: Form
prototypeName: standard
identifier: hidden-field-form
label: Hidden field form
finishers:
  -
    identifier: EmailToReceiver
    options:
      subject: Yes, I am ready
      recipients:
        your.company@example.com: 'Your Company name'
      senderAddress: tritum@example.org
      senderName: tritum@example.org
  -
    identifier: EmailToSender
    options:
      subject: This is a copy of the form data
      recipients:
        {email-address}: '{name}'
      senderAddress: tritum@example.org
      senderName: tritum@example.org
      renderingOptions:
        enabled: '{checkbox-1}'
renderables:
  -
    type: Page
    identifier: page-1
    label: General data
    renderables:
      -
        type: Text
        identifier: text-1
        label: A field hidden on confirmation step and in all mails (finishers)
        variants:
          -
            identifier: hide-1
            renderingOptions:
              enabled: false
            condition: 'stepType == "SummaryPage" || finisherIdentifier in ["EmailToSender", "EmailToReceiver"]'
      -
        type: Text
        identifier: email-address
        label: Email address
        properties:
          fluidAdditionalAttributes:
            required: required
        renderingOptions:
          enabled: true
      -
        type: Text
        identifier: text-3
        label: A temporarily disabled field
        renderingOptions:
          enabled: false
      -
        type: Checkbox
        identifier: checkbox-1
        label: Check this and the sender gets an email
  -
    type: SummaryPage
    identifier: summarypage-1
    label: Confirmation
Copied!

Hide steps 

In this example, the second step ( page-2) has a variant that disables it if checkbox-1 is checked. checkbox-1 has a variant which disables it on the summary step.

type: Form
prototypeName: standard
identifier: multi-step-form
label: Muli step form
renderables:
  -
    type: Page
    identifier: page-1
    label: First step
    renderables:
      -
        type: Text
        identifier: text-1
        label: A field
      -
        type: Checkbox
        identifier: checkbox-1
        label: Check this and the next step will be skipped
        variants:
          -
            identifier: variant-1
            condition: 'stepType == "SummaryPage"'
            renderingOptions:
              enabled: false
  -
    type: Page
    identifier: page-2
    label: Second step
    variants:
      -
        identifier: variant-2
        condition: 'traverse(formValues, "checkbox-1") == 1'
        renderingOptions:
          enabled: false
    renderables:
      -
        type: Text
        identifier: text-2
        label: Another field
  -
    type: SummaryPage
    identifier: summarypage-1
    label: Confirmation
Copied!

Set finisher values dynamically 

In this example, the form has a variant so that the finisher has different values depending on the application context.

type: Form
prototypeName: standard
identifier: finisher-condition-example
label: Finishers under condition
finishers:
  -
    identifier: Confirmation
    options:
      message: I am NOT a local environment.
variants:
  -
    identifier: variant-1
    condition: 'applicationContext matches "#Production/Local#"'
    finishers:
      -
        identifier: Confirmation
        options:
          message: I am a local environment.
renderables:
  -
    type: Page
    identifier: page-1
    label: General data
    renderables:
      -
        type: Text
        identifier: text-1
        label: A field
Copied!

Remove select options 

In this example, a select form element has a variant which removes an option for a specific locale.

type: Form
prototypeName: standard
identifier: option-remove-example
label: Options removed under condition
renderables:
  -
    type: Page
    identifier: page-1
    label: Step
    renderables:
      -
        identifier: salutation
        type: SingleSelect
        label: Salutation
        properties:
          options:
            '': '---'
            mr: Mr.
            mrs: Mrs.
            miss: Miss
        defaultValue: ''
        variants:
          -
            identifier: salutation-variant
            condition: 'siteLanguage("locale").getName() == "zh-CN"'
            properties:
              options:
                miss: __UNSET
Copied!

Adding your own expression language providers 

You can extend the expression language with your own custom functions. For more information see the official docs and the appropriate TYPO3 implementation details.

Register your own expression language provider class in Configuration/ExpressionLanguage.php and create it, making sure it implements \Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface.

EXT:some_extension/Configuration/ExpressionLanguage.php
return [
    'form' => [
        Vendor\MyExtension\ExpressionLanguage\CustomExpressionLanguageProvider::class,
    ],
];
Copied!

Adding your own expression language variables 

You can extend the expression language with your own variables. These variables can be used in conditions.

Register your own expression language provider class in Configuration/ExpressionLanguage.php as above and and create it as follows:

EXT:some_extension/Classes/ExpressionLanguage/CustomExpressionLanguageProvider.php
class CustomExpressionLanguageProvider extends AbstractProvider
{
    public function __construct()
    {
        $this->expressionLanguageVariables = [
            'variableA' => 'valueB',
        ];
    }
}
Copied!