TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:24 +02:00
commit aad9daaefd
1506 changed files with 94005 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
.. include:: /Includes.rst.txt
.. _apireference-events:
.. _apireference-formeditor-events:
.. _apireference-formeditor-hooks-beforeformcreate:
.. _apireference-formeditor-hooks-beforeformcreate-connect:
.. _apireference-formeditor-hooks-beforeformcreate-use:
.. _apireference-formeditor-events-beforeformiscreatedevent:
.. _apireference-formeditor-hooks-beforeformduplicate:
.. _apireference-formeditor-hooks-beforeformduplicate-connect:
.. _apireference-formeditor-hooks-beforeformduplicate-use:
.. _apireference-formeditor-hooks-beforeformdelete:
.. _apireference-formeditor-hooks-beforeformdelete-connect:
.. _apireference-formeditor-hooks-beforeformdelete-use:
.. _apireference-frontendrendering-runtimemanipulation-hooks:
.. _apireference-frontendrendering-runtimemanipulation-hooks-initializeformelement:
.. _apireference-frontendrendering-runtimemanipulation-hooks-initializeformelement-connect:
.. _apireference-frontendrendering-runtimemanipulation-hooks-initializeformelement-use:
.. _apireference-frontendrendering-runtimemanipulation-hooks-beforeremovefromparentrenderable:
.. _apireference-frontendrendering-runtimemanipulation-hooks-beforeremovefromparentrenderable-connect:
.. _apireference-frontendrendering-runtimemanipulation-hooks-beforeremovefromparentrenderable-use:
.. _apireference-frontendrendering-runtimemanipulation-hooks-afterbuildingfinished:
.. _apireference-frontendrendering-runtimemanipulation-hooks-afterbuildingfinished-connect:
.. _apireference-frontendrendering-runtimemanipulation-hooks-afterbuildingfinished-use:
.. _apireference-frontendrendering-runtimemanipulation-hooks-afterinitializecurrentpage:
.. _apireference-frontendrendering-runtimemanipulation-hooks-afterinitializecurrentpage-connect:
.. _apireference-frontendrendering-runtimemanipulation-hooks-afterinitializecurrentpage-use:
.. _apireference-frontendrendering-runtimemanipulation-hooks-aftersubmit:
.. _apireference-frontendrendering-runtimemanipulation-hooks-aftersubmit-connect:
.. _apireference-frontendrendering-runtimemanipulation-hooks-aftersubmit-use:
.. _apireference-frontendrendering-runtimemanipulation-hooks-beforerendering:
.. _apireference-frontendrendering-runtimemanipulation-hooks-beforerendering-connect:
.. _apireference-frontendrendering-runtimemanipulation-hooks-beforerendering-use:
.. _apireference-events-legacy-hooks-buildformvalidationconfiguration:
.. _apireference-events-legacy-hooks-afterformstateinitialized:
.. _apireference-events-legacy-hooks:
================
PSR-14 Events
================
EXT:form dispatches PSR-14 events at key points in the lifecycle of a form
both in the backend form editor and during frontend rendering. These events
are the recommended extension point for developers; the legacy
:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']` hooks have been removed.
.. seealso::
The canonical, always-up-to-date reference for all EXT:form events lives
in the TYPO3 Core API documentation:
`Form <https://docs.typo3.org/permalink/t3coreapi:eventlist-form>`_
The extension documentation below provides context, usage notes and
quick navigation; it intentionally avoids duplicating the full API
reference.
.. contents::
:depth: 1
:local:
.. _apireference-events-backend:
Backend events (form editor / manager)
=======================================
These events are dispatched when an editor creates, saves, duplicates or
deletes a form definition in the TYPO3 backend.
.. _apireference-events-backend-table:
.. list-table::
:header-rows: 1
:widths: 40 60
* - Event
- When / what can be modified
* - `BeforeFormIsCreatedEvent
<https://docs.typo3.org/permalink/t3coreapi:beforeformiscreatedevent>`_
- Modify the form definition array and/or the persistence identifier
before a new form is created in the backend.
* - `BeforeFormIsSavedEvent
<https://docs.typo3.org/permalink/t3coreapi:beforeformissavedevent>`_
- Modify the form definition array and/or the persistence identifier
before a form is saved in the backend.
* - `BeforeFormIsDuplicatedEvent
<https://docs.typo3.org/permalink/t3coreapi:beforeformisduplicatedevent>`_
- Modify the form definition array and/or the persistence identifier
of the copy before a form is duplicated.
* - `BeforeFormIsDeletedEvent
<https://docs.typo3.org/permalink/t3coreapi:beforeformisdeletedevent>`_
- Dispatched before a form is deleted. Set
:php:`$event->preventDeletion = true` to abort the deletion (the
event implements :php:`StoppableEventInterface`).
.. _apireference-events-frontend:
Frontend events (form rendering / runtime)
==========================================
These events are dispatched during form rendering in the frontend.
.. _apireference-events-frontend-table:
.. list-table::
:header-rows: 1
:widths: 40 60
* - Event
- When / what can be modified
* - `AfterFormIsBuiltEvent
<https://docs.typo3.org/permalink/t3coreapi:afterformisbuiltevent>`_
- Modify the :php:`FormDefinition` object after the form factory has
finished building the complete form.
* - `BeforeRenderableIsAddedToFormEvent
<https://docs.typo3.org/permalink/t3coreapi:beforerenderableisaddedtoformevent>`_
- Modify or replace a renderable (page, section or element) before it
is added to the form tree.
* - `BeforeRenderableIsRemovedFromFormEvent
<https://docs.typo3.org/permalink/t3coreapi:beforerenderableisremovedfromformevent>`_
- Dispatched before a renderable is removed from the form tree. Set
:php:`$event->preventRemoval = true` to abort the removal (the
event implements :php:`StoppableEventInterface`).
* - `AfterCurrentPageIsResolvedEvent
<https://docs.typo3.org/permalink/t3coreapi:aftercurrentpageisresolvedevent>`_
- Override :php:`$event->currentPage` after the current page has been
resolved from the request, e.g. to implement conditional page-skip
logic.
* - `BeforeRenderableIsValidatedEvent
<https://docs.typo3.org/permalink/t3coreapi:beforerenderableisvalidatedevent>`_
- Modify :php:`$event->value` before property-mapping and validation
run for each submitted form element.
* - `BeforeRenderableIsRenderedEvent
<https://docs.typo3.org/permalink/t3coreapi:beforerenderableisrenderedevent>`_
- Modify the renderable or the :php:`FormRuntime` just before a
renderable is output to the browser.
* - `BeforeEmailFinisherInitializedEvent
<https://docs.typo3.org/permalink/t3coreapi:beforeemailfinisherinitializedevent>`_
- Modify the options used by the :php:`EmailFinisher` (e.g. recipients,
subject) before they are applied.
* - `AfterFormStateInitializedEvent
<https://docs.typo3.org/permalink/t3coreapi:afterformstateinitializedevent>`_
- Enrich components with runtime data after the :php:`FormState` has
been restored from the request (form state and form session are
both available at this point).
* - `AfterFormDefinitionLoadedEvent
<https://docs.typo3.org/permalink/t3coreapi:afterformdefinitionloadedevent>`_
- Dispatched by :php:`FormPersistenceManager` after a YAML form
definition has been loaded from disk. Modify the definition globally
before it reaches the form factory.
* - `AfterFormDefinitionValidationConfigurationIsBuiltEvent
<https://docs.typo3.org/permalink/t3coreapi:afterformdefinitionvalidationconfigurationisbuiltevent>`_
- Dispatched after the form definition validation configuration has
been built from the form editor setup. Add additional writable
property paths for custom inspector editor implementations.
.. _apireference-events-register:
Registering an event listener
==============================
Register a listener via the :php:`#[AsEventListener]` PHP attribute:
.. literalinclude:: _codesnippets/_MyFormEventListener.php
:language: php
:caption: EXT:my_extension/Classes/EventListener/MyFormEventListener.php
.. seealso::
:ref:`t3coreapi:EventDispatcher` TYPO3 Core API documentation on how to
register and implement PSR-14 event listeners.
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace MyVendor\MyExtension\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Form\Event\BeforeFormIsSavedEvent;
#[AsEventListener(
identifier: 'my-extension/before-form-is-saved',
)]
final readonly class MyFormEventListener
{
public function __invoke(BeforeFormIsSavedEvent $event): void
{
// Enrich the form definition before it is persisted
$event->form['renderingOptions']['myCustomOption'] = 'value';
}
}
@@ -0,0 +1,182 @@
.. include:: /Includes.rst.txt
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel:
.. _apireference-formeditor-formelementmodel:
==================
FormElement model
==================
Every form element in the editor is represented by a **FormElement model**
object. This model is the single source of truth for all element properties
during an editing session; it is separate from the YAML form definition on
disk (which is only written on save).
.. contents::
:depth: 1
:local:
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-property-identifierpath:
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-property-parentrenderable:
.. _apireference-formeditor-formelementmodel-structure:
Model structure
===============
A FormElement model carries all YAML properties of the element plus two
internal bookkeeping properties:
.. list-table::
:header-rows: 1
:widths: 30 70
* - Property
- Description
* - :js:`__identifierPath`
- Slash-separated path from the root element to this element
(e.g. :js:`'example-form/page-1/name'`). Used as a unique key
for API lookups.
* - :js:`__parentRenderable`
- Reference to the parent FormElement model (filtered for display).
Example model in memory:
.. literalinclude:: _codesnippets/_model-structure.js
:language: javascript
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-get:
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-get-propertycollectionproperties:
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-get-renderables:
.. _apireference-formeditor-formelementmodel-api-get:
get()
-----
Reads a property by its dot-separated path. All intermediate levels must
be objects.
.. literalinclude:: _codesnippets/_get-simple.js
:language: javascript
For **property collections** (validators / finishers), whose position in
the array is unknown, use :js:`buildPropertyPath()` first:
.. literalinclude:: _codesnippets/_get-property-collection.js
:language: javascript
For **renderables** (child elements), :js:`get('renderables')` returns a
plain array of FormElement models. To access a specific child, use
:js:`formEditorApp.getFormElementByIdentifierPath()` with the full path.
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-set:
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-set-propertycollectionproperties:
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-set-renderables:
.. _apireference-formeditor-formelementmodel-api-set:
set()
-----
Writes a property by its dot-separated path. Every :js:`set()` call
automatically publishes all events registered for that path via
:ref:`on() <apireference-formeditor-formelementmodel-api-on>`, including
the built-in
:ref:`core/formElement/somePropertyChanged <apireference-formeditor-jsevents-core-formelement-somepropertychanged>`.
.. literalinclude:: _codesnippets/_set.js
:language: javascript
To modify property collection properties or add child renderables, use
the dedicated API methods on :js:`formEditorApp` / :js:`getViewModel()`
instead of setting array positions directly:
- :js:`createAndAddFormElement()`
- :js:`addFormElement()`
- :js:`moveFormElement()`
- :js:`removeFormElement()`
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-unset:
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-unset-propertycollectionproperties:
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-unset-renderables:
.. _apireference-formeditor-formelementmodel-api-unset:
unset()
-------
Removes a property at the given dot-separated path.
.. literalinclude:: _codesnippets/_unset.js
:language: javascript
For property collection properties, use :js:`buildPropertyPath()` in the
same way as for :ref:`get() <apireference-formeditor-formelementmodel-api-get>`.
To remove a child renderable, call
:js:`formEditorApp.removeFormElement()`.
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-on:
.. _apireference-formeditor-formelementmodel-api-on:
on()
----
Registers an additional publish/subscribe event name that is fired
whenever :js:`set()` is called for a given property path.
.. literalinclude:: _codesnippets/_on.js
:language: javascript
By default EXT:form registers
:ref:`core/formElement/somePropertyChanged <apireference-formeditor-jsevents-core-formelement-somepropertychanged>`
for every known property path of every form element.
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-off:
.. _apireference-formeditor-formelementmodel-api-off:
off()
-----
Removes an event registration created with :js:`on()`.
.. literalinclude:: _codesnippets/_off.js
:language: javascript
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-getobjectdata:
.. _apireference-formeditor-formelementmodel-api-getobjectdata:
getObjectData()
---------------
Returns a deep-cloned plain object of all properties. Used internally for
Ajax serialisation. Provides read access to data set via :js:`set()` from
outside the model without breaking encapsulation.
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-clone:
.. _apireference-formeditor-formelementmodel-api-clone:
clone()
-------
Returns a fully dereferenced clone of the FormElement model.
.. literalinclude:: _codesnippets/_clone.js
:language: javascript
.. _apireference-formeditor-basicjavascriptconcepts-formelementmodel-method-tostring:
.. _apireference-formeditor-formelementmodel-api-tostring:
toString()
----------
Returns the model data as a JSON string. Intended for debugging.
.. literalinclude:: _codesnippets/_to-string.js
:language: javascript
@@ -0,0 +1,5 @@
export function bootstrap(formEditorApp) {
const formElement = formEditorApp
.getFormElementByIdentifierPath('example-form/page-1/name');
const copy = formElement.clone();
}
@@ -0,0 +1,10 @@
export function bootstrap(formEditorApp) {
const formElement = formEditorApp
.getFormElementByIdentifierPath('example-form/page-1/name');
const propertyPath = formEditorApp
.buildPropertyPath('options.minimum', 'StringLength', 'validators', formElement);
// propertyPath = e.g. 'validators.0.options.minimum'
const value = formElement.get(propertyPath); // '1'
}
@@ -0,0 +1,6 @@
export function bootstrap(formEditorApp) {
// Returns 'Name'
const placeholder = formEditorApp
.getFormElementByIdentifierPath('example-form/page-1/name')
.get('properties.fluidAdditionalAttributes.placeholder');
}
@@ -0,0 +1,19 @@
// Illustrative snapshot of a FormElement model as it exists in memory at runtime.
// The actual object is managed by the FormElement class access it via
// formEditorApp.getFormElementByIdentifierPath() and the get()/set() API.
export const formElementSnapshot = {
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' },
],
};
@@ -0,0 +1,5 @@
export function bootstrap(formEditorApp) {
formEditorApp
.getFormElementByIdentifierPath('example-form/page-1/name')
.off('properties.fluidAdditionalAttributes.placeholder', 'my/custom/event');
}
@@ -0,0 +1,9 @@
export function bootstrap(formEditorApp) {
const element = formEditorApp
.getFormElementByIdentifierPath('example-form/page-1/name');
element.on('properties.fluidAdditionalAttributes.placeholder', 'my/custom/event');
// The next set() on that path will also publish 'my/custom/event'.
element.set('properties.fluidAdditionalAttributes.placeholder', 'New Placeholder');
}
@@ -0,0 +1,5 @@
export function bootstrap(formEditorApp) {
formEditorApp
.getFormElementByIdentifierPath('example-form/page-1/name')
.set('properties.fluidAdditionalAttributes.placeholder', 'New Placeholder');
}
@@ -0,0 +1,5 @@
export function bootstrap(formEditorApp) {
const formElement = formEditorApp
.getFormElementByIdentifierPath('example-form/page-1/name');
console.log(formElement.toString());
}
@@ -0,0 +1,5 @@
export function bootstrap(formEditorApp) {
formEditorApp
.getFormElementByIdentifierPath('example-form/page-1/name')
.unset('properties.fluidAdditionalAttributes.placeholder');
}
+97
View File
@@ -0,0 +1,97 @@
.. include:: /Includes.rst.txt
.. _apireference-formeditor:
.. _apireference-formeditor-basicjavascriptconcepts:
.. _apireference-formeditor-basicjavascriptconcepts-events:
.. _apireference-formeditor-stage:
===========
Form Editor
===========
This chapter is the developer reference for the TYPO3 backend form editor.
It covers the JavaScript extension points and the data model used by the
editor's TypeScript modules.
.. contents::
:depth: 1
:local:
.. _apireference-formeditor-architecture:
Architecture overview
=====================
The form editor consists of four cooperating TypeScript modules, each
responsible for one UI component:
.. list-table::
:header-rows: 1
:widths: 30 35 35
* - Module (import path)
- Component
- Responsibility
* - :js:`@typo3/form/backend/form-editor/view-model`
- —
- Central view model; wires DOM events and publishes/subscribes
to all cross-component events.
* - :js:`@typo3/form/backend/form-editor/stage-component`
- **Stage**
- Renders the abstract and preview views of the current form page.
* - :js:`@typo3/form/backend/form-editor/inspector-component`
- **Inspector**
- Renders the property editors for the selected form element.
* - :js:`@typo3/form/backend/form-editor/tree-component-adapter`
- **Structure tree**
- Wraps the TYPO3 backend tree web component and bridges its
events to the publish/subscribe bus.
* - :js:`@typo3/form/backend/form-editor/mediator`
- —
- Wires all publish/subscribe events to view-model actions.
Loaded automatically; replace via
:yaml:`dynamicJavaScriptModules.mediator` only when you need to
completely swap the event-wiring logic.
All modules communicate exclusively via a **publish/subscribe bus**
(:js:`PublisherSubscriber`). Direct module-to-module calls are avoided
so that extension code can hook into any point without modifying core
files.
.. _apireference-formeditor-custom-modules:
Registering a custom JavaScript module
=======================================
Custom modules must export a :js:`bootstrap` function. The form editor
calls this function once all built-in modules have loaded, passing the
central :js:`FormEditor` application object as the sole argument.
.. rst-class:: bignums-xxl
1. Create the JavaScript module
.. literalinclude:: _codesnippets/_bootstrap.js
:language: javascript
:caption: EXT:my_extension/Resources/Public/JavaScript/backend/form-editor/view-model.js
2. Register the module in the importmap
.. literalinclude:: _codesnippets/_JavaScriptModules.php
:language: php
:caption: EXT:my_extension/Configuration/JavaScriptModules.php
3. Tell the form editor to load the module
.. literalinclude:: _codesnippets/_prototype-setup.yaml
:language: yaml
:caption: EXT:my_extension/Configuration/Form/MyFormSet/config.yaml
.. toctree::
:maxdepth: 1
JavaScriptEvents/Index
StageTemplates/Index
FormElementModel/Index
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
export function bootstrap(formEditorApp) {
formEditorApp.getPublisherSubscriber().subscribe(
'view/inspector/editor/insert/perform',
(topic, args) => {
const [editorConfiguration, editorHtml] = args;
if (editorConfiguration.templateName !== 'Inspector-MyCustomEditor') {
return;
}
// Wire up your custom editor UI inside editorHtml
const input = editorHtml.querySelector('.my-custom-input');
if (input) {
input.addEventListener('change', (e) => {
formEditorApp
.getCurrentlySelectedFormElement()
.set(editorConfiguration.propertyPath, e.target.value);
});
}
},
);
}
@@ -0,0 +1,18 @@
prototypes:
standard:
formEditor:
dynamicJavaScriptModules:
additionalViewModelModules:
10: '@vendor/my-extension/backend/form-editor/view-model.js'
formEditorPartials:
Inspector-MyCustomEditor: 'Inspector/MyCustomEditor'
formEditorFluidConfiguration:
partialRootPaths:
100: 'EXT:my_extension/Resources/Private/Backend/Partials/FormEditor/'
formElementsDefinition:
Text:
formEditor:
editors:
600:
templateName: 'Inspector-MyCustomEditor'
myOption: 'example'
@@ -0,0 +1,3 @@
export function bootstrap(formEditorApp) {
formEditorApp.getPublisherSubscriber().publish('my/custom/event', ['arg1', 'arg2']);
}
@@ -0,0 +1,11 @@
export function bootstrap(formEditorApp) {
formEditorApp.getPublisherSubscriber().subscribe(
'core/formElement/somePropertyChanged',
(topic, args) => {
const [propertyPath, newValue, oldValue, identifierPath] = args;
if (propertyPath === 'label' && identifierPath?.startsWith('my-form/page-1/')) {
console.log('Label changed from', oldValue, 'to', newValue);
}
},
);
}
@@ -0,0 +1,4 @@
<div class="formeditor-element-body">
<div data-identifier="elementLabel"></div>
<div data-identifier="elementSummary"></div>
</div>
@@ -0,0 +1,24 @@
export function bootstrap(formEditorApp) {
formEditorApp.getPublisherSubscriber().subscribe(
'view/stage/abstract/render/template/perform',
(topic, args) => {
const [formElement, template] = args;
if (formElement.get('type') !== 'MyCustomElement') {
return;
}
const labelEl = template.querySelector('[data-identifier="elementLabel"]');
if (labelEl) {
labelEl.textContent =
formElement.get('label') || formElement.get('identifier');
}
const summaryEl = template.querySelector('[data-identifier="elementSummary"]');
if (summaryEl) {
summaryEl.textContent =
formElement.get('properties.myCustomProperty') ?? '';
}
},
);
}
@@ -0,0 +1,11 @@
prototypes:
standard:
formEditor:
dynamicJavaScriptModules:
additionalViewModelModules:
10: '@vendor/my-extension/backend/form-editor/view-model.js'
formEditorPartials:
FormElement-MyCustomElement: 'Stage/MyCustomElement'
formEditorFluidConfiguration:
partialRootPaths:
100: 'EXT:my_extension/Resources/Private/Backend/Partials/FormEditor/'
@@ -0,0 +1,11 @@
export function bootstrap(formEditorApp) {
const ps = formEditorApp.getPublisherSubscriber();
// Subscribe returns a token for later unsubscription
const token = ps.subscribe('view/ready', (topic, args) => {
// args is a typed tuple matching the event signature
});
// Unsubscribe
ps.unsubscribe(token);
}
@@ -0,0 +1,5 @@
export function bootstrap(formEditorApp) {
formEditorApp.getPublisherSubscriber().subscribe('view/ready', () => {
// Safe to call any formEditorApp API here.
});
}
@@ -0,0 +1,137 @@
.. include:: /Includes.rst.txt
.. _apireference-formeditor-stage-commonabstractformelementtemplates:
.. _apireference-formeditor-stagetemplates:
===============
Stage templates
===============
The **Stage** component renders each form element as an HTML item in the
abstract view. This section explains the two rendering strategies: the
modern web-component approach (recommended) and the legacy Fluid-partial
approach (deprecated).
.. contents::
:depth: 1
:local:
.. _apireference-formeditor-stagetemplates-webcomponent:
Built-in web component (recommended)
=====================================
When no :yaml:`formEditorPartials` entry exists for a form element type,
the Stage component automatically renders it using the built-in
:html:`<typo3-form-form-element-stage-item>` web component. The component
displays the element's label, type icon, validators, select options and
allowed MIME types without requiring any custom JavaScript.
.. tip::
For most custom form elements this is the recommended approach. Simply
omit :yaml:`formEditorPartials` from the prototype configuration and the
editor handles the rest.
Properties set on the web component from the :js:`FormElement` model:
.. list-table::
:header-rows: 1
:widths: 30 70
* - Property
- Source in FormElement model
* - :js:`elementType`
- Form element definition :yaml:`label`
* - :js:`elementLabel`
- :yaml:`label` (falls back to :yaml:`identifier`)
* - :js:`elementIconIdentifier`
- Form element definition :yaml:`iconIdentifier`
* - :js:`validators`
- :yaml:`validators` array (excludes ``NotEmpty``, shown via :js:`isRequired`)
* - :js:`isRequired`
- ``true`` when a ``NotEmpty`` validator is present
* - :js:`options`
- :yaml:`properties.options` (for select-like elements)
* - :js:`allowedMimeTypes`
- :yaml:`properties.allowedMimeTypes`
* - :js:`content`
- :yaml:`properties.text` or :yaml:`properties.contentElementUid`
* - :js:`isHidden`
- ``true`` when :yaml:`renderingOptions.enabled` is ``false``
.. _apireference-formeditor-stagetemplates-fluid:
Custom Fluid partial (advanced)
================================
If you need fully custom stage rendering for example to display a
proprietary summary of complex properties you can still provide a Fluid
partial and subscribe to the
:ref:`view/stage/abstract/render/template/perform <apireference-formeditor-jsevents-view-stage-abstract-render-template-perform>`
event to populate it with DOM manipulation.
The core Fluid partials are located in
:file:`EXT:form/Resources/Private/Backend/Partials/FormEditor/Stage/`.
.. warning::
The legacy stage rendering helpers
:js:`renderSimpleTemplateWithValidators()` and
:js:`renderSelectTemplates()` from
:js:`@typo3/form/backend/form-editor/stage-component` are deprecated
since TYPO3 v14.2 and will be removed in TYPO3 v15. Migrate to the
web component approach (omit :yaml:`formEditorPartials`) or implement
custom DOM manipulation in the event subscriber.
.. _apireference-formeditor-stage-commonabstractformelementtemplates-simpletemplate:
.. _apireference-formeditor-stagetemplates-fluid-simpletemplate:
Stage/SimpleTemplate (deprecated)
----------------------------------
Displays the element :yaml:`label`. When the element has validators, a
validator icon and their labels appear on hover/selection. Rendered via
the deprecated :js:`renderSimpleTemplateWithValidators()`.
.. deprecated:: 14.2
Use the :html:`<typo3-form-form-element-stage-item>` web component
by omitting :yaml:`formEditorPartials`, or implement custom DOM
manipulation in the
:ref:`view/stage/abstract/render/template/perform <apireference-formeditor-jsevents-view-stage-abstract-render-template-perform>`
subscriber. See Deprecation :issue:`109306`.
.. _apireference-formeditor-stage-commonabstractformelementtemplates-selecttemplate:
.. _apireference-formeditor-stagetemplates-fluid-selecttemplate:
Stage/SelectTemplate (deprecated)
----------------------------------
Extends ``Stage/SimpleTemplate`` by additionally listing the chosen option
labels from :yaml:`properties.options.*`. Rendered via the deprecated
:js:`renderSelectTemplates()`.
Example form element using select options:
.. literalinclude:: _codesnippets/_select-template.yaml
:language: yaml
The template partial contains a container with the path to read:
.. literalinclude:: _codesnippets/_select-template-partial.html
:language: html
For elements using a different array property (e.g. ``FileUpload`` with
:yaml:`properties.allowedMimeTypes`), adjust the :html:`data-template-property`
attribute accordingly:
.. literalinclude:: _codesnippets/_file-upload-partial.html
:language: html
The web component handles both cases automatically.
.. deprecated:: 14.2
Use the :html:`<typo3-form-form-element-stage-item>` web component
by omitting :yaml:`formEditorPartials`.
See `Deprecation: #109306 - Deprecate form editor stage template rendering functions <https://docs.typo3.org/permalink/changelog:deprecation-109306-1774010043>`_.
@@ -0,0 +1,2 @@
<div data-identifier="multiValueContainer"
data-template-property="properties.allowedMimeTypes"></div>
@@ -0,0 +1,2 @@
<div data-identifier="multiValueContainer"
data-template-property="properties.options"></div>
@@ -0,0 +1,7 @@
type: MultiCheckbox
identifier: multicheckbox-1
label: 'Multi checkbox'
properties:
options:
value1: label1
value2: label2
@@ -0,0 +1,9 @@
<?php
return [
'dependencies' => ['form'],
'imports' => [
'@vendor/my-extension/'
=> 'EXT:my_extension/Resources/Public/JavaScript/',
],
];
@@ -0,0 +1,10 @@
/**
* Custom form editor module for EXT:my_extension.
*/
export function bootstrap(formEditorApp) {
const ps = formEditorApp.getPublisherSubscriber();
ps.subscribe('view/ready', () => {
// Editor is fully initialised set up your custom logic here.
});
}
@@ -0,0 +1,6 @@
prototypes:
standard:
formEditor:
dynamicJavaScriptModules:
additionalViewModelModules:
10: '@vendor/my-extension/backend/form-editor/view-model.js'
+320
View File
@@ -0,0 +1,320 @@
.. include:: /Includes.rst.txt
.. _apireference-frontendrendering:
============================
Building and rendering forms
============================
This chapter explains how EXT:form renders forms in the frontend and how
developers can build forms programmatically or customize the rendering
pipeline.
For the complete PHP API of every class mentioned here, see
`EXT:form API on api.typo3.org <https://api.typo3.org/main/namespaces/typo3-cms-form.html>`__.
.. contents::
:local:
:depth: 2
.. _apireference-frontendrendering-fluidformrenderer:
Template resolution (FluidFormRenderer)
========================================
The :php-short:`\TYPO3\CMS\Form\Domain\Renderer\FluidFormRenderer` resolves
Fluid templates, layouts and partials through rendering options defined in the
prototype configuration. All options are read from
:php-short:`\TYPO3\CMS\Form\Domain\Model\FormDefinition::getRenderingOptions()`.
.. _apireference-frontendrendering-fluidformrenderer-options:
.. _apireference-frontendrendering-fluidformrenderer-options-templaterootpaths:
templateRootPaths
-----------------
Defines one or more paths to Fluid **templates**.
Paths are searched in reverse order (bottom to top); the first match wins.
Only the root form element (type :yaml:`Form`) must be a **template** file.
All child elements are resolved as **partials**.
.. literalinclude:: _templateRootPaths.yaml
:caption: EXT:my_sitepackage/Configuration/Form/CustomPrototype.yaml
:language: yaml
With the default type :yaml:`Form` the renderer expects a file named
:file:`Form.html` inside the first matching path.
.. _apireference-frontendrendering-fluidformrenderer-options-layoutrootpaths:
layoutRootPaths
---------------
Defines one or more paths to Fluid **layouts**, searched in reverse order.
.. literalinclude:: _layoutRootPaths.yaml
:caption: EXT:my_sitepackage/Configuration/Form/CustomPrototype.yaml
:language: yaml
.. _apireference-frontendrendering-fluidformrenderer-options-partialrootpaths:
partialRootPaths
----------------
Defines one or more paths to Fluid **partials**, searched in reverse order.
Within these paths the renderer looks for a file named after the
form element type (e.g. :file:`Text.html` for a :yaml:`Text` element).
Use :ref:`templateName <apireference-frontendrendering-fluidformrenderer-options-templatename>`
to override this convention.
.. literalinclude:: _partialRootPaths.yaml
:caption: EXT:my_sitepackage/Configuration/Form/CustomPrototype.yaml
:language: yaml
.. _apireference-frontendrendering-fluidformrenderer-options-templatename:
templateName
------------
By default the element type is used as the partial file name
(e.g. type :yaml:`Text`:file:`Text.html`).
Set :yaml:`templateName` to use a different file instead:
.. literalinclude:: _templateName.yaml
:caption: EXT:my_sitepackage/Configuration/Form/CustomPrototype.yaml
:language: yaml
The element of type :yaml:`Foo` now renders using :file:`Text.html`.
.. _apireference-frontendrendering-renderviewHelper:
The render ViewHelper
=====================
.. _apireference-frontendrendering-renderviewHelper-arguments:
Use :html:`<formvh:render>` in a Fluid template to render a form.
The ViewHelper accepts the following arguments:
.. _apireference-frontendrendering-renderviewHelper-persistenceidentifier:
persistenceIdentifier
---------------------
Path to a YAML form definition. This is the most common way to render a
form:
.. literalinclude:: _renderPersistenceIdentifier.html
:caption: EXT:my_sitepackage/Resources/Private/Templates/ContactPage.html
:language: html
.. _apireference-frontendrendering-renderviewHelper-overrideconfiguration:
overrideConfiguration
---------------------
A configuration array that is merged **on top** of the loaded form
definition (or passed directly to the factory when no
:yaml:`persistenceIdentifier` is given).
This allows adjusting a form per usage without duplicating the YAML file.
.. _apireference-frontendrendering-renderviewHelper-factoryclass:
factoryClass
------------
A fully qualified class name implementing
:php-short:`\TYPO3\CMS\Form\Domain\Factory\FormFactoryInterface`.
Defaults to :php-short:`\TYPO3\CMS\Form\Domain\Factory\ArrayFormFactory`.
Set a custom factory to :ref:`build forms programmatically <apireference-frontendrendering-programmatically>`.
.. literalinclude:: _renderFactoryClass.html
:caption: EXT:my_sitepackage/Resources/Private/Templates/ContactPage.html
:language: html
.. _apireference-frontendrendering-renderviewHelper-prototypename:
prototypeName
-------------
Name of the prototype the factory should use (e.g. :yaml:`standard`).
If omitted the framework looks for the prototype name inside the form
definition; if none is found, :yaml:`standard` is used.
.. _apireference-frontendrendering-programmatically:
Building forms programmatically
===============================
Instead of writing YAML, you can create a form entirely in PHP by
implementing a custom :php:`FormFactory`.
.. rst-class:: bignums-xxl
1. Create a FormFactory
Extend :php:`AbstractFormFactory` and implement :php:`build()`.
Use :php:`FormDefinition::createPage()` to add pages,
:php:`Page::createElement()` to add elements, and
:php:`FormDefinition::createFinisher()` to attach finishers.
.. literalinclude:: _CustomFormFactory.php
:caption: EXT:my_sitepackage/Classes/Domain/Factory/CustomFormFactory.php
:language: php
2. Render the form
Reference your factory in a Fluid template:
.. literalinclude:: _renderFactoryClass.html
:caption: EXT:my_sitepackage/Resources/Private/Templates/ContactPage.html
:language: html
.. _apireference-frontendrendering-programmatically-key-concepts:
Key classes and their responsibilities
--------------------------------------
.. _apireference-frontendrendering-programmatically-apimethods-formruntime:
The following table lists the most important classes you work with when
building or manipulating forms programmatically. Use your IDE's
autocompletion or the
`API documentation <https://api.typo3.org/main/namespaces/typo3-cms-form.html>`__
for the full method reference.
.. list-table::
:header-rows: 1
:widths: 30 70
* - Class
- Purpose
* - :php-short:`\TYPO3\CMS\Form\Domain\Model\FormDefinition`
- The complete form model. Create pages (:php:`createPage()`),
attach finishers (:php:`createFinisher()`), look up elements
(:php:`getElementByIdentifier()`), and bind to a request
(:php:`bind()`).
* - :php-short:`\TYPO3\CMS\Form\Domain\Runtime\FormRuntime`
- A *bound* form instance (created by :php:`FormDefinition::bind()`).
Provides access to the current page, submitted values
(:php:`getElementValue()`), and the request/response objects.
This is the object available inside finishers and event listeners.
* - :php-short:`\TYPO3\CMS\Form\Domain\Model\FormElements\Page`
- One page of a multi-step form. Add elements with
:php:`createElement()`, reorder them with :php:`moveElementBefore()`
/ :php:`moveElementAfter()`.
* - :php-short:`\TYPO3\CMS\Form\Domain\Model\FormElements\Section`
- A grouping element inside a page. Same API as :php:`Page` for
managing child elements.
* - :php-short:`\TYPO3\CMS\Form\Domain\Model\FormElements\AbstractFormElement`
- Base class of all concrete elements. Most element types use
:php-short:`\TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement`;
specialized subclasses include, e.g.
:php-short:`\TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload`.
Set properties (:php:`setProperty()`), add validators
(:php:`createValidator()`), define default values
(:php:`setDefaultValue()`).
* - :php-short:`\TYPO3\CMS\Form\Domain\Configuration\ConfigurationService`
- Reads the merged prototype configuration.
Call :php:`getPrototypeConfiguration('standard')` to obtain the
full array for a prototype.
.. _apireference-frontendrendering-programmatically-initializeformelement:
Initializing elements at runtime
---------------------------------
Override :php:`initializeFormElement()` in a custom form element class to
populate data (e.g. from a database) when the element is added to the form.
At that point the prototype defaults have already been applied; properties
from the YAML definition are applied **afterwards**.
.. tip::
If you only need to initialize an element without writing a full custom
class, listen to the :php:`BeforeRenderableIsAddedToFormEvent` PSR-14
event instead. See :ref:`apireference-events`.
.. _apireference-frontendrendering-finishers:
Working with finishers
======================
Custom finishers extend :php-short:`\TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher`
and place their logic in :php:`executeInternal()`. The base class provides:
:php:`parseOption(string $optionName)`
Resolves a finisher option, applying form-element variable replacements
and TypoScript-style option overrides. Always prefer this over direct
array access.
The :php-short:`\TYPO3\CMS\Form\Domain\Finishers\FinisherContext` passed to
:php:`execute()` gives access to:
:php:`getFormRuntime()`
The :php-short:`\TYPO3\CMS\Form\Domain\Runtime\FormRuntime` for the current
submission.
:php:`getFormValues()`
All submitted values (after validation and property mapping).
:php:`getFinisherVariableProvider()`
A key/value store to **share data between finishers** within the same
request. The returned
:php-short:`\TYPO3\CMS\Form\Domain\Finishers\FinisherVariableProvider` offers:
:php:`add(string $finisherIdentifier, string $key, mixed $value)`
Store a value under a finisher-specific namespace.
:php:`get(string $finisherIdentifier, string $key, mixed $default = null)`
Retrieve a previously stored value (returns :php:`$default` if not set).
:php:`exists(string $finisherIdentifier, string $key)`
Check whether a value has been stored.
:php:`remove(string $finisherIdentifier, string $key)`
Remove a stored value.
:php:`cancel()`
Stops execution of any remaining finishers.
.. seealso::
:ref:`Accessing finisher options <concepts-finishers-customfinisherimplementations-accessingoptions>` |
:ref:`Sharing data between finishers <concepts-finishers-customfinisherimplementations-finishercontext-sharedatabetweenfinishers>`
.. _apireference-frontendrendering-runtimemanipulation:
Runtime manipulation
====================
.. _apireference-frontendrendering-runtimemanipulation-events:
EXT:form dispatches PSR-14 events at every important step of the rendering
lifecycle. Use them to modify the form, redirect page flow, or adjust
submitted values without subclassing framework internals.
.. seealso::
:ref:`PSR-14 events overview for EXT:form <apireference-events>`
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Vendor\MySitePackage\Domain\Factory;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
use TYPO3\CMS\Form\Domain\Factory\AbstractFormFactory;
use TYPO3\CMS\Form\Domain\Model\FormDefinition;
use TYPO3\CMS\Form\Domain\Model\FormElements\AbstractFormElement;
final class CustomFormFactory extends AbstractFormFactory
{
public function build(
array $configuration,
?string $prototypeName = null,
?ServerRequestInterface $request = null,
): FormDefinition {
$prototypeName ??= 'standard';
$configurationService = GeneralUtility::makeInstance(
ConfigurationService::class,
);
$prototypeConfiguration = $configurationService->getPrototypeConfiguration(
$prototypeName,
);
$form = GeneralUtility::makeInstance(
FormDefinition::class,
'ContactForm',
$prototypeConfiguration,
);
$form->setRenderingOption('controllerAction', 'index');
// Page 1 personal data
$page1 = $form->createPage('page1');
/** @var AbstractFormElement $name */
$name = $page1->createElement('name', 'Text');
$name->setLabel('Name');
$name->createValidator('NotEmpty');
/** @var AbstractFormElement $email */
$email = $page1->createElement('email', 'Text');
$email->setLabel('Email');
// Page 2 message
$page2 = $form->createPage('page2');
/** @var AbstractFormElement $message */
$message = $page2->createElement('message', 'Textarea');
$message->setLabel('Message');
$message->createValidator('StringLength', ['minimum' => 5, 'maximum' => 500]);
// Radio buttons
/** @var AbstractFormElement $subject */
$subject = $page2->createElement('subject', 'RadioButton');
$subject->setProperty('options', [
'general' => 'General inquiry',
'support' => 'Support request',
]);
$subject->setLabel('Subject');
// Finisher send email
$form->createFinisher('EmailToSender', [
'subject' => 'Contact form submission',
'recipients' => [
'info@example.com' => 'My Company',
],
'senderAddress' => 'noreply@example.com',
]);
$this->triggerFormBuildingFinished($form);
return $form;
}
}
@@ -0,0 +1,8 @@
prototypes:
standard:
formElementsDefinition:
Form:
renderingOptions:
layoutRootPaths:
10: 'EXT:form/Resources/Private/Frontend/Layouts/'
20: 'EXT:my_sitepackage/Resources/Private/Forms/Frontend/Layouts/'
@@ -0,0 +1,8 @@
prototypes:
standard:
formElementsDefinition:
Form:
renderingOptions:
partialRootPaths:
10: 'EXT:form/Resources/Private/Frontend/Partials/'
20: 'EXT:my_sitepackage/Resources/Private/Forms/Frontend/Partials/'
@@ -0,0 +1,3 @@
<formvh:render
factoryClass="Vendor\MySitePackage\Domain\Factory\CustomFormFactory"
/>
@@ -0,0 +1,3 @@
<formvh:render
persistenceIdentifier="EXT:my_sitepackage/Resources/Private/Forms/Contact.yaml"
/>
@@ -0,0 +1,6 @@
prototypes:
standard:
formElementsDefinition:
Foo:
renderingOptions:
templateName: 'Text'
@@ -0,0 +1,8 @@
prototypes:
standard:
formElementsDefinition:
Form:
renderingOptions:
templateRootPaths:
10: 'EXT:form/Resources/Private/Frontend/Templates/'
20: 'EXT:my_sitepackage/Resources/Private/Forms/Frontend/Templates/'
+20
View File
@@ -0,0 +1,20 @@
.. include:: /Includes.rst.txt
.. _apireference:
==============================
For Developers / API Reference
==============================
This chapter is a complete reference of the API of the form framework. It
mainly addresses your concerns as a developer.
.. toctree::
:maxdepth: 1
Events/Index
FormEditor/Index
FrontendRendering/Index
.. seealso::
All built-in finishers including their options and programmatic usage
is now described in: `Ready-to-use finishers <https://docs.typo3.org/permalink/typo3/cms-form:apireference-finisheroptions>`_.