Variants

Basics

Variants allow you to change properties of form elements, validators, and finishers and are activated by conditions. This allows you among other things:

  • translating form element values depending on the frontend language
  • setting and removing validators of one form element depending on the value of another form element
  • hiding entire steps (form pages) depending on the value of a form element
  • setting finisher options depending on the value of a form element
  • hiding a form element in certain finishers and on the summary step

Variants are defined on the form element level either statically in form definitions or created programmatically through an API. The variants defined within a form definition are automatically applied to the form based on their conditions at runtime. Programmatically, variants can be applied at any time.

Furthermore, conditions of a variant can be evaluated programmatically at any time. However, some conditions are only available at runtime, for example a check for a form element value.

Custom conditions and operators can be added easily.

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 within a variant, the original finishers and validators will be used. If at least one finisher or validator is defined in a variant, the originally defined finishers or validators are overwritten by the list of finishers and validators of the variant.

Variants defined within 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 all form elements - except the root form element and the first form page. The option accepts a boolean value (true or false).

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

Setting enabled: true for a finisher executes it when submitting forms. Setting enabled: false skips the finisher.

By default, enabled is set to true.

See examples below to learn more about using this rendering option.

Definition of variants

Variants are defined on the form element level. Check the following - incomplete - example:

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!

As usual, identifier must be a unique name of the variant on the form element level.

Each variant has a single condition which applies the variants' changes as soon as the condition matches. In addition, the remaining properties are applied to the form element as well. In the aforementioned example the label of the form element text-1 is changed to Bar if the checkbox checkbox-1 is checked.

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

  • label
  • renderingOptions
  • finishers
  • rendererClassName

The following properties can be overwritten by variants within all of the other form elements:

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

Conditions

The form framework uses the Symfony component expression language. Here, an expression is a one-liner that returns a boolean value like applicationContext matches "#Production/Local#". Please check the Symfony docs to learn more about this topic. The form framework extends the expression language with some variables which can be used to access form values and environment settings.

formRuntime (object)

You can access every public method from the \TYPO3\CMS\Form\Domain\Runtime\FormRuntime, learn more here.

For example:

formRuntime.getIdentifier() == "test".

renderable (VariableRenderableInterface)

renderable holds the instance of renderable, 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 within this 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 (while no finishers are executed).

For example:

finisherIdentifier == "EmailToSender".

siteLanguage (object)

You can access every public method from \TYPO3\CMS\Core\Site\Entity\SiteLanguage. The most needed ones are for sure:

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

For example:

siteLanguage("locale") == "de_DE".

applicationContext (string)

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

For example:

applicationContext matches "#Production/Local#".

contentObject (array)

contentObject is set to the data of the current content object or to 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 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 defined conditions:

$formElement->applyVariant($variant);

Copied!

Examples

Here are some complex examples to show you the possibilities of the form framework.

Translation of form elements

In this example form elements are translated differently depending on the frontend language.

type: Form
prototypeName: standard
identifier: contact-form
label: Kontaktformular
renderingOptions:
  submitButtonLabel: Senden
variants:
  -
    identifier: language-variant-1
    condition: 'siteLanguage("locale") == "en_US.UTF-8"'
    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") == "en_US.UTF-8"'
        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") == "en_US.UTF-8"'
            label: Full name
            properties:
              fluidAdditionalAttributes:
                placeholder: Your full name
Copied!

Adding validators dynamically

In this example a bunch of validators are added to the field email-address depending on the value of the form element checkbox-1.

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 extensive example the form element email-address has been enabled explicitly but it is fine to leave this out since this is the default state. The form element text-3 has been disabled completely, for example to temporarily remove it from the form. The field text-1 is hidden in all finishers and on the summary step. The EmailToSender finisher takes the fact into account that finishers can refer to form values. It is only enabled if the form element checkbox-1 has been activated by the user. Otherwise, the finisher 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 is disabled if the field checkbox-1 is checked. Furthermore, the form element checkbox-1 is disabled 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 finisher values are set differently 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 option is removed 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") == "zh_CN.utf-8"'
            properties:
              options:
                miss: __UNSET
Copied!

Adding own expression language providers

If you need to extend the expression language with custom functions you can extend it. For more information check the official docs and the appropriate TYPO3 implementation details.

Register the expression language provider in the extension file Configuration/ExpressionLanguage.php. Make sure your expression language provider implements Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface.

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

Adding own expression language variables

If you need to add custom variables to the expression language you can extend it. Then the variables are ready to be checked in conditions.

Register a custom expression language provider as written above and provide the expression language variables:

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