first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
No files matched your search
@@ -0,0 +1,450 @@
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _concepts-configuration:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
|
||||
.. _concepts-configuration-whysomuchconfiguration:
|
||||
|
||||
A lot of configuration. Why?
|
||||
----------------------------
|
||||
|
||||
The requirements for building forms in a declarative and programmatic way
|
||||
are complex. What we have learned so far is that the program code must be
|
||||
kept as generic as possible to handle the dynamics of forms, but a generic
|
||||
program code means a lot of configurative overhead.
|
||||
|
||||
Initially, the configuration may overwhelm you, but it also has some great
|
||||
advantages. Many aspects of EXT:form can be manipulated in a purely
|
||||
configurative manner without involving a developer.
|
||||
|
||||
Furthermore, we wanted to avoid the configuration being done at places
|
||||
whose context actually suggests something different. This pedantry,
|
||||
however, leads to the situation in which certain settings have to be
|
||||
defined multiple times at multiple places. This may seem nonsensical, but
|
||||
it avoids unpredictable behaviour. Within the form framework, nothing
|
||||
happens magically. It is all about configuration.
|
||||
|
||||
|
||||
.. _concepts-configuration-whyyaml:
|
||||
|
||||
Why YAML?
|
||||
---------
|
||||
|
||||
Former versions of EXT:form used a subset of TypoScript to describe the
|
||||
definition of a specific form and the behaviour of the included form
|
||||
elements. This led to a lot of confusion from integrators because the
|
||||
implemented definition language looked like TypoScript but did not behave
|
||||
like TypoScript.
|
||||
|
||||
Since the definition of forms and form elements must be declarative, the
|
||||
EXT:form team decided to use YAML. Just through the visual appearance of
|
||||
YAML, it should be clear to everyone that neither magic nor TypoScript
|
||||
stdWrap functionality are possible.
|
||||
|
||||
|
||||
.. _concepts-configuration-yamlregistration:
|
||||
|
||||
YAML registration
|
||||
-----------------
|
||||
|
||||
At the moment, configuration via YAML is not natively integrated into the
|
||||
core of TYPO3. You have to make a short detour by using TypoScript in order
|
||||
to register your YAML configuration. Furthermore, there is a "speciality"
|
||||
regarding the integration of your YAML configuration for the backend
|
||||
module.
|
||||
|
||||
.. hint::
|
||||
|
||||
We recommend using a `site package <https://de.slideshare.net/benjaminkott/typo3-the-anatomy-of-sitepackages>`_.
|
||||
This will make your life easier if you want to customize EXT:form
|
||||
heavily in order to suit the customer's needs.
|
||||
|
||||
|
||||
.. _concepts-configuration-yamlregistration-frontend:
|
||||
|
||||
YAML registration for the frontend
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
EXT:form registers two YAML configuration files which are required for the
|
||||
frontend.
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
plugin.tx_form {
|
||||
settings {
|
||||
yamlConfigurations {
|
||||
10 = EXT:form/Configuration/Yaml/BaseSetup.yaml
|
||||
20 = EXT:form/Configuration/Yaml/FormEngineSetup.yaml
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Since the keys 10 and 20 are already taken, we recommend registering your
|
||||
own configuration beginning with the key ``100``.
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
plugin.tx_form {
|
||||
settings {
|
||||
yamlConfigurations {
|
||||
100 = EXT:my_site_package/Configuration/Form/CustomFormSetup.yaml
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. _concepts-configuration-yamlregistration-backend:
|
||||
|
||||
YAML registration for the backend
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
EXT:form registers three YAML configuration files which are required for
|
||||
the backend.
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
module.tx_form {
|
||||
settings {
|
||||
yamlConfigurations {
|
||||
10 = EXT:form/Configuration/Yaml/BaseSetup.yaml
|
||||
20 = EXT:form/Configuration/Yaml/FormEditorSetup.yaml
|
||||
30 = EXT:form/Configuration/Yaml/FormEngineSetup.yaml
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Since the keys 10, 20, and 30 are already taken, we recommend registering
|
||||
your own configuration beginning with the key ``100``.
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
module.tx_form {
|
||||
settings {
|
||||
yamlConfigurations {
|
||||
100 = EXT:my_site_package/Configuration/Form/CustomFormSetup.yaml
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. important::
|
||||
Consider the following methods to register TypoScript for the backend.
|
||||
|
||||
The backend module of EXT:form is based on Extbase. Such backend modules
|
||||
can, like frontend plugins, be configured via TypoScript. The frontend
|
||||
plugins are configured below ``plugin.tx_[pluginkey]``. For the
|
||||
configuration of the backend ``module.tx_[pluginkey]`` is used.
|
||||
|
||||
There are different ways to include the TypoScript configuration for the
|
||||
backend:
|
||||
|
||||
- a) use the API function ``\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptSetup()``,
|
||||
- b) add the configuration to your existing TypoScript template.
|
||||
|
||||
In both cases, the ``form editor`` will work as expected regardless the
|
||||
chosen page from the page tree. If using the aforementioned method b, the
|
||||
configuration would only be valid on a specific page tree, unless you add
|
||||
your configuration to all trees within your installation. Nevertheless,
|
||||
being on the root page (uid 0) would still be a problem.
|
||||
|
||||
To sum it up: choose either method a or b, and you will be fine.
|
||||
|
||||
.. _concepts-configuration-yamlregistration-backend-addtyposcriptsetup:
|
||||
|
||||
YAML registration for the backend via addTypoScriptSetup()
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Add the following PHP code to your :file:`ext_localconf.php` of your site
|
||||
package::
|
||||
|
||||
defined('TYPO3_MODE') or die();
|
||||
|
||||
call_user_func(function () {
|
||||
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptSetup(
|
||||
trim('
|
||||
module.tx_form {
|
||||
settings {
|
||||
yamlConfigurations {
|
||||
100 = EXT:my_site_package/Configuration/Form/CustomFormSetup.yaml
|
||||
}
|
||||
}
|
||||
}
|
||||
')
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
.. _concepts-configuration-configurationaspects:
|
||||
|
||||
Configuration aspects
|
||||
---------------------
|
||||
|
||||
In EXT:form, four aspects can be configured:
|
||||
|
||||
- the behaviour of the frontend rendering,
|
||||
- the behaviour of the ``form editor``,
|
||||
- the behaviour of the ``form manager``, and
|
||||
- the behaviour of the ``form plugin``.
|
||||
|
||||
Those aspects are defined in separate files which are only loaded in the
|
||||
frontend/ backend when needed. This approach has two advantages:
|
||||
|
||||
- increased clarity,
|
||||
- increased performance, e.g. the ``form editor`` configuration is not
|
||||
needed in the frontend and therefore not loaded.
|
||||
|
||||
It is up to you if you want to follow this guideline or if you want to put
|
||||
the whole configuration into one large file.
|
||||
|
||||
There are some configurational aspects which cannot explicitly be assigned
|
||||
to either the frontend or the backend. Instead, the configuration is
|
||||
valid for both areas. For example, within the backend, the whole frontend
|
||||
configuration is required in order to allow the form preview to work
|
||||
properly. In addition, as soon as the form is rendered via the ``form
|
||||
plugin``, the ``FormEngine`` configuration is needed to interpret the
|
||||
overridden finisher configuration correctly.
|
||||
|
||||
|
||||
.. _concepts-configuration-inheritances:
|
||||
|
||||
Inheritances
|
||||
------------
|
||||
|
||||
The final YAML configuration is not based on one huge file. Instead, it is
|
||||
a compilation of a sequential process:
|
||||
|
||||
- First of all, all registered configuration files are parsed as YAML and
|
||||
are overlaid according to their order.
|
||||
- After that, the ``__inheritances`` operator is applied. It is a unique
|
||||
operator introduced by the form framework.
|
||||
- Finally, all configuration entries with a value of ``null`` are deleted.
|
||||
|
||||
Additionally, the frontend configuration can be extended/ overridden by
|
||||
TypoScript:
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
plugin.tx_form {
|
||||
settings {
|
||||
yamlSettingsOverrides {
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
Your TypoScript overrides are not interpreted by the ``form editor``,
|
||||
i.e. those settings are ignored.
|
||||
|
||||
.. note::
|
||||
|
||||
The described process is quite handy for you. As soon as you are working
|
||||
with your :ref:`own configuration files <concepts-configuration-yamlregistration>`,
|
||||
you only have to define the differences compared to the previously
|
||||
loaded configuration files.
|
||||
|
||||
For example, if you want to override the fluid templates and you therefore
|
||||
register an additional configuration file via
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
plugin.tx_form {
|
||||
settings {
|
||||
yamlConfigurations {
|
||||
# register your own additional configuration
|
||||
# choose a number higher than 30 (below is reserved)
|
||||
100 = EXT:my_site_package/Configuration/Form/CustomFormSetup.yaml
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
... you only have to define the following YAML setup in ``EXT:my_site_package/Configuration/Form/CustomFormSetup.yaml``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formElementsDefinition:
|
||||
Form:
|
||||
renderingOptions:
|
||||
templateRootPaths:
|
||||
20: 'EXT:my_site_package/Resources/Private/Templates/Form/Frontend/'
|
||||
partialRootPaths:
|
||||
20: 'EXT:my_site_package/Resources/Private/Partials/Form/Frontend/'
|
||||
layoutRootPaths:
|
||||
20: 'EXT:my_site_package/Resources/Private/Layouts/Form/Frontend/'
|
||||
|
||||
The values of your own configuration file will overrule the corresponding
|
||||
values of the basic configuration file (:file:`EXT:form/Configuration/Yaml/BaseSetup.yaml`).
|
||||
|
||||
|
||||
.. _concepts-configuration-inheritances-operator:
|
||||
|
||||
__inheritances operator
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ``__inheritances`` operator is an extremely useful instrument. Using it
|
||||
helps to significantly reduce the configuration effort. It behaves similar
|
||||
to the ``<`` operator in TypoScript. That is, the definition of the source
|
||||
object is copied to the target object. The configuration can be inherited
|
||||
from several parent objects and can be overridden afterwards. Two simple
|
||||
examples will show you the usage and behaviour of the ``__inheritances``
|
||||
operator.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Form:
|
||||
part01:
|
||||
key01: value
|
||||
key02:
|
||||
key03: value
|
||||
part02:
|
||||
__inheritances:
|
||||
10: Form.part01
|
||||
|
||||
The configuration above results in:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Form:
|
||||
part01:
|
||||
key01: value
|
||||
key02:
|
||||
key03: value
|
||||
part02:
|
||||
key01: value
|
||||
key02:
|
||||
key03: value
|
||||
|
||||
As you can see, ``part02`` inherited all of ``part01``'s properties.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Form:
|
||||
part01:
|
||||
key: value
|
||||
part02:
|
||||
__inheritances:
|
||||
10: Form.part01
|
||||
key: 'value override'
|
||||
|
||||
The configuration above results in:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Form:
|
||||
part01:
|
||||
key: value
|
||||
part02:
|
||||
key: 'value override'
|
||||
|
||||
EXT:form heavily uses the ``__inheritances`` operator, in particular, for
|
||||
the definition of form elements. The following example shows you how to use
|
||||
the operator to define a new form element which behaves like the parent
|
||||
element but also has its own properties.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formElementsDefinition:
|
||||
GenderSelect:
|
||||
__inheritances:
|
||||
10: 'TYPO3.CMS.Form.prototypes.standard.formElementsDefinition.RadioButton'
|
||||
renderingOptions:
|
||||
templateName: 'RadioButton'
|
||||
properties:
|
||||
options:
|
||||
f: 'Female'
|
||||
m: 'Male'
|
||||
u: 'Unicorn'
|
||||
a: 'Alien'
|
||||
|
||||
The YAML configuration defines a new form element called ``GenderSelect``.
|
||||
This element inherits its definition from the ``RadioButton`` element but
|
||||
additionally ships four predefined options. Without any problems, the new
|
||||
element can be used and overridden within the ``form definition``.
|
||||
|
||||
.. hint::
|
||||
|
||||
Currently, there is no built-in solution within the TYPO3 core to
|
||||
preview the resulting/ final EXT:form YAML configuration. If you want
|
||||
to check the configuration, there is a fishy way which you should never
|
||||
implement on a production system.
|
||||
|
||||
Open the file ``typo3/sysext/form/Classes/Mvc/Configuration/ConfigurationManager.php::getConfigurationFromYamlFile()``
|
||||
and add the following code before the ``return`` statement::
|
||||
|
||||
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($yamlSettings, 'form configuration', 9999);
|
||||
|
||||
Now open the ``Forms`` module in the backend or navigate to a page in
|
||||
the frontend which contains a form. The DebuggerUtility will print the
|
||||
final configuration directly to the screen.
|
||||
|
||||
It will probably take some time to fully understand the awesomeness of
|
||||
this operator. If you are eager to learn more about this great instrument,
|
||||
check out the unit tests defined in ``EXT:form/Tests/Unit/Mvc/Configuration/InheritancesResolverServiceTest.php``.
|
||||
|
||||
|
||||
.. _concepts-configuration-prototypes:
|
||||
|
||||
Prototypes
|
||||
----------
|
||||
|
||||
Most of the configurational aspects of the form framework are defined
|
||||
in so-called ``prototypes``. By default, EXT:form defines a prototype
|
||||
named ``standard``. The definition of form elements - including their
|
||||
rendering in the frontend, ``form editor`` and ``form plugin`` - reside
|
||||
within those prototypes. As soon as you create a new form, the specific
|
||||
form definition references such a prototype.
|
||||
|
||||
This allows you to do a lot of nifty stuff. Let your imagination run free.
|
||||
For example:
|
||||
|
||||
- based on the referenced prototype, the same form can load
|
||||
|
||||
- ...varying templates
|
||||
- ...varying ``form editor`` configurations
|
||||
- ...varying ``form plugin`` finisher overrides
|
||||
|
||||
- within the ``form manager``, depending on the selected prototype
|
||||
|
||||
- ...varying ``form editor`` configurations can be loaded
|
||||
- ...varying pre-configured form templates (boilerplates) can be chosen
|
||||
|
||||
- different prototypes can define different/ extended form elements and
|
||||
display them in the frontend/ ``form editor`` accordingly
|
||||
|
||||
Check out the following use case to fully understand the concept behind
|
||||
prototypes. Imagine that there are two defined prototypes: "noob" and
|
||||
"poweruser".
|
||||
|
||||
.. t3-field-list-table::
|
||||
:header-rows: 1
|
||||
|
||||
- :a:
|
||||
:b: Prototype "noob"
|
||||
:c: Prototype "poweruser"
|
||||
|
||||
- :a: **Available form elements within the ``form editor``**
|
||||
:b: Text, Textarea
|
||||
:c: No changes. Default behaviour.
|
||||
|
||||
- :a: **Available finisher within the ``form editor``**
|
||||
:b: Only the email finisher is available. It offers a field for setting
|
||||
the subject of the mail. All remaining fields are hidden and filled
|
||||
with default values.
|
||||
:c: No changes. Default behaviour.
|
||||
|
||||
- :a: **Finisher overrides within the ``form plugin``**
|
||||
:b: It is not possible to override the finisher configuration.
|
||||
:c: No changes. Default behaviour.
|
||||
@@ -0,0 +1,527 @@
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _concepts-finishers:
|
||||
|
||||
Finishers
|
||||
=========
|
||||
|
||||
The form framework ships a bunch of finishers, which will be briefly
|
||||
described here. For more details, please head to the API reference and check
|
||||
out the section regarding :ref:`Finisher Options<apireference-finisheroptions>`.
|
||||
|
||||
|
||||
.. _concepts-finishers-closurefinisher:
|
||||
|
||||
Closure finisher
|
||||
----------------
|
||||
|
||||
The 'Closure finisher' can only be used within forms that are created
|
||||
programmatically. It allows you to execute your own finisher code without
|
||||
implementing/ declaring a finisher.
|
||||
|
||||
|
||||
.. _concepts-finishers-confirmationfinisher:
|
||||
|
||||
Confirmation finisher
|
||||
---------------------
|
||||
|
||||
The 'Confirmation finisher' is a simple finisher that outputs a given
|
||||
text after the form has been submitted.
|
||||
|
||||
|
||||
.. _concepts-finishers-deleteuploadsfinisher:
|
||||
|
||||
DeleteUploads finisher
|
||||
----------------------
|
||||
|
||||
The 'DeleteUploads finisher' removes submitted files. Use this finisher,
|
||||
for example, after the email finisher if you do not want to keep the files
|
||||
within your TYPO3 installation.
|
||||
|
||||
.. note::
|
||||
|
||||
Finishers are only executed on successfully submitted forms. If a user uploads a file but
|
||||
does not finish the form successfully, the uploaded files will not be deleted.
|
||||
|
||||
|
||||
.. _concepts-finishers-emailfinisher:
|
||||
|
||||
Email finisher
|
||||
--------------
|
||||
|
||||
The 'Email finisher' sends an email to one recipient. EXT:form uses two
|
||||
``EmailFinisher`` declarations with the identifiers ``EmailToReceiver`` and
|
||||
``EmailToSender``.
|
||||
|
||||
|
||||
.. _concepts-finishers-flashmessagefinisher:
|
||||
|
||||
FlashMessage finisher
|
||||
---------------------
|
||||
|
||||
The 'FlashMessage finisher' is a simple finisher that adds a message to the
|
||||
FlashMessageContainer.
|
||||
|
||||
|
||||
.. _concepts-finishers-redirectfinisher:
|
||||
|
||||
Redirect finisher
|
||||
-----------------
|
||||
|
||||
The 'Redirect finisher' is a simple finisher that redirects to another page.
|
||||
Additional link parameters can be added to the URL.
|
||||
|
||||
.. note::
|
||||
|
||||
This finisher stops the execution of all subsequent finishers in order to perform a redirect.
|
||||
Therefore, this finisher should always be the last finisher to be executed.
|
||||
|
||||
|
||||
.. _concepts-finishers-savetodatabasefinisher:
|
||||
|
||||
SaveToDatabase finisher
|
||||
-----------------------
|
||||
|
||||
The 'SaveToDatabase finisher' saves the data of a submitted form into a
|
||||
database table.
|
||||
|
||||
Here is an example for adding uploads to ext:news (fal_related_files and fal_media).
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
-
|
||||
identifier: SaveToDatabase
|
||||
options:
|
||||
-
|
||||
table: tx_news_domain_model_news
|
||||
mode: insert
|
||||
elements:
|
||||
my-field:
|
||||
mapOnDatabaseColumn: bodytext
|
||||
imageupload-1:
|
||||
mapOnDatabaseColumn: fal_media
|
||||
fileupload-1:
|
||||
mapOnDatabaseColumn: fal_related_files
|
||||
databaseColumnMappings:
|
||||
pid:
|
||||
value: 3
|
||||
tstamp:
|
||||
value: '{__currentTimestamp}'
|
||||
datetime:
|
||||
value: '{__currentTimestamp}'
|
||||
crdate:
|
||||
value: '{__currentTimestamp}'
|
||||
hidden:
|
||||
value: 1
|
||||
-
|
||||
table: sys_file_reference
|
||||
mode: insert
|
||||
elements:
|
||||
imageupload-1:
|
||||
mapOnDatabaseColumn: uid_local
|
||||
skipIfValueIsEmpty: true
|
||||
databaseColumnMappings:
|
||||
table_local:
|
||||
value: sys_file
|
||||
tablenames:
|
||||
value: tx_news_domain_model_news
|
||||
fieldname:
|
||||
value: fal_media
|
||||
tstamp:
|
||||
value: '{__currentTimestamp}'
|
||||
crdate:
|
||||
value: '{__currentTimestamp}'
|
||||
showinpreview:
|
||||
value: 1
|
||||
uid_foreign:
|
||||
value: '{SaveToDatabase.insertedUids.0}'
|
||||
-
|
||||
table: sys_file_reference
|
||||
mode: insert
|
||||
elements:
|
||||
fileupload-1:
|
||||
mapOnDatabaseColumn: uid_local
|
||||
skipIfValueIsEmpty: true
|
||||
databaseColumnMappings:
|
||||
table_local:
|
||||
value: sys_file
|
||||
tablenames:
|
||||
value: tx_news_domain_model_news
|
||||
fieldname:
|
||||
value: fal_related_files
|
||||
tstamp:
|
||||
value: '{__currentTimestamp}'
|
||||
crdate:
|
||||
value: '{__currentTimestamp}'
|
||||
uid_foreign:
|
||||
value: '{SaveToDatabase.insertedUids.0}'
|
||||
-
|
||||
table: sys_file_reference
|
||||
mode: update
|
||||
whereClause:
|
||||
uid_foreign: '{SaveToDatabase.insertedUids.0}'
|
||||
uid_local: 0
|
||||
databaseColumnMappings:
|
||||
pid:
|
||||
value: 0
|
||||
uid_foreign:
|
||||
value: 0
|
||||
|
||||
.. _concepts-finishers-translation:
|
||||
|
||||
Translation of finisher options
|
||||
-------------------------------
|
||||
|
||||
To learn more about this topic, please continue :ref:`here<concepts-frontendrendering-translation-finishers>`.
|
||||
|
||||
|
||||
.. _concepts-finishers-customfinisherimplementations:
|
||||
|
||||
Write a custom finisher
|
||||
-----------------------
|
||||
|
||||
If you want to make the finisher configurable in the backend UI read :ref:`here<concepts-finishers-customfinisherimplementations-extend-gui>`.
|
||||
|
||||
Finishers are defined as part of a ``prototype`` within a
|
||||
``finishersDefinition``. The property ``implementationClassName`` is to be
|
||||
utilized to load the finisher implementation.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
finishersDefinition:
|
||||
CustomFinisher:
|
||||
implementationClassName: 'VENDOR\MySitePackage\Domain\Finishers\CustomFinisher'
|
||||
|
||||
If the finisher requires options, you can define those within the
|
||||
``options`` property. The options will be used as default values and can
|
||||
be overridden using the ``form definition``.
|
||||
|
||||
Define the default value:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
finishersDefinition:
|
||||
CustomFinisher:
|
||||
implementationClassName: 'VENDOR\MySitePackage\Domain\Finishers\CustomFinisher'
|
||||
options:
|
||||
yourCustomOption: 'Ralf'
|
||||
|
||||
Override the option using the ``form definition``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
identifier: sample-form
|
||||
label: 'Simple Contact Form'
|
||||
prototype: standard
|
||||
type: Form
|
||||
|
||||
finishers:
|
||||
-
|
||||
identifier: CustomFinisher
|
||||
options:
|
||||
yourCustomOption: 'Björn'
|
||||
|
||||
renderables:
|
||||
...
|
||||
|
||||
Each finisher has to be programmed to the interface ``TYPO3\CMS\Form\Domain\Finishers\FinisherInterface``
|
||||
and should extend the class ``TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher``.
|
||||
In doing so, the logic of the finisher should start with the method
|
||||
``executeInternal()``.
|
||||
|
||||
|
||||
.. _concepts-finishers-customfinisherimplementations-accessingoptions:
|
||||
|
||||
Accessing finisher options
|
||||
""""""""""""""""""""""""""
|
||||
|
||||
If your finisher extends ``TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher``,
|
||||
you can access your finisher options with the help of the ``parseOption()``
|
||||
method::
|
||||
|
||||
$yourCustomOption = $this->parseOption('yourCustomOption');
|
||||
|
||||
``parseOption()`` is looking for 'yourCustomOption' in your
|
||||
``form definition``. If it cannot be found, the method checks
|
||||
|
||||
1. the ``prototype`` configuration for a default value,
|
||||
|
||||
2. the finisher class itself by searching for a default value within the
|
||||
``$defaultOptions`` property::
|
||||
|
||||
declare(strict_types = 1);
|
||||
namespace VENDOR\MySitePackage\Domain\Finishers;
|
||||
|
||||
class CustomFinisher extends \TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher
|
||||
{
|
||||
|
||||
protected $defaultOptions = [
|
||||
'yourCustomOption' => 'Olli',
|
||||
];
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
If the option cannot be found by processing this fallback chain, ``null`` is
|
||||
returned.
|
||||
|
||||
If the option is found, the process checks whether the option value will
|
||||
access :ref:`FormRuntime values<concepts-finishers-customfinisherimplementations-accessingoptions-formruntimeaccessor>`.
|
||||
If the ``FormRuntime`` returns a positive result, it is checked whether the
|
||||
option value :ref:`can access values of preceding finishers<concepts-finishers-customfinisherimplementations-finishercontext-sharedatabetweenfinishers>`.
|
||||
At the very end, it tries to :ref:`translate the finisher options<concepts-frontendrendering-translation-finishers>`.
|
||||
|
||||
|
||||
.. _concepts-finishers-customfinisherimplementations-accessingoptions-formruntimeaccessor:
|
||||
|
||||
Accessing form runtime values
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
By utilizing a specific notation, finisher options can be populated with
|
||||
submitted form values (assuming you are using the ``parseOption()`` method).
|
||||
You can access values of the ``FormRuntime`` and thus values of each single
|
||||
form element by encapsulating the option values with ``{}``. If there is a
|
||||
form element with the ``identifier`` 'subject', you can access its value
|
||||
within the finisher configuration. Check out the following example to
|
||||
get the whole idea.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
identifier: simple-contact-form
|
||||
label: 'Simple Contact Form'
|
||||
prototype: standard
|
||||
type: Form
|
||||
|
||||
finishers:
|
||||
-
|
||||
identifier: Custom
|
||||
options:
|
||||
yourCustomOption: '{subject}'
|
||||
|
||||
renderables:
|
||||
-
|
||||
identifier: subject
|
||||
label: 'Subject'
|
||||
type: Text
|
||||
|
||||
::
|
||||
|
||||
// $yourCustomOption contains the value of the form element with the
|
||||
// identifier 'subject'
|
||||
$yourCustomOption = $this->parseOption('yourCustomOption');
|
||||
|
||||
In addition, you can use ``{__currentTimestamp}`` as a special option value.
|
||||
It will return the current UNIX timestamp.
|
||||
|
||||
|
||||
.. _concepts-finishers-customfinisherimplementations-finishercontext:
|
||||
|
||||
Finisher Context
|
||||
""""""""""""""""
|
||||
|
||||
The class ``TYPO3\CMS\Form\Domain\Finishers\FinisherContext`` takes care of
|
||||
transferring a finisher context to each finisher. Given the finisher is
|
||||
derived from ``TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher`` the
|
||||
finisher context will be available via::
|
||||
|
||||
$this->finisherContext
|
||||
|
||||
The method ``cancel`` prevents the execution of successive finishers::
|
||||
|
||||
$this->finisherContext->cancel();
|
||||
|
||||
The method ``getFormValues`` returns all of the submitted form values.
|
||||
|
||||
``getFormValues``::
|
||||
|
||||
$this->finisherContext->getFormValues();
|
||||
|
||||
The method ``getFormRuntime`` returns the ``FormRuntime``::
|
||||
|
||||
$this->finisherContext->getFormRuntime();
|
||||
|
||||
|
||||
.. _concepts-finishers-customfinisherimplementations-finishercontext-sharedatabetweenfinishers:
|
||||
|
||||
Share data between finishers
|
||||
''''''''''''''''''''''''''''
|
||||
|
||||
The method ``getFinisherVariableProvider`` returns an object (``TYPO3\CMS\Form\Domain\Finishers\FinisherVariableProvider``)
|
||||
which allows you to store data and transfer it to other finishers. The data
|
||||
can be easily accessed programmatically or within your configuration::
|
||||
|
||||
$this->finisherContext->getFinisherVariableProvider();
|
||||
|
||||
The data is stored within the ``FinisherVariableProvider`` and is addressed
|
||||
by a user-defined 'finisher identifier' and a custom option value path. The
|
||||
name of the 'finisher identifier' should consist of the name of the finisher
|
||||
without the potential 'Finisher' appendix. If your finisher is derived from
|
||||
the class ``TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher``, the name of
|
||||
this construct is stored in the following variable::
|
||||
|
||||
$this->shortFinisherIdentifier
|
||||
|
||||
For example, if the name of your finisher class is 'CustomFinisher', the
|
||||
mentioned variable will contain the value 'Custom'.
|
||||
|
||||
There are a bunch of methods to access and manage the finisher data:
|
||||
|
||||
- Add data::
|
||||
|
||||
$this->finisherContext->getFinisherVariableProvider()->add(
|
||||
$this->shortFinisherIdentifier,
|
||||
'unique.value.identifier',
|
||||
$value
|
||||
);
|
||||
|
||||
- Get data::
|
||||
|
||||
$this->finisherContext->getFinisherVariableProvider()->get(
|
||||
$this->shortFinisherIdentifier,
|
||||
'unique.value.identifier',
|
||||
'default value'
|
||||
);
|
||||
|
||||
- Check the existence of data::
|
||||
|
||||
$this->finisherContext->getFinisherVariableProvider()->exists(
|
||||
$this->shortFinisherIdentifier,
|
||||
'unique.value.identifier'
|
||||
);
|
||||
|
||||
- Delete data::
|
||||
|
||||
$this->finisherContext->getFinisherVariableProvider()->remove(
|
||||
$this->shortFinisherIdentifier,
|
||||
'unique.value.identifier'
|
||||
);
|
||||
|
||||
In this way, each finisher can access data programmatically. Moreover, it is
|
||||
possible to retrieve the data via configuration, provided that a finisher
|
||||
stores the values within the ``FinisherVariableProvider``.
|
||||
|
||||
Assuming that a finisher called 'Custom' sets data as follows::
|
||||
|
||||
$this->finisherContext->getFinisherVariableProvider()->add(
|
||||
$this->shortFinisherIdentifier,
|
||||
'unique.value.identifier',
|
||||
'Wouter'
|
||||
);
|
||||
|
||||
... you are now able to access the value 'Wouter' via ``{Custom.unique.value.identifier}``
|
||||
in any other finisher.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
identifier: sample-form
|
||||
label: 'Simple Contact Form'
|
||||
prototype: standard
|
||||
type: Form
|
||||
|
||||
finishers:
|
||||
-
|
||||
identifier: Custom
|
||||
options:
|
||||
yourCustomOption: 'Frans'
|
||||
|
||||
-
|
||||
identifier: SomeOtherStuff
|
||||
options:
|
||||
someOtherCustomOption: '{Custom.unique.value.identifier}'
|
||||
|
||||
|
||||
.. _concepts-finishers-customfinisherimplementations-extend-gui:
|
||||
|
||||
Add finisher to backend UI
|
||||
''''''''''''''''''''''''''
|
||||
|
||||
After adding a custom finisher you can also add the finisher to the
|
||||
form editor GUI to let your backend users configure it visually. Add the
|
||||
following to the backend yaml setup:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formElementsDefinition:
|
||||
Form:
|
||||
formEditor:
|
||||
editors:
|
||||
900:
|
||||
# Extend finisher drop down
|
||||
selectOptions:
|
||||
35:
|
||||
value: 'CustomFinisher'
|
||||
label: 'Custom Finisher'
|
||||
propertyCollections:
|
||||
finishers:
|
||||
# add finisher fields
|
||||
25:
|
||||
identifier: 'CustomFinisher'
|
||||
editors:
|
||||
__inheritances:
|
||||
10: 'TYPO3.CMS.Form.mixins.formElementMixins.BaseCollectionEditorsMixin'
|
||||
100:
|
||||
label: "Custom Finisher"
|
||||
# custom field (input, required)
|
||||
110:
|
||||
identifier: 'customField'
|
||||
templateName: 'Inspector-TextEditor'
|
||||
label: 'Custom Field'
|
||||
propertyPath: 'options.customField'
|
||||
propertyValidators:
|
||||
10: 'NotEmpty'
|
||||
# email field
|
||||
120:
|
||||
identifier: 'email'
|
||||
templateName: 'Inspector-TextEditor'
|
||||
label: 'Subscribers email'
|
||||
propertyPath: 'options.email'
|
||||
enableFormelementSelectionButton: true
|
||||
propertyValidators:
|
||||
10: 'NotEmpty'
|
||||
20: 'FormElementIdentifierWithinCurlyBracesInclusive'
|
||||
|
||||
finishersDefinition:
|
||||
CustomFinisher:
|
||||
formEditor:
|
||||
iconIdentifier: 'form-finisher'
|
||||
label: 'Custom Finisher'
|
||||
predefinedDefaults:
|
||||
options:
|
||||
customField: ''
|
||||
email: ''
|
||||
# displayed when overriding finisher settings
|
||||
FormEngine:
|
||||
label: 'Custom Finisher'
|
||||
elements:
|
||||
customField:
|
||||
label: 'Custom Field'
|
||||
config:
|
||||
type: 'text'
|
||||
email:
|
||||
label: 'Subscribers email'
|
||||
config:
|
||||
type: 'text'
|
||||
|
||||
Make sure the setup file is registered in the backend:
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
module.tx_form.settings.yamlConfigurations {
|
||||
123456789 = EXT:yourExtension/Configuration/Form/Backend.yml
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _concepts-formdefinition-vs-formconfiguration:
|
||||
|
||||
Form configuration vs. form definition
|
||||
======================================
|
||||
|
||||
So far, we have only described the configuration of the form framework.
|
||||
Once again, based on prototypes, the form configuration allows you to
|
||||
define:
|
||||
|
||||
- which form elements, finishers, and validators are available,
|
||||
- how those objects are pre-configured,
|
||||
- how those objects will be displayed within the frontend and backend.
|
||||
|
||||
In contrast, the **form definition** describes the specific form, including
|
||||
|
||||
- all form elements and their corresponding validators,
|
||||
- the order of the form elements within the form, and
|
||||
- the finishers which are fired as soon as the form has been submitted.
|
||||
- Furthermore, it defines the concrete values of each property of the
|
||||
mentioned aspects.
|
||||
|
||||
In other words, the **prototype configuration** defines the existence of a
|
||||
form element of type ``Text`` globally. The **form definition** declares
|
||||
that such a form element of type ``Text`` is located on page 1 at position
|
||||
1 of a specific form. In addition, it carries the information that this form
|
||||
element comes with the HTML attribute "placeholder" with value "Your name
|
||||
here". The form definition is written by the ``form editor``.
|
||||
|
||||
|
||||
Example form definition
|
||||
-----------------------
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
identifier: ext-form-simple-contact-form-example
|
||||
label: 'Simple Contact Form'
|
||||
prototype: standard
|
||||
type: Form
|
||||
|
||||
finishers:
|
||||
-
|
||||
identifier: EmailToReceiver
|
||||
options:
|
||||
subject: 'Your message'
|
||||
recipientAddress: 'your.company@example.com'
|
||||
recipientName: 'Your company name'
|
||||
senderAddress: '{email}'
|
||||
senderName: '{name}'
|
||||
|
||||
renderables:
|
||||
-
|
||||
identifier: page-1
|
||||
label: 'Contact Form'
|
||||
type: Page
|
||||
|
||||
renderables:
|
||||
-
|
||||
identifier: name
|
||||
label: 'Name'
|
||||
type: Text
|
||||
properties:
|
||||
fluidAdditionalAttributes:
|
||||
placeholder: 'Name'
|
||||
defaultValue: ''
|
||||
validators:
|
||||
-
|
||||
identifier: NotEmpty
|
||||
-
|
||||
identifier: subject
|
||||
label: 'Subject'
|
||||
type: Text
|
||||
properties:
|
||||
fluidAdditionalAttributes:
|
||||
placeholder: 'Subject'
|
||||
defaultValue: ''
|
||||
validators:
|
||||
-
|
||||
identifier: NotEmpty
|
||||
-
|
||||
identifier: email
|
||||
label: 'Email'
|
||||
type: Text
|
||||
properties:
|
||||
fluidAdditionalAttributes:
|
||||
placeholder: 'Email address'
|
||||
defaultValue: ''
|
||||
validators:
|
||||
-
|
||||
identifier: NotEmpty
|
||||
-
|
||||
identifier: EmailAddress
|
||||
-
|
||||
identifier: message
|
||||
label: 'Message'
|
||||
type: Textarea
|
||||
properties:
|
||||
fluidAdditionalAttributes:
|
||||
placeholder: ''
|
||||
defaultValue: ''
|
||||
validators:
|
||||
-
|
||||
identifier: NotEmpty
|
||||
|
||||
-
|
||||
identifier: summarypage
|
||||
label: 'Summary page'
|
||||
type: SummaryPage
|
||||
@@ -0,0 +1,549 @@
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _concepts-formeditor:
|
||||
|
||||
Form editor
|
||||
===========
|
||||
|
||||
|
||||
.. _concepts-formeditor-general:
|
||||
|
||||
What does it do?
|
||||
----------------
|
||||
|
||||
The ``form editor`` is a powerful graphical user interface which allows the
|
||||
backend editor to create ``form definitions`` without writing a single line
|
||||
of code. Those ``form definitions`` will be used by the frontend process to
|
||||
render beautiful forms.
|
||||
|
||||
The ``form editor`` is a modular interface which consists of several
|
||||
components:
|
||||
|
||||
- Stage: central visual component of the ``form editor`` which displays the
|
||||
form elements in an abstract view and a frontend preview
|
||||
- Tree: displays the structure of the form as a tree
|
||||
- Inspector: context specific toolbar which handles the visual display of
|
||||
form element options and allows editing those
|
||||
- Core: includes core functionalities of the ``form editor``
|
||||
- ViewModel: defines and steers the visual display
|
||||
- Mediator: delegates events of the components
|
||||
- Modals: processes modals
|
||||
- FormEditor: provides API functions
|
||||
- Helper: helper functions which mainly allow the manipulation of DOM
|
||||
elements
|
||||
|
||||
Generally speaking, the ``Modals``, ``Inspector``, and ``Stage`` components
|
||||
can be adapted through configuration. Especially the ``Inspector`` component
|
||||
is modular and extremely flexible. As an integrator, you can reuse so-called
|
||||
``inspector editors``. Those elements are input fields of different types
|
||||
which allow the backend editor to alter all of the available form element
|
||||
options.
|
||||
|
||||
.. figure:: ../../Images/javascript_module_interaction.png
|
||||
:alt: JavaScript module interaction
|
||||
|
||||
JavaScript module interaction
|
||||
|
||||
There is a general ``form editor`` configuration which can be found below
|
||||
the following configuration path:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formEditor:
|
||||
|
||||
Furthermore, you are able to configure the ``form editor`` regarding its
|
||||
different aspects. The configuration can be found below the following
|
||||
configuration paths:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formElementsDefinition:
|
||||
<formElementTypeIdentifier>:
|
||||
formEditor:
|
||||
finishersDefinition:
|
||||
<finisherIdentifier>
|
||||
formEditor:
|
||||
validatorsDefinition:
|
||||
<validatorIdentifier>
|
||||
formEditor:
|
||||
|
||||
|
||||
.. _concepts-formeditor-stage:
|
||||
|
||||
Stage
|
||||
-----
|
||||
|
||||
The ``Stage`` is the central visual component of the form editor which
|
||||
displays the form elements in two different modes:
|
||||
|
||||
- abstract view: all form elements of a ``Page`` are presented in an
|
||||
abstract way,
|
||||
- frontend preview: renders the form like it will (nearly) be displayed in
|
||||
the frontend ('nearly' since you have to make sure that your frontend CSS
|
||||
is also loaded in the backend in order to get the exact preview).
|
||||
|
||||
Per default, the frontend templates of EXT:form are based on Twitter
|
||||
Bootstrap. Since the backend of TYPO3 CMS also depends on this CSS framework,
|
||||
the corresponding CSS files are already loaded in the backend context.
|
||||
Nevertheless, certain parts of the CSS were overridden and extended in order
|
||||
to meet the specific needs of the TYPO3 backend. Thus, the frontend preview
|
||||
in the backend could differ compared to the "real" frontend.
|
||||
|
||||
If your frontend preview requires loading additional CSS or a CSS framework
|
||||
then go ahead and configure a specific ``prototype`` accordingly.
|
||||
|
||||
Beside the frontend templates, there are also templates for the abstract
|
||||
view, i.e. you can customize the rendering of the abstract view for each
|
||||
form element. If you have created your own form elements, in most cases you
|
||||
will fall back to the already existing Fluid templates. But remember, you
|
||||
are always able to create you own Fluid templated and adapt the abstract view
|
||||
till it suits your needs.
|
||||
|
||||
For more information, read the following chapter: ':ref:`Common abstract view form element templates<apireference-formeditor-stage-commonabstractformelementtemplates>`'.
|
||||
|
||||
|
||||
.. _concepts-formeditor-inspector:
|
||||
|
||||
Inspector
|
||||
---------
|
||||
|
||||
The ``Inspector`` component is situated on the right side of the ``form
|
||||
editor``. It is a modular, extremely flexible, and context specific toolbar
|
||||
which depends on the chosen form element. The ``Inspector`` allows editing
|
||||
the form element's options with the help of so-called ``inspector editors``.
|
||||
For the most parts, the interface can be easily customized by writing
|
||||
YAML configuration. For each form element you can define which properties
|
||||
are available and in which way they can be edited.
|
||||
|
||||
In addition to the editable form element properties (like ``properties.placeholder``)
|
||||
there are so-called ``property collections`` which can be written by the
|
||||
``form editor`` as well. Their definition is stored on the hierarchical
|
||||
level of a form element. Right now, there are the following ``property
|
||||
collections``:
|
||||
|
||||
- validators
|
||||
- finishers
|
||||
|
||||
``Property collections`` also make use of ``inspector editors`` in order to
|
||||
configure them properly. Due to this, we can do a lot of cool stuff. Imagine
|
||||
we have got a validator "Number range" with two validator options called
|
||||
"Minimum" and "Maximum". Additionally, we have got two form elements "Age
|
||||
spouse" and "Age infant". For both form elements the validator is available
|
||||
but for the form element "Age child" the validator option "Minimum" is not
|
||||
editable and the option "Maximum" is pre-filled with a certain value.
|
||||
|
||||
|
||||
.. _concepts-formeditor-basicjavascriptconcepts:
|
||||
|
||||
Basic JavaScript concepts
|
||||
-------------------------
|
||||
|
||||
The form framework was designed to be as extendible as possible. Sooner or
|
||||
later, you want to customize the components of the ``form editor`` using
|
||||
JavaScript. This is especially true if you want to create your own
|
||||
``inspector editors``. In order to achieve this, you can implement your own
|
||||
JavaScript modules. Those modules will include the required algorithms for
|
||||
the ``inspector editors`` and the ``abstract view`` as well as your own
|
||||
event listing.
|
||||
|
||||
|
||||
.. _concepts-formeditor-basicjavascriptconcepts-registercustomjavascriptmodules:
|
||||
|
||||
Register custom JavaScript modules
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The following YAML configuration registers an additional JavaScript module.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formEditor:
|
||||
dynamicRequireJsModules:
|
||||
additionalViewModelModules:
|
||||
10: 'TYPO3/CMS/MySitePackage/Backend/FormEditor/ViewModel'
|
||||
|
||||
According to the example shown above, the JavaScript files have to be stored
|
||||
within the folder ``my_site_package/Resources/Public/JavaScript/Backend/FormEditor/ViewModel.js``.
|
||||
In the TYPO3 backend JavaScript files are loaded via RequireJS which depends
|
||||
on a certain convention. The name of the module starts with **TYPO3/CMS**/MySitePackage/Backend/FormEditor/ViewModel
|
||||
followed by your extension key TYPO3/CMS/**MySitePackage**/Backend/FormEditor/ViewModel.
|
||||
Thus, you have to ensure that the module is stored within a subfolder of ``my_site_package/Resources/Public/JavaScript/``.
|
||||
The part TYPO3/CMS/MySitePackage/**Backend/FormEditor**/ViewModel tells you
|
||||
that your files have to be stored in my_site_package/Resources/Public/JavaScript/**Backend/FormEditor**/.
|
||||
The last section TYPO3/CMS/MySitePackage/Backend/FormEditor/**ViewModel**
|
||||
states the name of the JavaScript file without the file extension (.js).
|
||||
|
||||
Check out the following base template which shows you the recommended way
|
||||
for setting up your own module.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
/**
|
||||
* Module: TYPO3/CMS/MySitePackage/Backend/FormEditor/ViewModel
|
||||
*/
|
||||
define(['jquery',
|
||||
'TYPO3/CMS/Form/Backend/FormEditor/Helper'
|
||||
], function($, Helper) {
|
||||
'use strict';
|
||||
|
||||
return (function($, Helper) {
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
var _formEditorApp = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
function getFormEditorApp() {
|
||||
return _formEditorApp;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
function getPublisherSubscriber() {
|
||||
return getFormEditorApp().getPublisherSubscriber();
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
function getUtility() {
|
||||
return getFormEditorApp().getUtility();
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param object
|
||||
* @return object
|
||||
*/
|
||||
function getHelper() {
|
||||
return Helper;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
function getCurrentlySelectedFormElement() {
|
||||
return getFormEditorApp().getCurrentlySelectedFormElement();
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param mixed test
|
||||
* @param string message
|
||||
* @param int messageCode
|
||||
* @return void
|
||||
*/
|
||||
function assert(test, message, messageCode) {
|
||||
return getFormEditorApp().assert(test, message, messageCode);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @return void
|
||||
* @throws 1491643380
|
||||
*/
|
||||
function _helperSetup() {
|
||||
assert('function' === $.type(Helper.bootstrap),
|
||||
'The view model helper does not implement the method "bootstrap"',
|
||||
1491643380
|
||||
);
|
||||
Helper.bootstrap(getFormEditorApp());
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function _subscribeEvents() {
|
||||
getPublisherSubscriber().subscribe('some/eventName/you/want/to/handle', function(topic, args) {
|
||||
myCustomCode();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function myCustomCode() {
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* @param object formEditorApp
|
||||
* @return void
|
||||
*/
|
||||
function bootstrap(formEditorApp) {
|
||||
_formEditorApp = formEditorApp;
|
||||
_helperSetup();
|
||||
_subscribeEvents();
|
||||
};
|
||||
|
||||
/**
|
||||
* Publish the public methods.
|
||||
* Implements the "Revealing Module Pattern".
|
||||
*/
|
||||
return {
|
||||
bootstrap: bootstrap
|
||||
};
|
||||
})($, Helper);
|
||||
});
|
||||
|
||||
|
||||
.. _concepts-formeditor-basicjavascriptconcepts-events:
|
||||
|
||||
Events
|
||||
^^^^^^
|
||||
|
||||
The event handling of EXT:form is based on the ``Publish/Subscribe Pattern``.
|
||||
To learn more about this terrific pattern, check out this website: https://addyosmani.com/resources/essentialjsdesignpatterns/book/.
|
||||
Please not that the processing sequence of the subscribers cannot be
|
||||
influenced. Furthermore, there is no information flow between the
|
||||
subscribers. All events have to be arranged asynchronously.
|
||||
|
||||
For more information, head to the API reference and read the section about
|
||||
':ref:`Events<concepts-formeditor-basicjavascriptconcepts-events>`'.
|
||||
|
||||
|
||||
.. _concepts-formeditor-basicjavascriptconcepts-formelementmodel:
|
||||
|
||||
FormElement model
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Within the JavaScript code, each form element is represented by a
|
||||
`FormElement model``. This model can be seen as a copy of the ``form
|
||||
definition'' enriched by some additional data. The following example shows
|
||||
you a ``form definition`` and the debug output of the corresponding
|
||||
``FormElement model``.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
identifier: javascript-form-element-model
|
||||
label: 'JavaScript FormElement model'
|
||||
type: Form
|
||||
finishers:
|
||||
-
|
||||
identifier: EmailToReceiver
|
||||
options:
|
||||
subject: 'Your message: {subject}'
|
||||
recipientAddress: your.company@example.com
|
||||
recipientName: 'Your Company name'
|
||||
senderAddress: '{email}'
|
||||
senderName: '{name}'
|
||||
replyToAddress: ''
|
||||
carbonCopyAddress: ''
|
||||
blindCarbonCopyAddress: ''
|
||||
format: html
|
||||
attachUploads: 'true'
|
||||
translation:
|
||||
language: ''
|
||||
renderables:
|
||||
-
|
||||
identifier: page-1
|
||||
label: 'Contact Form'
|
||||
type: Page
|
||||
renderables:
|
||||
-
|
||||
identifier: name
|
||||
label: Name
|
||||
type: Text
|
||||
properties:
|
||||
fluidAdditionalAttributes:
|
||||
placeholder: Name
|
||||
defaultValue: ''
|
||||
validators:
|
||||
-
|
||||
identifier: NotEmpty
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
"identifier": "javascript-form-element-model",
|
||||
"label": "JavaScript FormElement model",
|
||||
"type": "Form",
|
||||
"prototypeName": "standard",
|
||||
"__parentRenderable": null,
|
||||
"__identifierPath": "example-form",
|
||||
"finishers": [
|
||||
{
|
||||
"identifier": "EmailToReceiver",
|
||||
"options": {
|
||||
"subject": "Your message: {subject}",
|
||||
"recipientAddress": "your.company@example.com",
|
||||
"recipientName": "Your Company name",
|
||||
"senderAddress": "{email}",
|
||||
"senderName": "{name}",
|
||||
"replyToAddress": "",
|
||||
"carbonCopyAddress": "",
|
||||
"blindCarbonCopyAddress": "",
|
||||
"format": "html",
|
||||
"attachUploads": true,
|
||||
"translation": {
|
||||
"language": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"renderables": [
|
||||
{
|
||||
"identifier": "page-1",
|
||||
"label": "Contact Form",
|
||||
"type": "Page",
|
||||
"__parentRenderable": "example-form (filtered)",
|
||||
"__identifierPath": "example-form/page-1",
|
||||
"renderables": [
|
||||
{
|
||||
"identifier": "name",
|
||||
"defaultValue": "",
|
||||
"label": "Name",
|
||||
"type": "Text",
|
||||
"properties": {
|
||||
"fluidAdditionalAttributes": {
|
||||
"placeholder": "Name"
|
||||
}
|
||||
},
|
||||
"__parentRenderable": "example-form/page-1 (filtered)",
|
||||
"__identifierPath": "example-form/page-1/name",
|
||||
"validators": [
|
||||
{
|
||||
"identifier": "NotEmpty"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
For each form element which has child elements, you will find a property
|
||||
called ``renderables``. Those ``renderables`` are arrays whose elements
|
||||
consists of ``FormElement models`` of the particular child elements.
|
||||
|
||||
As previously mentioned, the ``FormElement model`` is a conglomerate of the
|
||||
data of the ``form definition`` and some additional information:
|
||||
|
||||
- __parentRenderable
|
||||
- __identifierPath
|
||||
|
||||
The following methods can be utilized in order to access the data of a
|
||||
``FormElement model``:
|
||||
|
||||
- get()
|
||||
- set()
|
||||
- unset()
|
||||
- on()
|
||||
- off()
|
||||
- getObjectData()
|
||||
- toString()
|
||||
- clone()
|
||||
|
||||
For more information, head to the API reference and read the section about
|
||||
the ':ref:`FormElement model<apireference-formeditor-basicjavascriptconcepts-formelementmodel>`'.
|
||||
|
||||
|
||||
.. _concepts-formeditor-translation-formeditor:
|
||||
|
||||
Translation of form editor
|
||||
--------------------------
|
||||
|
||||
All option values which reside below the following configuration keys can be
|
||||
translated:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formEditor:
|
||||
formElementsDefinition:
|
||||
<formElementTypeIdentifier>:
|
||||
formEditor:
|
||||
finishersDefinition:
|
||||
<finisherIdentifier>
|
||||
formEditor:
|
||||
validatorsDefinition:
|
||||
<validatorIdentifier>
|
||||
formEditor:
|
||||
|
||||
The translation files of the ``form editor`` are loaded as follows:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formEditor:
|
||||
translationFile:
|
||||
# translation files for the form editor
|
||||
10: 'EXT:form/Resources/Private/Language/Database.xlf'
|
||||
20: 'EXT:my_site_package/Resources/Private/Language/Database.xlf'
|
||||
|
||||
The process searches for each option value within all of the defined
|
||||
translation files. If a translation is found, the translated option value
|
||||
will be used in preference.
|
||||
|
||||
Imagine, the following is defined for an option value:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
...
|
||||
label: 'formEditor.elements.Form.editor.finishers.label'
|
||||
...
|
||||
|
||||
First of all, the process searches for the translation key ``formEditor.elements.Form.editor.finishers.label``
|
||||
within the file ``20: 'EXT:my_site_package/Resources/Private/Language/Database.xlf'``
|
||||
and after it inside the file ``10: 'EXT:form/Resources/Private/Language/Database.xlf'``.
|
||||
If nothing is found, the option value will be displayed unmodified.
|
||||
|
||||
Due to compatibility issues, the setting ``translationFile`` is not defined
|
||||
as an array in the default configuration. To load your own translation files,
|
||||
you should define an array containing 'EXT:form/Resources/Private/Language/Database.xlf'
|
||||
as first entry (key ``10``) followed by your own file (key ``20``) as
|
||||
displayed in the example above.
|
||||
|
||||
|
||||
.. _concepts-formeditor-extending:
|
||||
|
||||
Examples for extending the editor
|
||||
---------------------------------
|
||||
|
||||
Learn :ref:`here <concepts-finishers-customfinisherimplementations-extend-gui>`
|
||||
how to make the finisher configurable in the backend UI.
|
||||
@@ -0,0 +1,144 @@
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _concepts-form-file-storages:
|
||||
|
||||
Form/ File storages
|
||||
===================
|
||||
|
||||
EXT:form stores the form definitions within the file system and thus needs
|
||||
write access to this storage. By default, the filemount ``user_uploads`` is
|
||||
used. It is possible to configure a different and/ or an additional
|
||||
filemount, which is then utilized for storing and reading forms.
|
||||
|
||||
The backend user will only see form definitions that are stored in
|
||||
filemounts where the user has at least read access. The ``form editor`` and
|
||||
the ``form plugin`` respect those access rights. In this way, you are able
|
||||
to implement ACLs. If you have configured more than one filemount and the
|
||||
backend user is able to access those, the ``form manager`` will allow the
|
||||
user to choose the preferred storage in which the form will be saved.
|
||||
|
||||
Even cooler, form definitions can be stored in and shipped with your custom
|
||||
extensions. If configured accordingly, the backend user will be able to
|
||||
embed those forms. Furthermore, you can configure that these form
|
||||
definitions:
|
||||
|
||||
- can be edited within the ``form editor``,
|
||||
- can be deleted with the help of the ``form manager``.
|
||||
|
||||
By default, the aforementioned options are turned off. We decided to do so
|
||||
because having dynamic content within an extension - which is possibly
|
||||
version-controlled - is usually not a good idea. Furthermore, there is no
|
||||
ACL system available.
|
||||
|
||||
**File uploads** will be saved within filemounts as well. They are handled
|
||||
as FAL objects. The available filemounts for such uploads can be configured.
|
||||
When adding/ editing a file upload element, the backend user can select the
|
||||
desired upload storage.
|
||||
|
||||
.. note::
|
||||
|
||||
In principle, files in filemounts are publicly accessible. If the
|
||||
uploaded files could contain sensitive data, you should suppress any
|
||||
HTTP access to the filemount. This may, for example, be achieved by
|
||||
creating a :file:`.htaccess` file, assuming you are using an Apache web
|
||||
server. The directive of the :file:`.htaccess` file is fairly easy:
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
# Apache ≥ 2.3
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
|
||||
# Apache < 2.3
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
Satisfy All
|
||||
</IfModule>
|
||||
|
||||
The following code block shows you how to configure additional filemounts
|
||||
for form definitions.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
persistenceManager:
|
||||
allowedFileMounts:
|
||||
# default filemount, no need to redeclare it again
|
||||
# just to show you the structure
|
||||
# 10: 1:/form_definitions/
|
||||
# additional filemounts
|
||||
100: 1:/custom/forms/
|
||||
110: 2:/cloudstorage/forms/
|
||||
|
||||
The following code block shows you how to allow an extension path as an
|
||||
additional filemount for form definitions.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
persistenceManager:
|
||||
allowedExtensionPaths:
|
||||
10: EXT:my_site_package/Resources/Private/Forms/
|
||||
|
||||
Add the following config if you want to allow backend users to **edit**
|
||||
forms stored within your own extension.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
persistenceManager:
|
||||
allowSaveToExtensionPaths: true
|
||||
|
||||
Add the following config if you want to allow backend users to **delete**
|
||||
forms stored within your own extension.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
persistenceManager:
|
||||
allowDeleteFromExtensionPaths: true
|
||||
|
||||
The following code blocks show you the default setup for filemounts that
|
||||
are used for file (and image) uploads.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formElementsDefinition:
|
||||
FileUpload:
|
||||
formEditor:
|
||||
predefinedDefaults:
|
||||
properties:
|
||||
saveToFileMount: '1:/user_upload/'
|
||||
editors:
|
||||
400:
|
||||
selectOptions:
|
||||
10:
|
||||
value: '1:/user_upload/'
|
||||
label: '1:/user_upload/'
|
||||
properties:
|
||||
saveToFileMount: '1:/user_upload/'
|
||||
ImageUpload
|
||||
properties:
|
||||
saveToFileMount: '1:/user_upload/'
|
||||
editors:
|
||||
400:
|
||||
selectOptions:
|
||||
10:
|
||||
value: '1:/user_upload/'
|
||||
label: '1:/user_upload/'
|
||||
@@ -0,0 +1,145 @@
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _concepts-formmanager:
|
||||
|
||||
Form manager
|
||||
============
|
||||
|
||||
|
||||
.. _concepts-formmanager-general:
|
||||
|
||||
What does it do?
|
||||
----------------
|
||||
|
||||
The ``form manager`` can be accessed by opening the backend module 'Forms'.
|
||||
It allows the editor to administer all of the existing forms stored on the
|
||||
accessible filemounts. The central element of the ``form manager`` is a
|
||||
table view which...
|
||||
|
||||
- lists all forms
|
||||
- allows users to create, edit, duplicate, and delete forms
|
||||
- names the storage folder
|
||||
- gives a broad overview on which pages the listed forms are used in.
|
||||
|
||||
The creation and duplication of forms is supported by a so-called ``form
|
||||
wizard``. The wizard guides the editor through the process and offers a
|
||||
variety of settings depending on the form configuration. Those settings
|
||||
include choosing filemounts, prototypes, and start templates.
|
||||
|
||||
.. figure:: ../../Images/form_manager.png
|
||||
:alt: The form manager
|
||||
|
||||
TYPO3 Backend with opened module 'Forms' displaying the form manager.
|
||||
|
||||
|
||||
.. _concepts-formmanager-starttemplate:
|
||||
|
||||
Start templates
|
||||
---------------
|
||||
|
||||
This is a very nifty feature. When creating a new form, the ``form manager``
|
||||
allows the backend editor to select a so-called ``Start template``. Such a
|
||||
template is a predefined ``form definition`` without the property
|
||||
``prototypeName`` which is normally used as a foundation of a new form.
|
||||
|
||||
As an integrator, you can specify as many ``Start templates`` as you desire
|
||||
for a given ``prototype``. After you have defined such a template, follow
|
||||
these easy steps to use your defined ``Start templates`` as a foundation:
|
||||
|
||||
- open the ``Forms`` module
|
||||
- create a new form by clicking on the appropriate button
|
||||
- enter the 'Form name' and click the checkbox 'Advanced settings'
|
||||
- during the next steps you can select a ``Start template``
|
||||
|
||||
For each ``prototype``, you have to define a ``Start template`` in order to
|
||||
enable the editor to choose one. Additionally, the same ``Start template``
|
||||
can be used for several ``prototypes``. To do so, make sure the included
|
||||
form elements of the template are defined in the corresponding ``prototype``.
|
||||
|
||||
For example, imagine your integrator has :ref:`configured<typo3.cms.form.formmanager.selectablePrototypesConfiguration>`
|
||||
a prototype called 'routing' which contains a custom form element with the
|
||||
``<formElementTypeIdentifier>`` 'locationPicker'. The element is only
|
||||
defined for this prototype. The integrator has created a ``Start template``
|
||||
which carries the 'locationPicker' form element. A backend editor could now
|
||||
select and use this ``Start template``, including the custom form element,
|
||||
as long as the ``prototype`` is set to 'routing'. If the integrator also
|
||||
adds this custom form element to another ``prototype``, the process would
|
||||
crash. The custom form element is only known by the ``prototype`` 'routing'.
|
||||
|
||||
The following code block shows the minimal configuration of a ``Start
|
||||
template``. You need at least the root form element ('Form') and a 'Page'.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
type: 'Form'
|
||||
identifier: 'blankForm'
|
||||
label: '[Blank Form]'
|
||||
renderables:
|
||||
-
|
||||
type: 'Page'
|
||||
identifier: 'page-1'
|
||||
label: 'Page'
|
||||
|
||||
As mentioned previously, the form wizard within the ``form manager`` offers
|
||||
a list of all existing, :ref:`pre-configured<typo3.cms.form.formmanager.selectableprototypesconfiguration.*.newformtemplates>`
|
||||
``Start templates``. As soon as the backend editor creates a form with the
|
||||
help of such a template, a new ``form definition`` is generated based on the
|
||||
one of the selected ``Start template``. The ``form definition`` will be
|
||||
enriched by the property ``propertyName`` defining the chosen ``prototype``.
|
||||
The ``identifier`` of the root form element ('Form') is automatically set
|
||||
based on the entered "Form name". Additionally, this name is used for the
|
||||
property `` label`` of the 'Form' element. Finally, the ``form editor`` is
|
||||
loaded and displays the newly created form.
|
||||
|
||||
|
||||
.. _concepts-formmanager-translation-starttemplate:
|
||||
|
||||
Translation of the form manager
|
||||
-------------------------------
|
||||
|
||||
All option values which reside below the following configuration keys can be
|
||||
translated:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
formManager:
|
||||
|
||||
The translation files of the ``form manager`` are loaded as follows:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
formManager:
|
||||
translationFile:
|
||||
# translation files for the form manager
|
||||
10: 'EXT:form/Resources/Private/Language/Database.xlf'
|
||||
20: 'EXT:my_site_package/Resources/Private/Language/Form/Database.xlf'
|
||||
|
||||
The process searches for each option value within all of the defined
|
||||
translation files. If a translation is found, the translated option value
|
||||
will be used in preference.
|
||||
|
||||
Imagine, the following is defined for an option value:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
...
|
||||
label: 'formManager.selectablePrototypesConfiguration.standard.label'
|
||||
...
|
||||
|
||||
First of all, the process searches for the translation key ``formManager.selectablePrototypesConfiguration.standard.label``
|
||||
within the file ``20: 'EXT:my_site_package/Resources/Private/Language/Form/Database.xlf'``
|
||||
and after it inside the file ``10: 'EXT:form/Resources/Private/Language/Database.xlf'``.
|
||||
If nothing is found, the option value will be displayed unmodified.
|
||||
|
||||
Due to compatibility issues, the setting ``translationFile`` is not defined
|
||||
as an array in the default configuration. To load your own translation files,
|
||||
you should define an array containing 'EXT:form/Resources/Private/Language/Database.xlf'
|
||||
as first entry (key ``10``) followed by your own file (key ``20``) as
|
||||
displayed in the example above.
|
||||
@@ -0,0 +1,87 @@
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _concepts-formplugin:
|
||||
|
||||
Form plugin
|
||||
===========
|
||||
|
||||
|
||||
.. _concepts-formelugin-general:
|
||||
|
||||
What does it do?
|
||||
----------------
|
||||
|
||||
The form plugin allows you to assign a form - created with the ``form
|
||||
editor`` or shipped with your extension - to a specific page. This enables
|
||||
you to re-use forms throughout the whole TYPO3 installation. Furthermore, it
|
||||
offers the backend editor the possibility to override certain aspects of the
|
||||
form definition. At the moment, only finisher options can be overridden. The
|
||||
possibilities depend on the configuration of the underlying prototype.
|
||||
|
||||
Imagine, your form contains a redirect finisher. The redirect target is set
|
||||
globally and valid for the whole ``form definition`` . While adding the form
|
||||
to a specific page, the backend editor can define a different redirect targeting. This
|
||||
setting is only valid for the page containing the plugin.
|
||||
|
||||
Read more about changing the :ref:`general<typo3.cms.form.prototypes.\<prototypeidentifier>.formengine>`
|
||||
and :ref:`aspect-specific form plugin configuration<typo3.cms.form.prototypes.\<prototypeIdentifier>.finishersdefinition.\<finisheridentifier>.formengine>`.
|
||||
|
||||
|
||||
.. _concepts-formplugin-translation-formengine:
|
||||
|
||||
Translation of form plugin
|
||||
--------------------------
|
||||
|
||||
All option values which reside below the following configuration keys can be
|
||||
translated:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
finishersDefinition:
|
||||
<finisherIdentifier>
|
||||
formEngine:
|
||||
|
||||
The translation files of the ``form plugin`` are loaded as follows:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formEngine:
|
||||
translationFile:
|
||||
# translation files for the form plugin (finisher overrides)
|
||||
10: 'EXT:form/Resources/Private/Language/Database.xlf'
|
||||
20: 'EXT:my_site_package/Resources/Private/Language/Database.xlf'
|
||||
|
||||
The process searches for each option value within all of the defined
|
||||
translation files. If a translation is found, the translated option value
|
||||
will be used in preference.
|
||||
|
||||
Imagine, the following is defined for an option value:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
...
|
||||
label: 'tt_content.finishersDefinition.EmailToReceiver.label'
|
||||
...
|
||||
|
||||
First of all, the process searches for the translation key
|
||||
``tt_content.finishersDefinition.EmailToReceiver.label`` within the file
|
||||
20: 'EXT:my_site_package/Resources/Private/Language/Database.xlf' and after
|
||||
it inside the file 10: 'EXT:form/Resources/Private/Language/Database.xlf'.
|
||||
If nothing is found, the option value will be displayed unmodified.
|
||||
|
||||
Due to compatibility issues, the setting ``translationFile`` is not defined
|
||||
as an array in the default configuration. To load your own translation files,
|
||||
you should define an array containing 'EXT:form/Resources/Private/Language/Database.xlf'
|
||||
as first entry (key ``10``) followed by your own file (key ``20``) as
|
||||
displayed in the example above.
|
||||
@@ -0,0 +1,878 @@
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _concepts-frontendrendering:
|
||||
|
||||
==================
|
||||
Frontend rendering
|
||||
==================
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents:
|
||||
|
||||
Basic code components
|
||||
=====================
|
||||
|
||||
.. figure:: ../../Images/basic_code_components.png
|
||||
:alt: Basic code components
|
||||
|
||||
Basic code components
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents-formdefinition:
|
||||
|
||||
TYPO3\\CMS\\Form\\Domain\\Model\\FormDefinition
|
||||
-----------------------------------------------
|
||||
|
||||
The class ``TYPO3\CMS\Form\Domain\Model\FormDefinition`` encapsulates
|
||||
a complete ``form definition``, with all of its
|
||||
|
||||
- pages,
|
||||
- form elements,
|
||||
- applicable validation rules, and
|
||||
- finishers, which should be executed when the form is submitted.
|
||||
|
||||
The FormDefinition domain model is not modified when the form is executed.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents-formdefinition-anatomy:
|
||||
|
||||
The anatomy of a form
|
||||
"""""""""""""""""""""
|
||||
|
||||
A ``FormDefinition`` domain model consists of multiple ``Page`` objects.
|
||||
When a form is displayed, only one ``Page`` is visible at any given time.
|
||||
Moreover, there is a navigation to go back and forth between those pages. A
|
||||
``Page`` consists of multiple ``FormElements`` which represent the input
|
||||
fields, textareas, checkboxes, etc. shown on a page. The ``FormDefinition``
|
||||
domain model, ``Page`` and ``FormElement`` objects have ``identifier``
|
||||
properties which must be unique for each given ``<formElementTypeIdentifier>``,
|
||||
i.e. the ``FormDefinition`` domain model and a ``FormElement`` object may
|
||||
have the same ``identifier`` but having the same identifier for two
|
||||
``FormElement`` objects is disallowed.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents-formdefinition-anatomy-example:
|
||||
|
||||
Example
|
||||
'''''''
|
||||
|
||||
Basically, you can manually create a ``FormDefinition`` domain model just
|
||||
by calling the API methods on it, or you can use a ``FormFactory`` to build
|
||||
the form from a different representation format such as YAML::
|
||||
|
||||
$formDefinition = $this->objectManager->get(FormDefinition::class, 'myForm');
|
||||
|
||||
$page1 = $this->objectManager->get(Page::class, 'page1');
|
||||
$formDefinition->addPage($page);
|
||||
|
||||
// second argument is the <formElementTypeIdentifier> of the form element
|
||||
$element1 = $this->objectManager->get(GenericFormElement::class, 'title', 'Text');
|
||||
$page1->addElement($element1);
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents-formdefinition-createformusingabstracttypes:
|
||||
|
||||
Creating a form using abstract form element types
|
||||
"""""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
While you can use the ``TYPO3\CMS\Form\Domain\Model\FormDefinition::addPage()``
|
||||
or ``TYPO3\CMS\Form\Domain\Model\FormElements\Page::addElement()`` methods
|
||||
and create the ``Page`` and ``FormElement`` objects manually, it is often
|
||||
better to use the corresponding create* methods (``TYPO3\CMS\Form\Domain\Model\FormDefinition::createPage()``
|
||||
and ``TYPO3\CMS\Form\Domain\Model\FormElements\Page::createElement()``), as
|
||||
you pass them an abstract ``<formElementTypeIdentifier>`` such as ``Text``
|
||||
or ``Page``. EXT:form will automatically resolve the implementation class
|
||||
name and set default values.
|
||||
|
||||
The :ref:`simple example <concepts-frontendrendering-basiccodecomponents-formdefinition-anatomy-example>`
|
||||
shown above should be rewritten as follows::
|
||||
|
||||
// we will come back to this later on
|
||||
$prototypeConfiguration = [];
|
||||
|
||||
$formDefinition = $this->objectManager->get(FormDefinition::class, 'myForm', $prototypeConfiguration);
|
||||
$page1 = $formDefinition->createPage('page1');
|
||||
$element1 = $page1->addElement('title', 'Text');
|
||||
|
||||
You might wonder how the system knows that the element ``Text`` is
|
||||
implemented by using a ``GenericFormElement``. This is configured in the
|
||||
``$prototypeConfiguration``. To make the example from above actually work,
|
||||
we need to add some meaningful values to ``$prototypeConfiguration``::
|
||||
|
||||
$prototypeConfiguration = [
|
||||
'formElementsDefinition' => [
|
||||
'Page' => [
|
||||
'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page'
|
||||
],
|
||||
'Text' => [
|
||||
'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement'
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
For each abstract ``<formElementTypeIdentifier>`` we have to add some
|
||||
configuration. In the snippet above, we only define the ``implementation
|
||||
class name``. Apart form that, it is always possible to set default values
|
||||
for all configuration options of such elements, as the following example
|
||||
shows::
|
||||
|
||||
$prototypeConfiguration = [
|
||||
'formElementsDefinition' => [
|
||||
'Page' => [
|
||||
'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page',
|
||||
'label' => 'This is the label of the page if nothing else is specified'
|
||||
],
|
||||
'Text' => [
|
||||
'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement',
|
||||
'label' = >'Default Label',
|
||||
'defaultValue' => 'Default form element value',
|
||||
'properties' => [
|
||||
'placeholder' => 'Text that is shown if element is empty'
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents-formdefinition-preconfiguredconfiguration:
|
||||
|
||||
Using pre-configured $prototypeConfiguration
|
||||
""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Often, it does not make sense to manually create the $prototypeConfiguration
|
||||
array. Bigger parts of this array are pre-configured in the extensions's
|
||||
YAML settings. The ``TYPO3\CMS\Form\Domain\Configuration\ConfigurationService``
|
||||
contains helper methods which return the ready-to-use ``$prototypeConfiguration``.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents-formdefinition-rednering:
|
||||
|
||||
Rendering a FormDefinition
|
||||
""""""""""""""""""""""""""
|
||||
|
||||
To trigger the rendering of a ``FormDefinition`` domain model, the current
|
||||
``TYPO3\CMS\Extbase\Mvc\Web\Request`` needs to be bound to the
|
||||
``FormDefinition``. This binding results in a ``TYPO3\CMS\Form\Domain\Runtime\FormRuntime``
|
||||
object which contains the ``Runtime State`` of the form. Among other things,
|
||||
this object includes the currently inserted values::
|
||||
|
||||
// $currentRequest and $currentResponse need to be available
|
||||
// inside a controller, you would use $this->request and $this->response;
|
||||
$form = $formDefinition->bind($currentRequest, $currentResponse);
|
||||
// now, you can use the $form object to get information about the currently entered values, etc.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents-formruntime:
|
||||
|
||||
TYPO3\\CMS\\Form\\Domain\\Runtime\\FormRuntime
|
||||
----------------------------------------------
|
||||
|
||||
This class implements the runtime logic of a form, i.e. the class
|
||||
|
||||
- decides which page is currently shown,
|
||||
- determines the current values of the form
|
||||
- triggers validation and property mappings.
|
||||
|
||||
You generally receive an instance of this class by calling ``TYPO3\CMS\Form\Domain\Model\FormDefinition::bind()``.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents-formruntime-render:
|
||||
|
||||
Rendering a form
|
||||
""""""""""""""""
|
||||
|
||||
Rendering a form is easy. Just call ``render()`` on the ``FormRuntime``::
|
||||
|
||||
$form = $formDefinition->bind($request, $response);
|
||||
$renderedForm = $form->render();
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents-formruntime-accessingformvalues:
|
||||
|
||||
Accessing form values
|
||||
"""""""""""""""""""""
|
||||
|
||||
In order to get the values the user has entered into the form, you can
|
||||
access the ``FormRuntime`` object like an array. If a form element with the
|
||||
identifier ``firstName`` exists, you can use ``$form['firstName']`` to
|
||||
retrieve its current value. You can set values the same way.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents-formruntime-renderinginternals:
|
||||
|
||||
Rendering internals
|
||||
"""""""""""""""""""
|
||||
|
||||
The ``FormRuntime`` inquires the ``FormDefinition`` domain model regarding
|
||||
the configured renderer (``TYPO3\CMS\Form\Domain\Model\FormDefinition::getRendererClassName()``)
|
||||
and then triggers render() on this Renderer.
|
||||
|
||||
This allows you to declaratively define how a form should be rendered.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formElementsDefinition:
|
||||
Form:
|
||||
rendererClassName: 'TYPO3\CMS\Form\Domain\Renderer\FluidFormRenderer'
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-basiccodecomponents-fluidformrenderer:
|
||||
|
||||
TYPO3\\CMS\\Form\\Domain\\Renderer\\FluidFormRenderer
|
||||
-----------------------------------------------------
|
||||
|
||||
This class is a ``TYPO3\CMS\Form\Domain\Renderer\RendererInterface``
|
||||
implementation which used to render a ``FormDefinition`` domain model. It
|
||||
is the default EXT:form renderer.
|
||||
|
||||
Learn more about the :ref:`FluidFormRenderer Options<apireference-frontendrendering-fluidformrenderer-options>`.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-codecomponents-customformelementimplementations:
|
||||
|
||||
Custom form element implementations
|
||||
-----------------------------------
|
||||
|
||||
EXT:form ships a decent amount of hooks which are available at crucial
|
||||
points of the life cycle of a ``FormElement``. Most of the time, own
|
||||
implementations are therefore unnecessary. An own form element can be
|
||||
defined by:
|
||||
|
||||
- writing some configuration, and
|
||||
- utilizing the standard implementation of ``TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement``.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formElementsDefinition:
|
||||
CustomFormElementIdentifier:
|
||||
implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement'
|
||||
|
||||
With the provided hooks, this ``FormElement`` can now be manipulated.
|
||||
|
||||
If you insist on your own implementation, the abstract class ``TYPO3\CMS\Form\Domain\Model\FormElements\AbstractFormElement``
|
||||
offers a perfect entry point. In addition, we recommend checking-out ``TYPO3\CMS\Form\Domain\Model\Renderable\AbstractRenderable``.
|
||||
All of your own form element implementations must be programmed to the
|
||||
interface ``TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface``.
|
||||
It is a good idea to derive your implementation from ``TYPO3\CMS\Form\Domain\Model\FormElements\AbstractFormElement``.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-renderviewHelper:
|
||||
|
||||
"render" viewHelper
|
||||
===================
|
||||
|
||||
The ``RenderViewHelper`` is the actual starting point for form rendering and
|
||||
not the typical Extbase Controller as you may know it.
|
||||
|
||||
For more technical insights read more about the viewHelper's :ref:`arguments<apireference-frontendrendering-renderviewHelper-arguments>`.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-fluidtemplate:
|
||||
|
||||
Render through FLUIDTEMPLATE (without controller)
|
||||
-------------------------------------------------
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
tt_content.custom_content_element = COA_INT
|
||||
tt_content.custom_content_element {
|
||||
10 = < lib.stdheader
|
||||
20 = FLUIDTEMPLATE
|
||||
20 {
|
||||
file = EXT:my_site_package/Resources/Private/Templates/CustomContentElement.html
|
||||
settings {
|
||||
persistenceIdentifier = EXT:my_site_package/Resources/Private/Forms/MyForm.yaml
|
||||
}
|
||||
extbase.pluginName = Formframework
|
||||
extbase.controllerExtensionName = Form
|
||||
extbase.controllerName = FormFrontend
|
||||
extbase.controllerActionName = perform
|
||||
}
|
||||
}
|
||||
|
||||
``my_site_package/Resources/Private/Templates/CustomContentElement.html``:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<formvh:render persistenceIdentifier="{settings.persistenceIdentifier}" />
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-extbase:
|
||||
|
||||
Render within your own Extbase extension
|
||||
----------------------------------------
|
||||
|
||||
It is straight forward. Use the ``RenderViewHelper`` like this and you are
|
||||
done:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<formvh:render persistenceIdentifier="EXT:my_site_package/Resources/Private/Forms/MyForm.yaml"/>
|
||||
|
||||
Point the property ``controllerAction`` to the desired action name and
|
||||
provide values for the other parameters displayed below (you might need
|
||||
those).
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
type: Form
|
||||
identifier: 'example-form'
|
||||
label: 'TYPO3 is cool'
|
||||
prototypeName: standard
|
||||
renderingOptions:
|
||||
controllerAction: perform
|
||||
addQueryString: false
|
||||
argumentsToBeExcludedFromQueryString: []
|
||||
additionalParams: []
|
||||
|
||||
renderables:
|
||||
...
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-programmatically:
|
||||
|
||||
Build forms programmatically
|
||||
============================
|
||||
|
||||
To learn more about this topic, head to the chapter ':ref:`Build forms programmatically<apireference-frontendrendering-programmatically>`'
|
||||
which is part of the API reference section.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-runtimemanipulation:
|
||||
|
||||
Runtime manipulation
|
||||
====================
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-runtimemanipulation-hooks:
|
||||
|
||||
Hooks
|
||||
-----
|
||||
|
||||
EXT:form implements a decent amount of hooks that allow the manipulation of
|
||||
your forms during runtime. In this way, it is possible to, for example,
|
||||
|
||||
- ... prefill form elements with values from your database,
|
||||
- ... skip a whole page based on the value of a certain form element,
|
||||
- ... mark a form element as mandatory depending of the chosen value of another
|
||||
form element.
|
||||
|
||||
Please check out the ':ref:`API reference section<apireference-frontendrendering-runtimemanipulation-hooks>`'
|
||||
for more details.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-runtimemanipulation-typoscriptoverrides:
|
||||
|
||||
TypoScript overrides
|
||||
--------------------
|
||||
|
||||
Each and every ``form definition`` can be overridden via TypoScript if the
|
||||
``FormFrontendController`` of EXT:form is used to render the form. Normally,
|
||||
this is the case if the form has been added to the page using the form
|
||||
plugin or when rendering the form via :ref:`FLUIDTEMPLATE <concepts-frontendrendering-fluidtemplate>`.
|
||||
|
||||
The overriding of settings with TypoScript's help takes place after the :ref:`custom finisher settings<concepts-formplugin>`
|
||||
of the form plugin have been loaded. In this way, you are able to manipulate
|
||||
the ``form definition`` for a single page. In doing so, the altered
|
||||
``form definition`` is passed to the ``RenderViewHelper`` which then
|
||||
generates the form programmatically. At this point, you can still change the
|
||||
form elements using the above-mentioned concept of :ref:`hooks<concepts-frontendrendering-runtimemanipulation-hooks>`.
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
plugin.tx_form {
|
||||
settings {
|
||||
formDefinitionOverrides {
|
||||
<formDefinitionIdentifier> {
|
||||
renderables {
|
||||
0 {
|
||||
renderables {
|
||||
0 {
|
||||
label = TEXT
|
||||
label.value = Overridden label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-templates:
|
||||
|
||||
Templates
|
||||
=========
|
||||
|
||||
The Fluid templates of the form framework are based on Twitter Bootstrap.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-templates-customtemplates:
|
||||
|
||||
Custom templates
|
||||
----------------
|
||||
|
||||
If you want to use custom Fluid templates for the frontend output of the
|
||||
form elements, you cannot register an additional template path using
|
||||
TypoScript. Instead, the registration of new template paths has to be done
|
||||
via YAML. The settings are part of the ``prototypes`` configuration.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formElementsDefinition:
|
||||
Form:
|
||||
renderingOptions:
|
||||
templateRootPaths:
|
||||
100: 'EXT:my_site_package/Resources/Private/Frontend/Templates/'
|
||||
partialRootPaths:
|
||||
100: 'EXT:my_site_package/Resources/Private/Frontend/Partials/'
|
||||
layoutRootPaths:
|
||||
100: 'EXT:my_site_package/Resources/Private/Frontend/Layouts/'
|
||||
|
||||
For each ``form definition`` - which references the prototype ``standard`` -
|
||||
the form framework will additionally look for Fluid templates within the
|
||||
path 'EXT:my_site_package/Resources/Private/Frontend/[*]' as set above.
|
||||
Apart from the 'Form' element, the process will search for templates within
|
||||
the ``partialRootPaths`` folder. The name of the partial is derived from the
|
||||
property ``formElementTypeIdentifier``. For example, the template of the
|
||||
form element ``Text`` must be stored within the ``partialRootPaths`` folder
|
||||
named ``Text.html``. In contrast, the template of the ``Form`` element must
|
||||
reside within the ``templateRootPaths`` folder. According to the introduced
|
||||
logic, the template name must be ``Form.html``.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-translation:
|
||||
|
||||
Translation
|
||||
===========
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-translation-formdefinition:
|
||||
|
||||
Translate form definition
|
||||
-------------------------
|
||||
|
||||
The translation of ``form definitions`` works differently to the translation
|
||||
of the backend aspects. Currently, there is no graphical user interface
|
||||
supporting the translation process.
|
||||
|
||||
If the backend editor needed to translate the ``form definition`` properties
|
||||
in the same way the backend aspects are translated, he/ she would see long
|
||||
and unwieldy translation keys while editing a form within the ``form editor``.
|
||||
In order to avoid this, rather the element properties are translated than
|
||||
their values. Thus, the form framework does not look for translation keys
|
||||
within the translation file. Instead, the system searches for translations
|
||||
of the form element properties independent of their property values. The
|
||||
property values are ignored if the process finds a proper entry within the
|
||||
translation file. As a result, the property values are overridden by the
|
||||
translated value.
|
||||
|
||||
This approach is a compromise between two scenarios: the exclusive usage of
|
||||
the ``form editor`` and/ or the manual creation of ``form definitions``
|
||||
which can afterwards (theoretically) be edited with the ``form editor``. In
|
||||
addition, the described compromise allows the editor to create forms in the
|
||||
default language whose form element property values are displayed as
|
||||
specified in the ``form editor``. Based on this, an integrator could provide
|
||||
additional language files which automatically translate the specific form.
|
||||
|
||||
Additional translation files can be defined as follows:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
formElementsDefinition:
|
||||
Form:
|
||||
renderingOptions:
|
||||
translation:
|
||||
translationFile:
|
||||
# translation files for the frontend
|
||||
10: 'EXT:form/Resources/Private/Language/locallang.xlf'
|
||||
20: 'EXT:my_site_package/Resources/Private/Language/Form/locallang.xlf'
|
||||
|
||||
Due to compatibility issues, the setting ``translationFile`` is not defined
|
||||
as an array in the default configuration. To load your own translation files,
|
||||
you should define an array containing 'EXT:form/Resources/Private/Language/Form/locallang.xlf'
|
||||
as first entry (key ``10``) followed by your own file (key ``20``) as
|
||||
displayed in the example above. The array is processed from the highest key
|
||||
to the lowest, i.e. your translation file with the key ``20`` is processed
|
||||
first. If the look-up process does not find a key within all of the provided
|
||||
files, the property value will be displayed unmodified.
|
||||
|
||||
The following properties can be translated:
|
||||
|
||||
- label
|
||||
- properties.[*]
|
||||
- properties.options.[*]
|
||||
- properties.fluidAdditionalAttributes.[*]
|
||||
- renderingOptions.[*]
|
||||
|
||||
The translation keys are put together based on a specific pattern. In
|
||||
addition, a fallback chain that depends on the form element identifiers
|
||||
exists. As a result, the following translation scenarios are possible:
|
||||
|
||||
- translation of a form element property for a specific form and form
|
||||
element
|
||||
- translation of a form element property for a specific form element and
|
||||
various forms
|
||||
- translation of a form element property for an element type and various
|
||||
forms, e.g. the ``Page`` element
|
||||
|
||||
The look-up process searches for translation keys in all given translation
|
||||
files based on the following order:
|
||||
|
||||
- ``<formDefinitionIdentifier>.element.<elementIdentifier>.properties.<propertyName>``
|
||||
- ``element.<formElementIdentifier>.properties.<propertyName>``
|
||||
- ``element.<elementType>.properties.<propertyName>``
|
||||
|
||||
Form elements with option properties (``properties.options``), like the
|
||||
``Select`` element, feature the following look-up process:
|
||||
|
||||
- ``<formDefinitionIdentifier>.element.<elementIdentifier>.properties.options.<propertyValue>``
|
||||
- ``element.<elementIdentifier>.properties.options.<propertyValue>``
|
||||
|
||||
|
||||
Example
|
||||
"""""""
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
identifier: ApplicationForm
|
||||
type: Form
|
||||
prototypeName: standard
|
||||
label: 'Application form'
|
||||
|
||||
renderables:
|
||||
-
|
||||
identifier: GeneralInformation
|
||||
type: Page
|
||||
label: 'General information'
|
||||
|
||||
renderables:
|
||||
-
|
||||
identifier: LastName
|
||||
type: Text
|
||||
label: 'Last name'
|
||||
properties:
|
||||
placeholder: 'Please enter your last name.'
|
||||
defaultValue: ''
|
||||
-
|
||||
identifier: Software
|
||||
type: MultiSelect
|
||||
label: 'Known software'
|
||||
properties:
|
||||
options:
|
||||
value1: TYPO3
|
||||
value2: Neos
|
||||
|
||||
For the form element ``LastName``, the process will look for the following
|
||||
translation keys within the translation files:
|
||||
|
||||
- ``ApplicationForm.element.LastName.properties.label``
|
||||
- ``element.LastName.properties.label``
|
||||
- ``element.Text.properties.label``
|
||||
|
||||
If none of the above-mentioned keys exist, 'Last name' will be displayed.
|
||||
|
||||
For the form element ``Software``, the process will look for the following
|
||||
translation keys within the translation files:
|
||||
|
||||
- ``ApplicationForm.element.Software.properties.label``
|
||||
- ``element.Software.properties.label``
|
||||
- ``element.MultiSelect.properties.label``
|
||||
|
||||
If none of the above-mentioned keys exist, 'Known software' will be
|
||||
displayed. The option properties are addressed as follows:
|
||||
|
||||
- ``ApplicationForm.element.Software.properties.options.value1``
|
||||
- ``element.Software.properties.options.value1``
|
||||
- ``ApplicationForm.element.Software.properties.options.value2``
|
||||
- ``element.Software.properties.options.value2``
|
||||
|
||||
If none of the above-mentioned keys exist, 'TYPO3' will be displayed as
|
||||
label for the first option and 'Neos' as label for the second option.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-translation-validationerrors:
|
||||
|
||||
Translation of validation messages
|
||||
----------------------------------
|
||||
|
||||
The translation of validation messages is similar to the translation of
|
||||
``form definitions``. The same translation files can be used. If the look-up
|
||||
process does not find a key within the provided files, the appropriate
|
||||
message of the Extbase framework will be displayed. EXT:form already
|
||||
translates all of those validators by default.
|
||||
|
||||
As mentioned above, the translation keys are put together based on a
|
||||
specific pattern. Furthermore, the fallback chain exists here as well. Thus,
|
||||
the following translation scenarios are possible:
|
||||
|
||||
- translation of validation messages for a specific validator of a concrete
|
||||
form element and concrete form
|
||||
- translation of validation messages for a specific validator of various
|
||||
form elements within a concrete form
|
||||
- translation of validation messages for a specific validator of a concrete
|
||||
form element in various forms
|
||||
- translation of validation messages for a specific validator within various
|
||||
forms
|
||||
|
||||
In Extbase, the validation messages are identified with the help of
|
||||
numerical codes (UNIX timestamps). For the same validator, different codes
|
||||
are valid. Read more about :ref:`concrete validator configurations <typo3.cms.form.prototypes.\<prototypeidentifier>.validatorsdefinition.\<validatoridentifier>-concreteconfigurations>`.
|
||||
|
||||
The look-up process searches for translation keys in all given translation
|
||||
files based on the following order:
|
||||
|
||||
- ``<formDefinitionIdentifier>.validation.error.<elementIdentifier>.<validationErrorCode>``
|
||||
- ``<formDefinitionIdentifier>.validation.error.<validationErrorCode>``
|
||||
- ``validation.error.<elementIdentifier>.<validationErrorCode>``
|
||||
- ``validation.error.<validationErrorCode>``
|
||||
|
||||
|
||||
Example
|
||||
"""""""
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
identifier: ContactForm
|
||||
type: Form
|
||||
prototypeName: standard
|
||||
label: 'Contact us'
|
||||
|
||||
renderables:
|
||||
-
|
||||
identifier: Page1
|
||||
type: Page
|
||||
label: 'Page 1'
|
||||
|
||||
renderables:
|
||||
-
|
||||
identifier: LastName
|
||||
type: Text
|
||||
label: 'Last name'
|
||||
properties:
|
||||
fluidAdditionalAttributes:
|
||||
required: required
|
||||
validators:
|
||||
-
|
||||
identifier: NotEmpty
|
||||
|
||||
Amongst others, the ``NotEmpty`` validator sends 1221560910 as ``<validationErrorCode>``.
|
||||
If a user submits this form without providing a value for the field "Last
|
||||
name", the ``NotEmpty`` validator fails. Now, the look-up process searches
|
||||
for the following translation keys for the ``NotEmpty`` validator combined
|
||||
with the form element ``LastName``:
|
||||
|
||||
- ContactForm.validation.error.LastName.1221560910
|
||||
- ContactForm.validation.error.1221560910
|
||||
- validation.error.LastName.1221560910
|
||||
- validation.error.1221560910
|
||||
|
||||
As mentioned above, if there is no corresponding translation key available,
|
||||
the default message of the Extbase framework will be shown.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-translation-finishers:
|
||||
|
||||
Translation of finisher options
|
||||
-------------------------------
|
||||
|
||||
The translation of finisher options is similar to the translation of
|
||||
``form definitions``. The same translation files can be used. If the look-up
|
||||
process does not find a key within all provided files, the property value
|
||||
will be displayed unmodified.
|
||||
|
||||
As mentioned above, the translation keys are put together based on a
|
||||
specific pattern. Furthermore, the fallback chain exists here as well. Thus,
|
||||
the following translation scenarios are possible:
|
||||
|
||||
- translation of finisher options for a specific finisher of a concrete form
|
||||
- translation of finisher options for a specific finisher of various forms
|
||||
|
||||
The look-up process searches for translation keys in all given translation
|
||||
files based on the following order:
|
||||
|
||||
- ``<formDefinitionIdentifier>.finisher.<finisherIdentifier>.<optionName>``
|
||||
- ``finisher.<finisherIdentifier>.<optionName>``
|
||||
|
||||
|
||||
Example
|
||||
"""""""
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
identifier: ContactForm
|
||||
type: Form
|
||||
prototypeName: standard
|
||||
label: 'Contact us'
|
||||
|
||||
finishers:
|
||||
-
|
||||
identifier: Confirmation
|
||||
options:
|
||||
message: 'Thank you for your inquiry.'
|
||||
|
||||
renderables:
|
||||
...
|
||||
|
||||
The look-up process searches for the following translation keys for the
|
||||
``<finisherIdentifier>`` 'Confirmation' and the option 'message':
|
||||
|
||||
- ``ContactForm.finisher.Confirmation.message``
|
||||
- ``finisher.Confirmation.message``
|
||||
|
||||
If no translation key exists, the message 'Thank you for your inquiry.' will
|
||||
be shown.
|
||||
|
||||
|
||||
.. _concepts-frontendrendering-translation-arguments:
|
||||
|
||||
Form element translation arguments are supported
|
||||
================================================
|
||||
|
||||
Form element property translations and finisher option translations can use
|
||||
placeholders to output translation arguments. Translations can be enriched
|
||||
with variable values by passing arguments to form element properties. The
|
||||
feature was introduced with :issue:`81363`.
|
||||
|
||||
|
||||
Form element properties
|
||||
-----------------------
|
||||
|
||||
Pure YAML is sufficient to add simple, static values:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
renderables:
|
||||
- identifier: field-with-translation-arguments
|
||||
type: Checkbox
|
||||
label: This is a %s feature
|
||||
renderingOptions:
|
||||
translation:
|
||||
translationFile: path/to/locallang.xlf
|
||||
arguments:
|
||||
label:
|
||||
- useful
|
||||
|
||||
This will produce the label: `This is a useful feature`.
|
||||
|
||||
Alternatively, translation arguments can be set via
|
||||
:ts:`formDefinitionOverrides` in TypoScript. A common usecase is a checkbox for
|
||||
user confirmation linking to details of the topic. Here it makes sense to use
|
||||
YAML hashes instead of YAML lists to give sections named keys. This simplifies
|
||||
references in TypoScript a lot since named keys are way more readable and also
|
||||
keep the setup working in case elements are reordered. With lists and numeric
|
||||
keys the TypoScript setup would also need to be updated in this case.
|
||||
|
||||
In the following example the list of :yaml:`renderables` has been replaced with
|
||||
a hash of :yaml:`renderables` and the field :yaml:`field-with-translation-arguments`
|
||||
has received a named key :yaml:`fieldWithTranslationArguments`. This key can be anything
|
||||
as long as it is unique on the same level, usually simply copying the :yaml:`identifier`
|
||||
should be enough:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
renderables:
|
||||
fieldWithTranslationArguments:
|
||||
identifier: field-with-translation-arguments
|
||||
type: Checkbox
|
||||
label: I agree to the <a href="%s">terms and conditions</a>
|
||||
renderingOptions:
|
||||
translation:
|
||||
translationFile: path/to/locallang.xlf
|
||||
|
||||
The following TypoScript setup uses the named key :yaml:`fieldWithTranslationArguments` to refer
|
||||
to the field and adds a page URL as translation argument:
|
||||
|
||||
.. code-block:: typoscript
|
||||
|
||||
plugin.tx_form {
|
||||
settings {
|
||||
formDefinitionOverrides {
|
||||
<form-id> {
|
||||
renderables {
|
||||
0 {
|
||||
# Page
|
||||
renderables {
|
||||
fieldWithTranslationArguments {
|
||||
renderingOptions {
|
||||
translation {
|
||||
arguments {
|
||||
label {
|
||||
0 = TEXT
|
||||
0.typolink {
|
||||
# Terms and conditions page, could be
|
||||
# set also via TypoScript constants
|
||||
parameter = 42
|
||||
returnLast = url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
The :yaml:`Page` element of the form definition was not registered with a named key so a numeric
|
||||
key :yaml:`0` must be used which, as mentioned above, is prone to errors when more pages are added
|
||||
or pages are reordered.
|
||||
|
||||
.. important::
|
||||
|
||||
There must be at least one translation file with a translation for the
|
||||
configured form element property. Arguments are not inserted into default
|
||||
values defined in a form definition.
|
||||
|
||||
|
||||
Finishers
|
||||
---------
|
||||
|
||||
The same mechanism (YAML, YAML + TypoScript) works for finisher options:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
finishers:
|
||||
finisherWithTranslationArguments:
|
||||
identifier: EmailToReceiver
|
||||
options:
|
||||
subject: My %s subject
|
||||
recipientAddress: foo@example.org
|
||||
senderAddress: bar@example.org
|
||||
translation:
|
||||
translationFile: path/to/locallang.xlf
|
||||
arguments:
|
||||
subject:
|
||||
- awesome
|
||||
|
||||
This will produce `My awesome subject`.
|
||||
@@ -0,0 +1,26 @@
|
||||
.. include:: ../Includes.txt
|
||||
|
||||
|
||||
.. _concepts:
|
||||
|
||||
========
|
||||
Concepts
|
||||
========
|
||||
|
||||
Within this chapter, you will learn the basic concepts of the form framework.
|
||||
It addresses your concerns as backend editor and integrator. Some of the
|
||||
chapters also cover topics for developers.
|
||||
|
||||
.. toctree::
|
||||
|
||||
TargetGroupsAndMainPrinciples/Index
|
||||
Configuration/Index
|
||||
FormConfigurationFormDefinition/Index
|
||||
FormFileStorages/Index
|
||||
FrontendRendering/Index
|
||||
Variants/Index
|
||||
Validators/Index
|
||||
Finishers/Index
|
||||
FormManager/Index
|
||||
FormEditor/Index
|
||||
FormPlugin/Index
|
||||
@@ -0,0 +1,48 @@
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _concepts-introduction:
|
||||
|
||||
Target groups and main principles
|
||||
=================================
|
||||
|
||||
As :ref:`mentioned earlier<what-does-it-do>`, the ``form`` extension can be
|
||||
seen as a framework which allows editors, integrators, and developers to
|
||||
create and manage all kind of forms. For this task, different interfaces
|
||||
and techniques are available.
|
||||
|
||||
Conceptually, EXT:form always tries to consider the ``form editor`` first.
|
||||
The requirements for the ``form editor`` differ between the defined target
|
||||
groups. On the one hand, as an integrator, you may want to manage HTML
|
||||
class attributes. On the other hand, as a developer you may want to use the
|
||||
``form editor`` as a kick starter for complex ``form definitions``, and you
|
||||
may want to edit all possible (technical) properties you can think of.
|
||||
|
||||
The form extension tries to find a compromise for such cases. Since the
|
||||
``form editor`` is mainly used by backend editors, only simple,
|
||||
nontechnical properties are displayed and editable. However, EXT:form
|
||||
allows you to easily extend the ``form editor`` by writing some YAML
|
||||
configurations.
|
||||
|
||||
If this is not enough for your specific project, EXT:form provides a way to
|
||||
integrate your own JavaScript code by utilizing the JavaScript API. Thus,
|
||||
it should be possible to meet all your requirements.
|
||||
|
||||
Your forms can be created and defined globally in the ``form module`` and/
|
||||
or loaded from extensions. Within the ``Mail form`` content element, one of
|
||||
those forms can be referenced.
|
||||
|
||||
Furthermore, certain aspects of a form can be overridden in the plugin. This
|
||||
concept allows you to reuse the same form on different pages with the same,
|
||||
or a different, configuration.
|
||||
|
||||
The following explanations will show you that there are many ways to
|
||||
manipulate the form framework in different contexts.
|
||||
|
||||
Those explanations are partly contradictory, depending on your use case. It
|
||||
is up to you how you want to use the form framework. Be creative and share
|
||||
your solution with the TYPO3 community!
|
||||
|
||||
This chapter attempts to describe the basics of the form framework. Check
|
||||
out the reference and the example sections to get a deeper understanding of
|
||||
the framework.
|
||||
@@ -0,0 +1,296 @@
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _concepts-validators:
|
||||
|
||||
Validators
|
||||
==========
|
||||
|
||||
The form framework ships a set of server-side validators, which implement
|
||||
Extbase validators. Be aware that not all of the existing validators are
|
||||
available for each form element, e.g. the "Date range validator" is
|
||||
only available for the "Date" element. Furthermore, some form elements
|
||||
(like "Email") already contain reasonable validators.
|
||||
|
||||
With the help of the form element property ``validationErrorMessages``
|
||||
you can define custom validation error messages. The message can also
|
||||
be set within the form editor.
|
||||
|
||||
|
||||
.. _concepts-validators-client-side-validation:
|
||||
|
||||
Client-side validation
|
||||
----------------------
|
||||
|
||||
If a form element is configured accordingly, the form framework adds
|
||||
HTML 5 based frontend validation. Nevertheless, there is no JavaScript
|
||||
validation included by default. We as the TYPO3 core have no plans to
|
||||
opt for a specific solution. This has to be integrated manually.
|
||||
Reliable and maintained projects are `Parsley <https://github.com/guillaumepotier/Parsley.js>`_
|
||||
and `jQuery Validation <https://github.com/jquery-validation/jquery-validation>`_.
|
||||
|
||||
|
||||
.. _concepts-validators-localization-client-side-validations:
|
||||
|
||||
Localization of client side validations
|
||||
"""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
The displayed validation message is a browser specific text. The output
|
||||
is not generated by TYPO3 and therefore you cannot change it easily.
|
||||
Nevertheless, there is a JavaScript solution for changing the validation
|
||||
message. See `Stack Overflow <http://stackoverflow.com/questions/5272433/html5-form-required-attribute-set-custom-validation-message>`_
|
||||
for more information.
|
||||
|
||||
|
||||
.. _concepts-validators-server-side-validation:
|
||||
|
||||
Server-side validation
|
||||
----------------------
|
||||
|
||||
|
||||
.. _concepts-validators-alphanumeric:
|
||||
|
||||
Alphanumeric validator
|
||||
""""""""""""""""""""""
|
||||
|
||||
The 'Alphanumeric validator' checks for alphanumeric strings.
|
||||
Alphanumeric is defined as a combination of alphabetic and numeric
|
||||
characters [A-Z + 0-9].
|
||||
|
||||
|
||||
.. _concepts-validators-date_time:
|
||||
|
||||
Date/ time validator
|
||||
""""""""""""""""""""
|
||||
|
||||
The 'Date/ time validator' checks if the given value is a valid DateTime
|
||||
object. The date string is expected to be formatted according to the `W3C
|
||||
standard <http://www.w3.org/TR/NOTE-datetime.html>`_ which is
|
||||
"YYYY-MM-DDT##:##:##+##:##", for example "2005-08-15T15:52:01+00:00".
|
||||
|
||||
|
||||
.. _concepts-validators-date_range:
|
||||
|
||||
Date range validator
|
||||
""""""""""""""""""""
|
||||
|
||||
The 'Date range validator' checks if the given value is a valid DateTime
|
||||
object and in-between a specified date range. The range can be defined by
|
||||
providing a minimum and/ or maximum date. The validator has 2 options:
|
||||
|
||||
- Minimum date: The minimum date.
|
||||
- Maximum date: The maximum date.
|
||||
|
||||
The properties ``minimum`` and ``maximum`` must have the format 'Y-m-d' which
|
||||
represents the RFC 3339 'full-date' format.
|
||||
|
||||
Read more: https://www.w3.org/TR/2011/WD-html-markup-20110405/input.date.html
|
||||
|
||||
The input must be a DateTime object. This input can be tested against a minimum
|
||||
date and a maximum date. The minimum date and the maximum date are strings. The minimum
|
||||
date and the maximum date can be configured through the validator options.
|
||||
|
||||
|
||||
.. _concepts-validators-email:
|
||||
|
||||
Email validator
|
||||
"""""""""""""""
|
||||
|
||||
The 'Email validator' checks if the given value is a valid email address.
|
||||
The format of a valid email address is defined in `RFC 3696 <http://tools.ietf.org/html/rfc3696>`_.
|
||||
The standard allows international characters and the multiple appearance
|
||||
of the @ sign.
|
||||
|
||||
|
||||
.. _concepts-validators-empty:
|
||||
|
||||
Empty validator
|
||||
"""""""""""""""
|
||||
|
||||
The 'Empty validator' checks if the given value is not empty (NULL,
|
||||
empty string, empty array or empty object).
|
||||
|
||||
|
||||
.. _concepts-validators-filesize:
|
||||
|
||||
File size validator
|
||||
"""""""""""""""""""
|
||||
|
||||
The 'File size validator' validates a file resource regarding its file
|
||||
size. The validator has 2 options:
|
||||
|
||||
- Minimum: The minimum file size to accept.
|
||||
- Maximum: The maximum file size to accept.
|
||||
|
||||
Use the format <size>B|K|M|G when entering file sizes. For example: 10M
|
||||
means 10 megabytes. Please keep in mind that the maximum file size also
|
||||
depends on the php.ini settings of your environment.
|
||||
|
||||
|
||||
.. _concepts-validators-floating_point:
|
||||
|
||||
Floating-point number validator
|
||||
"""""""""""""""""""""""""""""""
|
||||
|
||||
The 'Floating-point number validator' checks if the given value is of
|
||||
type float or a string matching the regular expression [0-9.e+-].
|
||||
|
||||
|
||||
.. _concepts-validators-integer:
|
||||
|
||||
Integer number validator
|
||||
""""""""""""""""""""""""
|
||||
|
||||
The 'Integer number validator' checks if the given value is a valid
|
||||
integer.
|
||||
|
||||
|
||||
.. _concepts-validators-text:
|
||||
|
||||
Non-XML text validator
|
||||
""""""""""""""""""""""
|
||||
|
||||
The 'Non-XML text validator' checks if the given value is a valid text
|
||||
(contains no XML tags). This basically means, that tags are stripped.
|
||||
In this special case quotes are not encoded (see `filter_var()
|
||||
<http://php.net/filter_var>`_ for more information.
|
||||
|
||||
Be aware that the value of this check entirely depends on the output
|
||||
context. The validated text is not expected to be secure in every
|
||||
circumstance, if you want to be sure of that, use a customized regular
|
||||
expression or filter on output.
|
||||
|
||||
|
||||
.. _concepts-validators-count:
|
||||
|
||||
Number of submitted values validator
|
||||
""""""""""""""""""""""""""""""""""""
|
||||
|
||||
The 'Number of submitted values validator' checks if the given value
|
||||
contains the specified amount of elements. The validator has 2 options:
|
||||
|
||||
- Minimum: The minimum number of submitted values.
|
||||
- Maximum: The maximum number of submitted values.
|
||||
|
||||
|
||||
.. _concepts-validators-number_range:
|
||||
|
||||
Number range validator
|
||||
""""""""""""""""""""""
|
||||
|
||||
The 'Number range validator' checks if the given value is a number in
|
||||
the specified range. The validator has 2 options:
|
||||
|
||||
- Minimum: The minimum value to accept.
|
||||
- Maximum: The maximum vlaue to accept.
|
||||
|
||||
|
||||
.. _concepts-validators-mimetype:
|
||||
|
||||
MimeType validator
|
||||
""""""""""""""""""
|
||||
|
||||
The 'MimeType validator' checks if the given value is a FileReference
|
||||
of the configured type (one of the */* IANA media types).
|
||||
|
||||
|
||||
.. _concepts-validators-string_length:
|
||||
|
||||
String length validator
|
||||
"""""""""""""""""""""""
|
||||
|
||||
The 'String length validator' checks if the given value is a valid string
|
||||
and its length is in the specified range. The validator has 2 options:
|
||||
|
||||
- Minimum: The minimum length of the string.
|
||||
- Maximum: The maximum length of the string.
|
||||
|
||||
|
||||
.. _concepts-validators-regular_expressions:
|
||||
|
||||
Regular expression validator
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
The 'Regular expression validator' checks if the given value matches
|
||||
the specified regular expression. Delimiters or modifiers are not
|
||||
supported. The validator has 1 option:
|
||||
|
||||
- Regular expression: The regular expression to use for validation.
|
||||
|
||||
Imagine the following example. You want the user to provide a domain
|
||||
name. The submitted value shall only contain the second and the top
|
||||
level domain, e.g. "typo3.org" instead of "https://typo3.org". The
|
||||
regular expression for this use case would be
|
||||
:code:`/^[-a-z0-9]+\.[a-z]{2,6}$/`.
|
||||
|
||||
|
||||
.. _concepts-validators-validation-message-translation:
|
||||
|
||||
Translation of validation messages
|
||||
----------------------------------
|
||||
|
||||
To learn more about this topic, please continue :ref:`here<concepts-frontendrendering-translation-validationerrors>`.
|
||||
|
||||
|
||||
.. _concepts-validators-customvalidatorimplementations:
|
||||
|
||||
Custom validator implementations
|
||||
--------------------------------
|
||||
|
||||
Validators belong to a certain ``prototype`` and are defined within the
|
||||
``validatorsDefinition``. The property ``implementationClassName`` is used
|
||||
for the validator implementation.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
validatorsDefinition:
|
||||
Custom:
|
||||
implementationClassName: 'VENDOR\MySitePackage\Domain\Validation\CustomValidator'
|
||||
|
||||
You can provide options for your validator using the property ``options``.
|
||||
Those will be used as default values which can be overridden within a
|
||||
specific ``form definition``.
|
||||
|
||||
Define the default value of the option ``yourCustomOption``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
validatorsDefinition:
|
||||
Custom:
|
||||
implementationClassName: 'VENDOR\MySitePackage\Domain\Validation\CustomValidator'
|
||||
options:
|
||||
yourCustomOption: 'Jurian'
|
||||
|
||||
Override the default value within your ``form definition``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
identifier: sample-form
|
||||
label: 'Simple Contact Form'
|
||||
prototype: standard
|
||||
type: Form
|
||||
|
||||
renderables:
|
||||
-
|
||||
identifier: subject
|
||||
label: 'Name'
|
||||
type: Text
|
||||
validators:
|
||||
-
|
||||
identifier: Custom
|
||||
options:
|
||||
yourCustomOption: 'Mathias'
|
||||
|
||||
As mentioned above EXT:form implements Extbase validators. That said,
|
||||
your own validators should extend ``TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator``.
|
||||
Read more about this topic in the `Extbase docs <https://docs.typo3.org/typo3cms/ExtbaseFluidBook/9-CrosscuttingConcerns/2-validating-domain-objects.html>`_.
|
||||
@@ -0,0 +1,639 @@
|
||||
.. include:: ../../Includes.txt
|
||||
|
||||
|
||||
.. _concepts-variants:
|
||||
|
||||
Variants
|
||||
========
|
||||
|
||||
|
||||
.. _concepts-variants-basics:
|
||||
|
||||
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".
|
||||
|
||||
.. note::
|
||||
At the current state it is **not** possible to define variants in
|
||||
the UI of the form editor.
|
||||
|
||||
|
||||
.. _concepts-variants-enabled-property:
|
||||
|
||||
Rendering option ``enabled``
|
||||
----------------------------
|
||||
|
||||
The rendering option :yaml:`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 (:yaml:`true` or :yaml:`false`).
|
||||
|
||||
Setting :yaml:`enabled: true` for a form element renders it in the
|
||||
frontend and enables processing of its value including property mapping
|
||||
and validation. Setting :yaml:`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 :yaml:`enabled: true` for a finisher executes it when
|
||||
submitting forms. Setting :yaml:`enabled: false` skips the finisher.
|
||||
|
||||
By default, :yaml:`enabled` is set to :yaml:`true`.
|
||||
|
||||
See :ref:`examples<concepts-variants-examples-hide-form-elements>`
|
||||
below to learn more about using this rendering option.
|
||||
|
||||
|
||||
.. _concepts-variants-definition:
|
||||
|
||||
Definition of variants
|
||||
----------------------
|
||||
|
||||
Variants are defined on the form element level. Check the following -
|
||||
incomplete - example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
type: Text
|
||||
identifier: text-1
|
||||
label: Foo
|
||||
variants:
|
||||
-
|
||||
identifier: variant-1
|
||||
condition: 'formValues["checkbox-1"] == 1'
|
||||
# If the condition matches, the label property of the form
|
||||
# element is set to the value 'Bar'
|
||||
label: Bar
|
||||
|
||||
|
||||
As usual, :yaml:`identifier` must be a unique name of the variant on
|
||||
the form element level.
|
||||
|
||||
Each variant has a single :yaml:`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 :yaml:`text-1` is
|
||||
changed to ``Bar`` if the checkbox :yaml:`checkbox-1` is checked.
|
||||
|
||||
The following properties can be overwritten by variants within the
|
||||
topmost element (:yaml:`Form`):
|
||||
|
||||
* :yaml:`label`
|
||||
* :yaml:`renderingOptions`
|
||||
* :yaml:`finishers`
|
||||
* :yaml:`rendererClassName`
|
||||
|
||||
The following properties can be overwritten by variants within all of
|
||||
the other form elements:
|
||||
|
||||
* :yaml:`enabled`
|
||||
* :yaml:`label`
|
||||
* :yaml:`defaultValue`
|
||||
* :yaml:`properties`
|
||||
* :yaml:`renderingOptions`
|
||||
* :yaml:`validators`
|
||||
|
||||
.. note::
|
||||
To selectively unset list items in variants like select options the special value :code:`__UNSET` can be used as value for the item to remove.
|
||||
|
||||
|
||||
.. _concepts-variants-conditions:
|
||||
|
||||
Conditions
|
||||
----------
|
||||
|
||||
The form framework uses the Symfony component `expression language <https://symfony.com/doc/4.1/components/expression_language.html>`_.
|
||||
Here, an expression is a one-liner that returns a boolean value like
|
||||
:yaml:`applicationContext matches "#Production/Local#"`. Please check
|
||||
the `Symfony docs <https://symfony.com/doc/4.1/components/expression_language/syntax.html>`_
|
||||
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 :php:`\TYPO3\CMS\Form\Domain\Runtime\FormRuntime`,
|
||||
learn more :ref:`here<apireference-frontendrendering-programmatically-apimethods-formruntime>`.
|
||||
|
||||
For example:
|
||||
|
||||
:yaml:`formRuntime.getIdentifier() == "test"`.
|
||||
|
||||
|
||||
``formValues`` (array)
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:yaml:`formValues` holds all the submitted form element values. Each
|
||||
key within this array represents a form element identifier.
|
||||
|
||||
For example:
|
||||
|
||||
:yaml:`formValues["text-1"] == "yes"`.
|
||||
|
||||
|
||||
``stepIdentifier`` (string)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:yaml:`stepIdentifier` is set to the :yaml:`identifier` of the current
|
||||
step.
|
||||
|
||||
For example:
|
||||
|
||||
:yaml:`stepIdentifier == "page-1"`.
|
||||
|
||||
|
||||
``stepType`` (string)
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:yaml:`stepType` is set to the :yaml:`type` of the current step.
|
||||
|
||||
For example:
|
||||
|
||||
:yaml:`stepType == "SummaryPage"`.
|
||||
|
||||
|
||||
``finisherIdentifier`` (string)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:yaml:`finisherIdentifier` is set to the :yaml:`identifier` of the
|
||||
current finisher or an empty string (while no finishers are executed).
|
||||
|
||||
For example:
|
||||
|
||||
:yaml:`finisherIdentifier == "EmailToSender"`.
|
||||
|
||||
|
||||
``siteLanguage`` (object)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can access every public method from :php:`\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:
|
||||
|
||||
:yaml:`siteLanguage("locale") == "de_DE"`.
|
||||
|
||||
|
||||
``applicationContext`` (string)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:yaml:`applicationContext` is set to the application context
|
||||
(@see GeneralUtility::getApplicationContext()).
|
||||
|
||||
For example:
|
||||
|
||||
:yaml:`applicationContext matches "#Production/Local#"`.
|
||||
|
||||
|
||||
``contentObject`` (array)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:yaml:`contentObject` is set to the data of the current content object
|
||||
or to an empty array if no content object is available.
|
||||
|
||||
For example:
|
||||
|
||||
:yaml:`contentObject["pid"] in [23, 42]`.
|
||||
|
||||
|
||||
.. _concepts-variants-programmatically:
|
||||
|
||||
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' => 'formValues["checkbox-1"] == 1',
|
||||
'label' => 'foo',
|
||||
]);
|
||||
|
||||
|
||||
Get all variants of a form element::
|
||||
|
||||
/** @var TYPO3\CMS\Form\Domain\Model\Renderable\RenderableVariantInterface[] $variants */
|
||||
$variants = $formElement->getVariants();
|
||||
|
||||
|
||||
Apply a variant to a form element regardless of its defined conditions::
|
||||
|
||||
$formElement->applyVariant($variant);
|
||||
|
||||
|
||||
.. _concepts-variants-examples:
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Here are some complex examples to show you the possibilities of the
|
||||
form framework.
|
||||
|
||||
|
||||
.. _concepts-variants-examples-translation:
|
||||
|
||||
Translation of form elements
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In this example form elements are translated differently depending on
|
||||
the frontend language.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
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
|
||||
|
||||
|
||||
.. _concepts-variants-examples-validation:
|
||||
|
||||
Adding validators dynamically
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In this example a bunch of validators are added to the field
|
||||
:yaml:`email-address` depending on the value of the form element
|
||||
:yaml:`checkbox-1`.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
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: '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
|
||||
|
||||
|
||||
.. _concepts-variants-examples-hide-form-elements:
|
||||
|
||||
Hide form elements
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In this extensive example the form element :yaml:`email-address` has
|
||||
been enabled explicitly but it is fine to leave this out since this is
|
||||
the default state. The form element :yaml:`text-3` has been disabled
|
||||
completely, for example to temporarily remove it from the form. The
|
||||
field :yaml:`text-1` is hidden in all finishers and on the summary step.
|
||||
The :yaml:`EmailToSender` finisher takes the fact into account that
|
||||
finishers can refer to form values. It is only enabled if the form
|
||||
element :yaml:`checkbox-1` has been activated by the user. Otherwise,
|
||||
the finisher is skipped.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
type: Form
|
||||
prototypeName: standard
|
||||
identifier: hidden-field-form
|
||||
label: Hidden field form
|
||||
finishers:
|
||||
-
|
||||
identifier: EmailToReceiver
|
||||
options:
|
||||
subject: Yes, I am ready
|
||||
recipientAddress: tritum@example.org
|
||||
recipientName: TRITUM
|
||||
senderAddress: tritum@example.org
|
||||
senderName: tritum@example.org
|
||||
-
|
||||
identifier: EmailToSender
|
||||
options:
|
||||
subject: This is a copy of the form data
|
||||
recipientAddress: '{email-address}'
|
||||
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
|
||||
|
||||
|
||||
.. _concepts-variants-examples-hide-steps:
|
||||
|
||||
Hide steps
|
||||
^^^^^^^^^^
|
||||
|
||||
In this example the second step :yaml:`page-2` is disabled if the field
|
||||
:yaml:`checkbox-1` is checked. Furthermore, the form element
|
||||
:yaml:`checkbox-1` is disabled on the summary step.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
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: 'formValues["checkbox-1"] == 1'
|
||||
renderingOptions:
|
||||
enabled: false
|
||||
renderables:
|
||||
-
|
||||
type: Text
|
||||
identifier: text-2
|
||||
label: Another field
|
||||
-
|
||||
type: SummaryPage
|
||||
identifier: summarypage-1
|
||||
label: Confirmation
|
||||
|
||||
|
||||
.. _concepts-variants-examples-finisher:
|
||||
|
||||
Set finisher values dynamically
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In this example finisher values are set differently depending on the
|
||||
application context.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
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
|
||||
|
||||
|
||||
.. _concepts-variants-examples-remove-options:
|
||||
|
||||
Remove select options
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In this example a select option is removed for a specific locale.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
type: Form
|
||||
prototypeName: standard
|
||||
identifier: option-remove-example
|
||||
label: Options removed under condition
|
||||
renderables:
|
||||
-
|
||||
type: Page
|
||||
identifier: page-1
|
||||
label: Step
|
||||
renderables:
|
||||
-
|
||||
identifier: salulation
|
||||
type: SingleSelect
|
||||
label: Salutation
|
||||
properties:
|
||||
options:
|
||||
'': '---'
|
||||
mr: Mr.
|
||||
mrs: Mrs.
|
||||
miss: Miss
|
||||
defaultValue: ''
|
||||
variants:
|
||||
-
|
||||
identifier: salulation-variant
|
||||
condition: 'siteLanguage("locale") == "zh_CN.utf-8"'
|
||||
properties:
|
||||
options:
|
||||
miss: __UNSET
|
||||
|
||||
|
||||
.. _concepts-variants-custom-language-providers:
|
||||
|
||||
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 <https://symfony.com/doc/4.1/components/expression_language/extending.html#using-expression-providers>`_.
|
||||
|
||||
Register the expression language provider within the form setup and
|
||||
provide the functionality you need. Make sure your expression language
|
||||
provider implements :php:`Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface`.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
conditionContextDefinition:
|
||||
expressionLanguageProvider:
|
||||
MyCustomExpressionLanguageProvider:
|
||||
implementationClassName: '\Vendor\MyExtension\CustomExpressionLanguageProvider'
|
||||
|
||||
|
||||
.. _concepts-variants-custom-language-variables:
|
||||
|
||||
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 the variable provider within the form setup. Make sure your
|
||||
expression language variable provider implements :php:`TYPO3\CMS\Form\Domain\Condition\ExpressionLanguageVariableProviderInterface`.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
TYPO3:
|
||||
CMS:
|
||||
Form:
|
||||
prototypes:
|
||||
standard:
|
||||
conditionContextDefinition:
|
||||
expressionLanguageVariableProvider:
|
||||
MyCustomExpressionLanguageVariableProvider:
|
||||
implementationClassName: '\Vendor\MyExtension\CustomExpressionLanguageVariableProvider'
|
||||
Reference in new issue
Block a user