[FEATURE] Introduce conditional variants for form elements
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Documentation / Changelog / master / Feature-84133-IntroduceVariants.rst
1 .. include:: ../../Includes.txt
2
3 ====================================
4 Feature: #84133 - Introduce variants
5 ====================================
6
7 See :issue:`84133`
8
9 Description
10 ===========
11
12
13 Short Description
14 -----------------
15
16 Variants allow you to change properties of a form element and can be activated based on conditions.
17
18 This makes it possible to manipulate form element properties, validator options, and finisher options based on conditions.
19
20 This allows you among other things:
21
22 * translate form element values depending on the frontend language
23 * set and remove validators of one form element depending on the value of another form element
24 * hide entire steps (form pages) depending on the value of a form element
25 * set finisher options depending on the value of a form element
26 * hiding a form element in certain finishers and on the summary step
27
28 This feature implements variants for frontend rendering and the ability to define variants in form definitions.
29 The implementation to define variants graphically in the form editor is out of scope of this patchset.
30
31
32 Basics
33 ------
34
35 Variants allow you to change properties of form elements, validators, and finishers and are activated by conditions.
36 They are defined on the form element level either statically in form definitions or created programmatically through an API.
37
38 The variants defined within a form definition are automatically applied to the form based on their conditions at runtime.
39 Programmatically, variants can be applied at any time.
40
41 Furthermore, the conditions of a variant can be evaluated programmatically at any time. However, some conditions are only
42 available at runtime, for example a check for a form element value.
43
44 Custom conditions and operators can be added easily.
45
46 Only the form element properties listed in a variant are applied to the form element, all other properties are retained.
47 An exception to this rule are finishers and validators. If finishers or validators are **not** defined within a variant, the
48 original finishers and validators will be used. If at least one finisher or validator is defined in a variant, the
49 originally defined finishers or validators are overwritten by the list of finishers and validators of the variant.
50
51 Variants defined within a form definition are **all** processed and applied in the order of their condition matches. This means
52 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
53 will be "Y".
54
55
56 Variants definition
57 -------------------
58
59 Variants are defined on the form element level. Check the following - incomplete - example:
60
61 .. code-block:: yaml
62
63     type: Text
64     identifier: text-1
65     label: ''
66     variants:
67       -
68         identifier: variant-1
69         condition: 'formValues["checkbox-1"] == 1'
70         # If the condition matches, the label property of the form element is set to the value 'foo'
71         label: foo
72
73
74 As usual :yaml:`identifier` must be a unique name of the variant on the form element level.
75
76 Each variant has a single :yaml:`condition` which lets the variants' changes get applied if it matches.
77
78 If the :yaml:`condition` of a variant matches, the remaining properties are applied to the form element. In the
79 aforementioned example the label of the form element :yaml:`text-1` is changed to ``foo`` if the checkbox
80 :yaml:`checkbox-1` is checked.
81
82 The following properties can be overwritten by variants within the topmost element (:yaml:`Form`):
83
84 * :yaml:`label`
85 * :yaml:`renderingOptions`
86 * :yaml:`finishers`
87 * :yaml:`rendererClassName`
88
89 The following properties can be overwritten by variants within all of the other form elements:
90
91 * :yaml:`enabled`
92 * :yaml:`label`
93 * :yaml:`defaultValue`
94 * :yaml:`properties`
95 * :yaml:`renderingOptions`
96 * :yaml:`validators`
97
98
99 Conditions
100 ----------
101
102 The form framework uses the Symfony component `expression language` to match the conditions. (@see https://symfony.com/doc/4.1/components/expression_language.html)
103 An expression is a one-liner that returns a boolean value like :yaml:`applicationContext matches "#Production/Local#"`.
104 Please read https://symfony.com/doc/4.1/components/expression_language/syntax.html to learn more about this topic.
105 The form framework extends the expression language with some variables which can be used to access form values and environment settings.
106
107
108 ``formRuntime`` (object)
109 ^^^^^^^^^^^^^^^^^^^^^^^^
110
111 You can access every public method from the :php:`\TYPO3\CMS\Form\Domain\Runtime\FormRuntime` (@see https://docs.typo3.org/typo3cms/extensions/form/ApiReference/Index.html#typo3-cms-form-domain-model-formruntime).
112
113 Example
114 '''''''
115
116 :yaml:`formRuntime.getIdentifier() == "test"`.
117
118
119 ``formValues`` (array)
120 ^^^^^^^^^^^^^^^^^^^^^^
121
122 :yaml:`formValues` holds all of the submitted form element values. Each key within this array represents a form element identifier.
123
124 Example
125 '''''''
126
127 :yaml:`formValues["text-1"] == "yes"`.
128
129
130 ``stepIdentifier`` (string)
131 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
132
133 :yaml:`stepIdentifier` is set to the :yaml:`identifier` of the current step.
134
135 Example
136 '''''''
137
138 :yaml:`stepIdentifier == "page-1"`.
139
140
141 ``stepType`` (string)
142 ^^^^^^^^^^^^^^^^^^^^^
143
144 :yaml:`stepType` is set to the :yaml:`type` of the current step.
145
146 Example
147 '''''''
148
149 :yaml:`stepType == "SummaryPage"`.
150
151
152 ``finisherIdentifier`` (string)
153 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
154
155 :yaml:`finisherIdentifier` is set to the :yaml:`identifier` of the current finisher or an empty string (while no finishers are executed).
156
157 Example
158 '''''''
159
160 :yaml:`finisherIdentifier == "EmailToSender"`.
161
162
163 ``siteLanguage`` (object)
164 ^^^^^^^^^^^^^^^^^^^^^^^^^
165
166 You can access every public method from :php:`\TYPO3\CMS\Core\Site\Entity\SiteLanguage`.
167 The most needed ones are probably:
168
169 * getLanguageId() / Aka sys_language_uid.
170 * getLocale() / The language locale. Something like 'en_US.UTF-8'.
171 * getTypo3Language() / The language key for XLF files. Something like 'de' or 'default'.
172 * getTwoLetterIsoCode() / Returns the ISO-639-1 language ISO code. Something like 'de'.
173
174 Example
175 '''''''
176
177 :yaml:`siteLanguage.getLocale() == "de_DE"`.
178
179
180 ``applicationContext`` (string)
181 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
182
183 :yaml:`applicationContext` is set to the application context (@see GeneralUtility::getApplicationContext()).
184
185 Example
186 '''''''
187
188 :yaml:`applicationContext matches "#Production/Local#"`.
189
190
191 ``contentObject`` (array)
192 ^^^^^^^^^^^^^^^^^^^^^^^^^
193
194 :yaml:`contentObject` is set to the data of the current content object or to an empty array if no content object is available.
195
196 Example
197 '''''''
198
199 :yaml:`contentObject["pid"] in [23, 42]`.
200
201
202 Working with variants programmatically
203 --------------------------------------
204
205 Create a variant with conditions through the PHP API:
206
207 .. code-block:: php
208
209     /** @var TYPO3\CMS\Form\Domain\Model\Renderable\RenderableVariantInterface $variant */
210     $variant = $formElement->createVariant([
211         'identifier' => 'variant-1',
212         'condition' => 'formValues["checkbox-1"] == 1',
213         'label' => 'foo',
214     ]);
215
216 Get all variants of a form element:
217
218 .. code-block:: php
219
220     /** @var TYPO3\CMS\Form\Domain\Model\Renderable\RenderableVariantInterface[] $variants */
221     $variants = $formElement->getVariants();
222
223 Apply a variant to a form element regardless of its defined conditions:
224
225 .. code-block:: php
226
227     $formElement->applyVariant($variant);
228
229
230 Examples
231 --------
232
233 Translate form element values depending on the frontend language:
234
235 .. code-block:: yaml
236
237     type: Form
238     identifier: test
239     prototypeName: standard
240     label: DE
241     renderingOptions:
242       submitButtonLabel: Abschicken
243     variants:
244       -
245         identifier: language-variant-1
246         condition: 'siteLanguage.getLocale() == "en_US.UTF-8"'
247         label: EN
248         renderingOptions:
249           submitButtonLabel: Submit
250     renderables:
251       -
252         type: Page
253         identifier: page-1
254         label: DE
255         renderingOptions:
256           previousButtonLabel: 'zurück'
257           nextButtonLabel: 'weiter'
258         variants:
259           -
260             identifier: language-variant-1
261             condition: 'siteLanguage.getLocale() == "en_US.UTF-8"'
262             label: EN
263             renderingOptions:
264               previousButtonLabel: 'Previous step'
265               nextButtonLabel: 'Next step'
266         renderables:
267           -
268             type: Text
269             identifier: text-1
270             label: DE
271             properties:
272               fluidAdditionalAttributes:
273                 placeholder: Platzhalter
274             variants:
275               -
276                 identifier: language-variant-1
277                 condition: 'siteLanguage.getLocale() == "en_US.UTF-8"'
278                 label: EN
279                 properties:
280                   fluidAdditionalAttributes:
281                     placeholder: Placeholder
282
283 Set validators of one form element depending on the value of another form element:
284
285 .. code-block:: yaml
286
287
288     type: Form
289     identifier: test
290     label: test
291     prototypeName: standard
292     renderables:
293       -
294         type: Page
295         identifier: page-1
296         label: Step
297         renderables:
298           -
299             defaultValue: ''
300             type: Text
301             identifier: text-1
302             label: 'Email address'
303             variants:
304               -
305                 identifier: variant-1
306                 condition: 'formValues["checkbox-1"] == 1'
307                 properties:
308                   fluidAdditionalAttributes:
309                     required: 'required'
310                 validators:
311                   -
312                     identifier: NotEmpty
313                   -
314                     identifier: EmailAddress
315           -
316             type: Checkbox
317             identifier: checkbox-1
318             label: 'Subscribe to newsletter'
319
320 Hide entire steps depending on the value of a form element:
321
322 .. code-block:: yaml
323
324     type: Form
325     identifier: test
326     prototypeName: standard
327     label: Test
328     renderables:
329       -
330         type: Page
331         identifier: page-1
332         label: 'Page 1'
333         renderables:
334           -
335             type: Text
336             identifier: text-1
337             label: 'Text 1'
338           -
339             type: Checkbox
340             identifier: checkbox-1
341             label: 'Skip page 2'
342             variants:
343               -
344                 identifier: hide-1
345                 condition: 'stepType == "SummaryPage"'
346                 renderingOptions:
347                   enabled: false
348       -
349         type: Page
350         identifier: page-2
351         label: 'Page 2'
352         variants:
353           -
354             identifier: variant-1
355             condition: 'formValues["checkbox-1"] == 1'
356             renderingOptions:
357               enabled: false
358         renderables:
359           -
360             type: Text
361             identifier: text-2
362             label: 'Text 2'
363       -
364         type: SummaryPage
365         identifier: summarypage-1
366         label: 'Summary step'
367
368 Set finisher values depending on the application context:
369
370 .. code-block:: yaml
371
372     type: Form
373     identifier: test
374     prototypeName: standard
375     label: Test
376     renderingOptions:
377       submitButtonLabel: Submit
378     finishers:
379       -
380         identifier: Confirmation
381         options:
382           message: 'Thank you'
383     variants:
384       -
385         identifier: variant-1
386         condition: 'applicationContext matches "#Production/Local#"'
387         finishers:
388           -
389             identifier: Confirmation
390             options:
391               message: 'ouy knahT'
392     renderables:
393       -
394         type: Page
395         identifier: page-1
396         label: 'Page 1'
397         renderingOptions:
398           previousButtonLabel: 'Previous step'
399           nextButtonLabel: 'Next step'
400
401 Hide a form element in certain finishers and on the summary step:
402
403 .. code-block:: yaml
404
405     type: Form
406     identifier: test
407     prototypeName: standard
408     label: Test
409     finishers:
410       -
411         identifier: EmailToReceiver
412         options:
413           subject: Testmail
414           recipientAddress: tritum@example.org
415           recipientName: 'Test'
416           senderAddress: tritum@example.org
417           senderName: tritum@example.org
418     renderables:
419       -
420         type: Page
421         identifier: page-1
422         label: 'Page 1'
423         renderables:
424           -
425             type: Text
426             identifier: text-1
427             label: 'Text 1'
428             variants:
429               -
430                 identifier: hide-1
431                 renderingOptions:
432                   enabled: false
433                 condition: 'stepType == "SummaryPage" || finisherIdentifier in ["EmailToSender", "EmailToReceiver"]'
434           -
435             type: Text
436             identifier: text-2
437             label: 'Text 2'
438       -
439         type: SummaryPage
440         identifier: summarypage-1
441         label: 'Summary step'
442
443
444 Adding own expression language provider
445 ---------------------------------------
446
447 If you need to extend the expression language with custom functions you can extend it. For more
448 information @see https://symfony.com/doc/4.1/components/expression_language/extending.html#using-expression-providers.
449
450 First of all, you have to register the expression language provider within the form setup:
451
452 .. code-block:: yaml
453
454     TYPO3:
455       CMS:
456         Form:
457           prototypes:
458             standard:
459               conditionContextDefinition:
460                 expressionLanguageProvider:
461                   MyCustomExpressionLanguageProvider:
462                     implementationClassName: '\Vendor\MyExtension\CustomExpressionLanguageProvider'
463
464 Your expression language provider must implement :php`Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface`.
465
466
467 Adding own expression language variables
468 ----------------------------------------
469
470 If you need to add custom variables to the expression language you can extend it.
471 Then the variables are ready to be checked in conditions.
472
473 First of all, you have to register the variable provider within the form setup:
474
475 .. code-block:: yaml
476
477     TYPO3:
478       CMS:
479         Form:
480           prototypes:
481             standard:
482               conditionContextDefinition:
483                 expressionLanguageVariableProvider:
484                   MyCustomExpressionLanguageVariableProvider:
485                     implementationClassName: '\Vendor\MyExtension\CustomExpressionLanguageVariableProvider'
486
487 Your expression language variable provider must implement :php`TYPO3\CMS\Form\Domain\Condition\ExpressionLanguageVariableProviderInterface`.
488
489
490 .. index:: Frontend, ext:form, NotScanned