TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
.. include:: /Includes.rst.txt
|
||||
|
||||
.. _howtos-custom-form-element:
|
||||
|
||||
==============================
|
||||
Creating a custom form element
|
||||
==============================
|
||||
|
||||
This tutorial shows you how to create a custom form element for the TYPO3 Form
|
||||
Framework. We'll create a "Gender Select" element as an example.
|
||||
|
||||
.. contents:: Table of Contents
|
||||
:depth: 2
|
||||
:local:
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
|
||||
Before you start, make sure you have:
|
||||
|
||||
* Basic knowledge of YAML configuration
|
||||
* A sitepackage where you can add configuration files
|
||||
|
||||
Step 1: Create the configuration files
|
||||
======================================
|
||||
|
||||
Create a form set directory in your extension. The sub-directory name is
|
||||
arbitrary — we use ``CustomElement`` here.
|
||||
|
||||
File location
|
||||
-------------
|
||||
|
||||
Create the following structure in your extension:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
EXT:my_extension/
|
||||
Configuration/
|
||||
Form/
|
||||
CustomElement/
|
||||
config.yaml
|
||||
|
||||
Configuration structure
|
||||
-----------------------
|
||||
|
||||
Here's the complete configuration for our Gender Select element:
|
||||
|
||||
.. literalinclude:: _CustomFormSetup.yaml
|
||||
:language: yaml
|
||||
:caption: EXT:my_extension/Configuration/Form/CustomElement/config.yaml (after metadata)
|
||||
|
||||
Common inspector editors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Here are some commonly used inspector editors (:ref:`Inspector <concepts-formeditor-inspector>`) you can add to your form elements:
|
||||
|
||||
**Inspector-FormElementHeaderEditor** (100)
|
||||
Shows the element header in the inspector panel
|
||||
|
||||
**Inspector-TextEditor** (200-300)
|
||||
A simple text input field for properties like label and description
|
||||
|
||||
**Inspector-PropertyGridEditor** (400)
|
||||
A grid editor for managing key-value pairs (like options)
|
||||
|
||||
**Inspector-GridColumnViewPortConfigurationEditor** (700)
|
||||
Controls responsive behavior and column widths for different screen sizes
|
||||
|
||||
**Inspector-RequiredValidatorEditor** (800)
|
||||
Adds a checkbox to make the field required
|
||||
|
||||
**Inspector-ValidationErrorMessageEditor** (900)
|
||||
Allows customizing validation error messages
|
||||
|
||||
**Inspector-RemoveElementEditor** (9999)
|
||||
Shows a button to remove the element from the form
|
||||
|
||||
Step 2: Register the configuration
|
||||
===================================
|
||||
|
||||
No PHP or TypoScript registration is needed. TYPO3 discovers YAML files
|
||||
automatically from any extension that follows the directory convention.
|
||||
|
||||
Create a form set with a single :file:`config.yaml`:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
EXT:my_extension/
|
||||
Configuration/
|
||||
Form/
|
||||
CustomElement/
|
||||
config.yaml ← set metadata + all configuration
|
||||
|
||||
Set the ``priority`` in :file:`config.yaml` to a value greater than ``10``
|
||||
(the EXT:form core base set) so your configuration is merged on top:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: EXT:my_extension/Configuration/Form/CustomElement/config.yaml
|
||||
|
||||
name: my-vendor/custom-element
|
||||
label: 'My Custom Form Element'
|
||||
priority: 200
|
||||
|
||||
# Form element configuration goes here:
|
||||
prototypes:
|
||||
standard:
|
||||
...
|
||||
|
||||
Step 3: Clear Caches
|
||||
====================
|
||||
|
||||
After adding the configuration, you must clear all TYPO3 caches.
|
||||
|
||||
Step 4: Using your custom element
|
||||
==================================
|
||||
|
||||
Now you can use your custom element in the form editor:
|
||||
|
||||
1. Open the Form Editor user interface (:guilabel:`Forms > [Your Form] > Edit`)
|
||||
2. Look for "Gender Select" in the form element browser.
|
||||
3. Add the element to the form.
|
||||
4. Configure the element using the inspector panel on the right.
|
||||
5. Save your form.
|
||||
6. Add a form content element to a page and select the form you just edited.
|
||||
7. Preview the page in the frontend.
|
||||
|
||||
The element will now be available in your forms and will render using the
|
||||
RadioButton template in the frontend.
|
||||
|
||||
Step 5: Customizing frontend output (optional)
|
||||
===============================================
|
||||
|
||||
If you want to use a custom template instead of reusing an existing one, follow
|
||||
these steps:
|
||||
|
||||
Create custom template
|
||||
----------------------
|
||||
|
||||
Create your own Fluid template:
|
||||
|
||||
:file:`EXT:my_extension/Resources/Private/Partials/Form/GenderSelect.fluid.html`
|
||||
|
||||
.. literalinclude:: _GenderSelect.fluid.html
|
||||
:language: html
|
||||
:caption: EXT:my_extension/Resources/Private/Partials/Form/GenderSelect.fluid.html
|
||||
|
||||
|
||||
Update configuration
|
||||
--------------------
|
||||
|
||||
Update your YAML configuration to use the custom partial template:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: EXT:my_extension/Configuration/Form/CustomElement/config.yaml
|
||||
:emphasize-lines: 3-7, 10
|
||||
|
||||
prototypes:
|
||||
standard:
|
||||
formElementsDefinition:
|
||||
Form:
|
||||
renderingOptions:
|
||||
partialRootPaths:
|
||||
1732785721: 'EXT:my_extension/Resources/Private/Partials/Form/'
|
||||
GenderSelect:
|
||||
renderingOptions:
|
||||
templateName: 'GenderSelect'
|
||||
|
||||
|
||||
Further reading
|
||||
===============
|
||||
|
||||
* :ref:`Form Configuration <concepts-configuration>`
|
||||
* :ref:`Register a custom stage template <apireference-formeditor-basicjavascriptconcepts-events-view-inspector-editor-insert-perform>`
|
||||
@@ -0,0 +1,82 @@
|
||||
prototypes:
|
||||
standard:
|
||||
formElementsDefinition:
|
||||
GenderSelect:
|
||||
implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement
|
||||
renderingOptions:
|
||||
templateName: 'RadioButton'
|
||||
properties:
|
||||
options:
|
||||
f: 'Female'
|
||||
m: 'Male'
|
||||
d: 'Diverse'
|
||||
formEditor:
|
||||
label: 'Gender Select'
|
||||
group: select
|
||||
groupSorting: 9000
|
||||
iconIdentifier: form-single-select
|
||||
editors:
|
||||
100:
|
||||
identifier: header
|
||||
templateName: Inspector-FormElementHeaderEditor
|
||||
200:
|
||||
identifier: label
|
||||
templateName: Inspector-TextEditor
|
||||
# Labels are retrieved from the default language file "EXT:form/Resources/Private/Language/Database.xlf"
|
||||
# The most keys follow the pattern: formEditor.elements.FormElement.editor.[identifier].[key]
|
||||
# In this example: "formEditor.elements.FormElement.editor.label.label"
|
||||
label: formEditor.elements.FormElement.editor.label.label
|
||||
propertyPath: label
|
||||
230:
|
||||
identifier: elementDescription
|
||||
templateName: Inspector-TextEditor
|
||||
label: formEditor.elements.FormElement.editor.elementDescription.label
|
||||
propertyPath: properties.elementDescription
|
||||
700:
|
||||
identifier: gridColumnViewPortConfiguration
|
||||
templateName: Inspector-GridColumnViewPortConfigurationEditor
|
||||
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.label
|
||||
configurationOptions:
|
||||
viewPorts:
|
||||
10:
|
||||
viewPortIdentifier: xs
|
||||
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xs.label
|
||||
20:
|
||||
viewPortIdentifier: sm
|
||||
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.sm.label
|
||||
30:
|
||||
viewPortIdentifier: md
|
||||
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.md.label
|
||||
40:
|
||||
viewPortIdentifier: lg
|
||||
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.lg.label
|
||||
50:
|
||||
viewPortIdentifier: xl
|
||||
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xl.label
|
||||
60:
|
||||
viewPortIdentifier: xxl
|
||||
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xxl.label
|
||||
numbersOfColumnsToUse:
|
||||
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.label
|
||||
propertyPath: 'properties.gridColumnClassAutoConfiguration.viewPorts.{@viewPortIdentifier}.numbersOfColumnsToUse'
|
||||
description: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.description
|
||||
800:
|
||||
identifier: requiredValidator
|
||||
templateName: Inspector-RequiredValidatorEditor
|
||||
label: formEditor.elements.FormElement.editor.requiredValidator.label
|
||||
validatorIdentifier: NotEmpty
|
||||
propertyPath: properties.fluidAdditionalAttributes.required
|
||||
propertyValue: required
|
||||
configurationOptions:
|
||||
validationErrorMessage:
|
||||
label: formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.label
|
||||
propertyPath: properties.validationErrorMessages
|
||||
description: formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.description
|
||||
errorCodes:
|
||||
10: 1221560910
|
||||
20: 1221560718
|
||||
30: 1347992400
|
||||
40: 1347992453
|
||||
9999:
|
||||
identifier: removeButton
|
||||
templateName: Inspector-RemoveElementEditor
|
||||
@@ -0,0 +1,105 @@
|
||||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||
xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers"
|
||||
data-namespace-typo3-fluid="true">
|
||||
|
||||
<f:comment>
|
||||
Custom form element template for a gender selection field.
|
||||
</f:comment>
|
||||
|
||||
<formvh:renderRenderable renderable="{element}">
|
||||
|
||||
<f:comment>Render the field wrapper with optional fieldset and legend</f:comment>
|
||||
<f:render partial="Field/Field"
|
||||
arguments="{
|
||||
element: element,
|
||||
renderFieldset: '{true}',
|
||||
doNotShowLabel: '{true}'
|
||||
}"
|
||||
contentAs="elementContent">
|
||||
|
||||
<f:form.validationResults for="{element.rootForm.identifier}.{element.identifier}">
|
||||
|
||||
<f:comment>Apply error class if validation errors exist</f:comment>
|
||||
<f:variable name="errorClass">
|
||||
{f:if(
|
||||
condition: '{validationResults.errors}',
|
||||
then: 'is-invalid'
|
||||
)}
|
||||
</f:variable>
|
||||
|
||||
<f:comment>Radio button group container</f:comment>
|
||||
<div id="{element.uniqueIdentifier}"
|
||||
class="gender-select {errorClass}"
|
||||
role="radiogroup"
|
||||
aria-label="{element.label}">
|
||||
|
||||
<f:comment>Loop through all available options (e.g., male, female, diverse)</f:comment>
|
||||
<f:for each="{element.properties.options}" as="label" key="value" iteration="idIterator">
|
||||
|
||||
<f:comment>Set ARIA attributes for accessibility</f:comment>
|
||||
<f:if condition="{element.properties.elementDescription}">
|
||||
<f:variable name="aria" value="{describedby: '{element.uniqueIdentifier}-desc'}" />
|
||||
</f:if>
|
||||
|
||||
<f:comment>Add error indication to the first radio button if validation fails</f:comment>
|
||||
<f:if condition="{validationResults.errors} && {idIterator.isFirst}">
|
||||
<f:variable name="aria" value="{
|
||||
invalid: 'true',
|
||||
describedby: '{element.uniqueIdentifier}-errors'
|
||||
}" />
|
||||
</f:if>
|
||||
|
||||
<f:comment>Individual radio button container</f:comment>
|
||||
<div class="form-check mb-2">
|
||||
<label class="form-check-wrapping-label"
|
||||
for="{element.uniqueIdentifier}-{idIterator.index}">
|
||||
|
||||
<f:form.radio
|
||||
property="{element.identifier}"
|
||||
id="{element.uniqueIdentifier}-{idIterator.index}"
|
||||
class="form-check-input"
|
||||
value="{value}"
|
||||
errorClass="is-invalid"
|
||||
additionalAttributes="{formvh:translateElementProperty(
|
||||
element: element,
|
||||
property: 'fluidAdditionalAttributes'
|
||||
)}"
|
||||
aria="{aria}"
|
||||
/>
|
||||
|
||||
<span class="{element.properties.labelTextClassAttribute}">
|
||||
{formvh:translateElementProperty(
|
||||
element: element,
|
||||
property: '{0: \'options\', 1: value}'
|
||||
)}
|
||||
</span>
|
||||
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</f:for>
|
||||
|
||||
</div>
|
||||
|
||||
<f:comment>Display validation errors</f:comment>
|
||||
<f:if condition="{validationResults.flattenedErrors}">
|
||||
<span id="{element.uniqueIdentifier}-errors"
|
||||
role="alert">
|
||||
|
||||
<f:for each="{validationResults.errors}" as="error">
|
||||
<f:format.htmlspecialchars>
|
||||
{formvh:translateElementError(element: element, error: error)}
|
||||
</f:format.htmlspecialchars>
|
||||
<br/>
|
||||
</f:for>
|
||||
|
||||
</span>
|
||||
</f:if>
|
||||
|
||||
</f:form.validationResults>
|
||||
|
||||
</f:render>
|
||||
|
||||
</formvh:renderRenderable>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user