Een formulierthema bouwen uit Twig-componenten
08 november 2023
5 minutes
In het vorige artikel kon je lezen hoe je atomaire Twig componenten kan bouwen. Een van de voordelen van atomaire componenten is dat je ze overal kan hergebruiken. In dit artikel willen we de formulier-specifieke atomen die we gemaakt hebben koppelen aan het symfony/form pakket. Dit maakt het mogelijk om geavanceerde form types te configureren in onze applicatie, en de form component te visualiseren met behulp van een Twig component.
Een eenvoudig tekstinvoerveld koppelen
Laten we beginnen met een zeer eenvoudige implementatie: we willen ons invoeratoom koppelen aan Symfony's TextType
. Om een input in Symfony forms te configureren, ziet de configuratie er als volgt uit:
$builder
->add('query', TextType::class, [
'placeholder' => 'Search',
])
Zoals u in het vorige artikel kon lezen, kan het formulierinvoer-atoom als volgt worden geconfigureerd:
{{ component('atoms:input', {placeholder: 'Search', name: 'query'}) }}
Om het TextType aan ons invoeratoom te koppelen, moeten we een aangepast Symfony-formulierthema definiëren. Hiermee kunnen we de manier waarop symfony/form het formulier weergeeft overschrijven. Aangezien ons invoeratoom meerdere invoertypes kan verwerken (tekst, e-mail, telefoon, ...), kunnen we de 'eenvoudige' formulierwidget overschrijven:
{# templates/form/theme.html.twig #}
{% extends 'form_div_layout.html.twig' %}
{%- block form_widget_simple -%}
{% set attributes = attr|merge({
type: type ?? 'text',
required: required,
value: value ?? '',
id: id,
name: full_name,
erroneous: (errors|length > 0),
view: form,
}) %}
{{ component('atoms:form:input', attributes) }}
{%- endblock form_widget_simple -%}
In deze code kun je zien dat de attributen die worden doorgegeven door symfony/form worden geproxied naar ons input atoom. Deze attributen kunnen door het input atoom gebruikt worden om het uiterlijk en de sfeer van het formulier aan te passen. Je zou bijvoorbeeld een rode rand rond het formulier kunnen weergeven als erroneous
in het input atoom als true
gemarkeerd is.
Voor elke Twig formuliercomponent die je maakt, kan je de manier overschrijven waarop symfony/form het overeenstemmende formuliertype weergeeft. Je kunt specificeren hoe labels, formulier rijen, fouten en nog veel meer moeten worden weergegeven. Dit laat je toe om de atomen te gebruiken als zelfstandige componenten en ze zelfs te koppelen aan formuliertypes.
Geavanceerde formuliertypes
Het bovenstaande voorbeeld is vrij eenvoudig: het definieert alleen hoe twig de invoer weergeeft. Dezelfde techniek kan echter ook worden toegepast op meer geavanceerde formuliertypes.
Stel dat je in je formulier optioneel naar de naam van een persoon wilt vragen en je wilt niet teveel optionele velden weergeven. U zou in dat geval een optioneel voornaam veld kunnen weergeven. Afhankelijk van of de gebruiker het veld invult of niet, kunt u ook het optionele veld achternaam tonen of verbergen.
Het formuliertype zou er zo uit kunnen zien:
class OptionalNameType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('firstName', TextType::class, [
'required' => false,
])
->add('lastName', TextType::class, [
'required' => false,
]);
}
}
Vervolgens kunt u bepalen hoe dit formulieronderdeel wordt weergegeven door een formulierthema voor dit onderdeel op te geven:
{% block optional_name_row %}
{{ component('molecules:search-or-create', {
form,
}|merge(component_attr ?? {})) }}
{% endblock %}
De Twig component zou er zo uit kunnen zien:
#[AsTwigComponent('molecules:optional-name', 'components/molecules/optional-name.html.twig')]
class OptionalNameComponent
{
public FormView $form;
}
Door de form view door te geven, kan je symfony's form helper functies gebruiken in je twig component. Deze worden weergegeven met behulp van je aangepaste formulier thema, dat je gekoppeld hebt aan je aangepaste twig componenten. De Twig template van deze component zou er zo uit kunnen zien:
<div {{ attributes }} {{ stimulus_controller('optional-name', {}) }}>
{{ form_row(form.firstName, {
widget_attr: stimulus_target('optional-name', 'firstNameInput').toArray(),
}) }}
{{ form_row(form.lastName, {
attr: stimulus_target('optional-name', 'lastNameRow').toArray()
}) }}
</div>
Er zijn enkele dingen op te merken over dit sjabloon:
Door de attributen door te geven kun je extra configuraties van het component uit het bovenliggende formuliertype configureren. Bijvoorbeeld: als u het wat opvulling of marge wilt geven.
Het sjabloon bevat stimulus controllers en targets. Die worden toegevoegd via Twig helpers die door Symfony UX ter beschikking worden gesteld. Deze worden toegevoegd om het formuliertype interactief te maken op basis van Javascript.
Nu we de gerenderde HTML van het formuliertype hebben, is het enige resterende deel om het interactief te maken met Javascript. Aangezien Symfony UX stimulus gebruikt om dit soort interacties te bereiken, zullen we een kleine Stimulus controller toevoegen:
import { Controller } from '@hotwired/stimulus';
export default class extends Controller<HTMLDivElement> {
static targets = ["firstNameInput", "lastNameRow"];
declare readonly firstNameInputTarget: HTMLInputElement;
declare readonly lastNameRowTarget: HTMLDivElement;
connect() {
this.updateUI();
this.firstNameInputTarget.addEventListener('change', () => this.updateUI());
}
updateUI() {
const hasFirstName = Boolean(this.firstNameInputTarget.value);
this.lastNameRowTarget.classList.toggle('hidden', !hasFirstName)
}
}
In een volgend artikel gaan we wat dieper in op Stimulus. Hier is een algemenere uitleg hoe het werkt: De controller en targets worden gekoppeld door Stimulus, gebaseerd op de attributen die we hebben toegevoegd in het sjabloon van het optionele naammolecuul.
In de bovenstaande controller wordt de lastName-rij alleen getoond als het firstName-veld een niet-lege waarde bevat. Wanneer de waarde van het invoerveld firstName verandert, wordt opnieuw besloten de rij van de lastName weer te geven.
In een volgend artikel gaan we
Hoewel deze component wat complexer begint te worden dan het eerste voorbeeld, is het gebruik ervan heel eenvoudig: Je kunt het OptionalNameType
form type toevoegen aan al je andere Symfony formulieren. Het formulier zal dit complexe component renderen en de stimulus controller toepassen op dat specifieke deel van het formulier.
Dit betekent dat je zeer complexe formuliertypes kunt hergebruiken - en de logica ervoor maar op één plaats hoeft te schrijven en te onderhouden.
Dat is krachtig!