Formulaires imbriqués

Voir la vidéo
Description Sommaire

Dans ce chapitre je vous propose de découvrir comment gérer un système de formulaire imbriqué. Cela sera l'occasion de découvrir le type CollectionType qui permet par exemple de gérer les données d'une relation OneToMany.

Pour commencer on va créer une classe de formulaire pour la donnée liée, classe que l'on pourra utiliser dans notre formulaire principal.

    ->add('quantities', CollectionType::class, [
        'entry_type' => QuantityType::class,
        'allow_add' => true,
        'allow_delete' => true,
        'by_reference' => false,
        'entry_options' => ['label' => false],
        'attr' => [
            'data-controller' => 'form-collection'
        ]
    ])
  • allow_add permet l'ajout de nouvel élément, cela ajoutera un attribut
    data-prototype au niveau du conteneur HTML.
  • allow_delete permet de supprimer un élément à la volée
  • by_reference indique qu'on ne modifie pas directement la collection, mais que l'on doit appeler les méthodes addXXX / removeXXX sur notre entité à la place.

On doit ensuite ajouter du code JavaScript pour ajouter les boutons d'action.

// assets/controller/form-collection_controller.js
import { Controller } from '@hotwired/stimulus';

export default class extends Controller {

    static values = {
        addLabel: String,
        deleteLabel: String
    }

    /**
    * Injecte dynamiquement le bouton "Ajouter" et les boutons "Supprimer"
    */
    connect() {
        this.index = this.element.childElementCount
        const btn = document.createElement('button')
        btn.setAttribute('class', 'btn btn-secondary')
        btn.innerText = this.addLabelValue || 'Ajouter un élément'
        btn.setAttribute('type', 'button')
        btn.addEventListener('click', this.addElement)
        this.element.childNodes.forEach(this.addDeleteButton)
        this.element.append(btn)
    }

    /**
     * Ajoute une nouvelle entrée dans la structure HTML
     * 
     * @param {MouseEvent} e
     */
    addElement = (e) => {
        e.preventDefault()
        const element = document.createRange().createContextualFragment(
            this.element.dataset['prototype'].replaceAll('__name__', this.index)
        ).firstElementChild
        this.addDeleteButton(element)
        this.index++
        e.currentTarget.insertAdjacentElement('beforebegin', element)
    }

    /**
     * Ajoute le bouton pour supprimer une ligne
     * 
     * @param {HTMLElement} item
     */
    addDeleteButton = (item) => {
        const btn = document.createElement('button')
        btn.setAttribute('class', 'btn btn-secondary')
        btn.innerText = this.deleteLabelValue || 'Supprimer'
        btn.setAttribute('type', 'button')
        item.append(btn)
        btn.addEventListener('click', e => {
            e.preventDefault()
            item.remove()
        })
    }
}

Et voila, notre entité sera ensuite automatiquement hydratée et Doctrine saura porter les modifications en base de données.

Publié
Technologies utilisées
Auteur :
Grafikart
Partager