# Form handling
TYPO3 Headless supports Form Framework (opens new window) and nuxt-typo3 supports form handling.
Forms handling is disabled by default. You can enable it in nuxt.config
typo3: {
forms: true
},
# TYPO3 Headless Limitations
Please note, that currently we are experiencing following limitations on API level:
- TYPO3 Headless supports only one step forms (however, you are still able to build multistep forms on frontend side - guide soon).
- You have to enable specific headless.elementBodyResponse feature flag (opens new window) to handle POST requests more easily.
- You have to add custom finishers to handle
success
state and redirects (headless.redirectMiddlewares ) (opens new window) in a more sophisticated way.
Look at code example (opens new window)
# Component data flow
To handle Form Framework (opens new window) we have implemented FormFramework Content Element (opens new window) which uses T3Form component (opens new window). This is good example of distribution UI components and Content elements where UI is responsible for displaying interface based on delivered props. In that specific case FormFramework Content Element is responsible for delivering props to UI and submiting form data to API. You can use T3Form component (opens new window) for more specific cases, not only for T3FormFramework - you can build your own forms, not related with FormFramework ContentElement. One thing you have to do is use our T3Form schema. You may also override whole T3Form component to implement complicated scenarios. However, before you decide to do so, please read this documentation. We have provided a lot of ways of customising forms.
For model validation we have used VeeValidate (opens new window) plugin which in our opinion is the best way to validate forms in Vue.js applications.
# FormFramework Content Element
Entry point for form handling is T3CeFormFormframework
(opens new window) and this component is responsible for wraping T3Form
and exchaning information between them. At this level you can override form template, default I18n labels and customize mapping fields to T3Form schema.
# Customize T3CeFormFormframework
To customize T3CeFormFormframework
you have to register a new one with the same name. This is common solution to override global components (opens new window).
To do this please create a new file components/T3CeFormFormframework.vue
:
<template>
<div>form markup</div>
</template>
<script>
import T3CeFormFormframework from '~typo3/components/T3CeFormFormframework/T3CeFormFormframework.vue'
export default {
extends: T3CeFormFormframework
}
</script>
Please notice it extends logic from T3CeFormFormframework
Now register it as global component in plugins/components.js
file
import Vue from 'vue'
import T3CeFormFormframework from '~/components/T3CeFormFormframework.vue'
Vue.component('T3CeFormFormframework', T3CeFormFormframework)
Please remember to add components.js
file to nuxt plugins (opens new window).
After that you should be able to see your new component.
# Customize markup and logic
This FormFramewor Content element is mainly responsible for the logic of form submiting. If it comes to markup, it wraps the T3Form
in order to pass formData elements and init methods to form events.
Please remember that whole form is generated based on elements
prop which is delivered by API.
To override T3Form template on this level you can use slots (Take a look at the snippet below):
- before - inside
<form>
tag - before field list render - after - inside
<form>
tag - after field list render - cta - submit/reset button template
- fields - template for fields rendering - not recommended to override here, there is easier way to customize form fields - read it.
If it comes to logic, you may want to handle your forms in more complex scenario. For example currently we support redirect as a finisher of the form (if the finisher exist), but you can add more ways to handle it. You can override onSuccess
method.
At this level you can also provide custom css classes for your fields.
components/T3CeFormFormframework.vue
:
<template>
<T3Form
ref="form"
:elements="elements"
:classes="css"
@submit="onSubmit"
>
<template #before="{model}">
{{ model }}
</template>
<template #cta>
<button type="submit">
Send form
</button>
</template>
<template #after="{state}">
<p v-if="state.error || state.failture">
{{ i18n.serverError }}
</p>
<p v-if="state.success">
{{ i18n.serverSuccess }}
</p>
</template>
</T3Form>
</template>
<script>
import T3CeFormFormframework from '~typo3/components/T3CeFormFormframework/T3CeFormFormframework.vue'
export default {
extends: T3CeFormFormframework,
data () {
return {
css: Object.freeze({
// field.identifier : class name
name: 'wrap-my-name-field'
}),
// map TYPO3 Form Framework validation rules to vee-validate schema
rules: {
NotEmpty: 'required',
EmailAddress: 'email',
RegularExpression: {
identifier: 'regex',
options: {
regex: 'pattern'
}
}
}
}
},
methods: {
onSuccess (actionAfterSuccess) {
if (actionAfterSuccess?.redirectUri) {
this.$router.push(actionAfterSuccess.redirectUri)
} else {
// do something else
}
},
}
}
</script>
# Customize field templates
Each form field uses T3FormField
(opens new window) component as the base template. You can override this template:
Create and register global components components/T3FormField.vue
:
<template>
<ValidationProvider
v-slot="state"
:vid="field.identifier"
:name="field.label"
:rules="rules"
:custom-messages="messages"
slim
>
<div class="field-row">
<label :for="field.identifier"> {{ field.label }} </label>
<slot :state="state">
<input
:id="field.identifier"
v-model="innerValue"
:type="field.type"
:label="field.label"
:name="field.name || field.identifier"
:class="state.classes"
:placeholder="placeholder"
:required="required"
@input="(event) => $emit('input', event.target.value)"
>
</slot>
<ul v-if="state.errors.length" class="t3-form-field__errors">
<li v-for="(error, key) in state.errors" :key="key">
{{ error }}
</li>
</ul>
</div>
</ValidationProvider>
</template>
<script>
import { T3FormField } from '~typo3/components/T3Form'
export default {
name: 'T3FormField',
extends: T3FormField
}
</script>
<style scoped>
.field-row {
display: flex;
align-items: center;
margin: 10px 0;
}
label {
font-weight: bold;
margin-right: 10px;
}
ul {
margin: 0;
color: red;
}
</style>
This component uses <ValidationProvider/>
(opens new window) for field validation.
This template will be suitable for most regular input elements. You can use this template to provide more form types like textarea, checkbox, etc.
# Field list rendering
To be able to easily customize template fields, it's important to understand how we generate field list.
All fields are generated in the loop, based on T3Form schema. T3FormFormFramework component is responsible for mapping fields to this strategy.
{
"elements": [
{
"type": "fieldset",
"fieldlist": true,
"identifier": "fieldset-1",
"label": "Personal Information",
"value": null,
"elements": [
{
"value": "",
"validators": [
{
"identifier": "email",
"message": "You must enter a valid email address."
},
{
"identifier": "required",
"message": "This field is mandatory."
}
],
"type": "email",
"identifier": "email",
"label": "Email",
"placeholder": "your email",
"required": true,
"name": "tx_form_formframework[email]"
},
{
"value": "",
"type": "text",
"identifier": "name",
"label": "Name",
"description": "",
"required": true,
"validators": [
{
"identifier": "required",
"message": "This field is mandatory."
}
],
"name": "tx_form_formframework[name]"
},
{
"value": "+49",
"type": "text",
"identifier": "phone",
"label": "Telephone",
"placeholder": "00-000-000-000",
"validators": [
{
"options": {
"regex": "^[0-9-+]+$"
},
"identifier": "regex",
"message": "You must enter a valid value. Please refer to the description of this field."
}
],
"name": "tx_form_formframework[phone]"
}
]
}
]
}
You may have noticed that name
and email
and phone
fields are nested in fieldset-1
which is nested in fieldset-1
and fieldset-1
is fieldlist
type to generate nested elements. It means we have to render component fields in recursion.
T3FormFieldList
component (opens new window) is responsible for rendering this list.
This part of the template is responsible for matching frontend component with field.type
or field.identifier
.
<component
:is="getComponentField(field)"
v-model="model[field.name]"
:field="field"
/>
For example: to display and render input type="hidden"
we had to register T3FormFieldHidden
.
If you want to add custom textarea field with type "textarea" then you have to register T3FormFieldTextarea
You can also register custom field component for specific field - T3FormFieldName
if your field.identifier === 'name'
Default one is T3FormField
# Add new field type
At this moment we support
- regular input fields like text, email, number
- single select
- fieldset
- honeypot == hidden
But you can easliy add new field type.
For example we can add FormFieldCheckbox
. In that case add new global component components/T3FormFieldCheckbox.vue
:
<template>
<T3FormField v-bind="$props">
<template #default="{state}">
<input
:id="field.identifier"
v-model="innerValue"
type="checkbox"
:label="field.label"
:name="field.name || field.identifier"
:class="state.classes"
:required="required"
:true-value="true"
false-value=""
@input="(event) => $emit('input', event.target.value)"
>
</template>
</T3FormField>
</template>
<script>
import { T3FormField } from '~typo3/components/T3Form'
export default {
name: 'T3FormFieldCheckbox',
components: {
T3FormField
},
extends: T3FormField
}
</script>
TIP
Notice that we have used default T3FormField
as the base template for new form field type. Thanks to that we can create multiple form types with the same template. On the other hand you can put all input types in one FormField component and match them by v-if.
# Add new validation rule
For model validation we use vee-validate
.
If you want to use validation rules from vee-validate
you have to map validation rules which come from API to vee-validate naming.
Example in T3FormFormFramework
:
rules: {
NotEmpty: 'required',
EmailAddress: 'email',
RegularExpression: {
identifier: 'regex',
options: {
expression: 'regex'
}
}
}
If you want to extend these rules please override T3FormFormFramework
component and rules
data object.
For example:
<script>
import T3CeFormFormframework from '~typo3/components/T3CeFormFormframework/T3CeFormFormframework.vue'
export default {
extends: T3CeFormFormframework,
data () {
return {
// map TYPO3 Form Framework validation rules to vee-validate schema
rules: {
Match: 'confirmed',
NotEmpty: 'required',
EmailAddress: 'email',
RegularExpression: {
identifier: 'regex',
options: {
regex: 'pattern'
}
}
}
}
}
}
</script>
If you don't want to map rules, then you can add own rule:
import { extend } from 'vee-validate';
extend('Length', {
validate(value, args) {
return value.length >= args.length;
},
params: ['length']
});
All additional rules are available here (opens new window).