Créer une modal personnalisée pour une action sur EasyAdmin

Créer une modal personnalisée dans l’administration de EasyAdmin n’est pas bien compliqué, mais peu documenté. Je n’en ai pas trouvé la référence dans la documentation officielle du bundle, c’est pourquoi je détaille ici un peu plus mon process.

Le but à atteindre.

Je tire cet article d’une expérience professionnelle. Je voulais mettre en place une action spécifique pour renvoyer une requête HTTP depuis le back office avec une confirmation avant de lancer la requête. Le tout intégré dans l’interface utilisateur d’EasyAdmin. Cette fonctionnalité pourrait être facilement traitée en utilisant la fonction confirm() en JavaScript, mais c’est peu ergonomique et donne la sensation que l’interface n’est pas terminée. Ce n’est pas professionnel.

Voici plutôt ce que j’ai mis en place et que je vais présenter ici :

Tableau affichant deux requête avec une action spécifique sur la droite de l'image qui est "Renvoyer"

On peut voir le bouton « Renvoyer » sur la droite. J’ai pris le parti de lui donner une couleur orange pour mettre en avant cette action. Quand on clique sur ce bouton, voici la modal qui apparaît :

Ici la modal est claire sur l’action qui va être effectuée. Il y a un titre, et une explication. Il y a possibilité d’annuler l’action ou de confirmer son choix. Je trouve que c’est pertinent d’un point de vue UX, même si on peut argumenter sur le choix des couleurs.

Mise en place de la solution

Création de l’action

La première étape est d’ajouter l’action à la liste dans le tableau. Pour cela, je viens modifier mon RequestCrudController.php et rajouter une action spécifique.

public function configureActions(Actions $actions): Actions
{
    $actions = parent::configureActions($actions);
    $actions->add(
        Crud::PAGE_INDEX,
        Action::new('request.resend', 'easy.admin.actions.resend', 'fas fa-paper-plane-top')
            ->linkToCrudAction('resend')
            ->addCssClass('text-warning confirm-action')
            ->setHtmlAttributes([
                'data-bs-toggle' => 'modal',
                'data-bs-target' => '#modal-resend',
            ])
    );
    return $actions;
}

Je crée donc une action sur le listing des requêtes. Je lui donne un identifiant unique ainsi qu’un label qui sera définit plus tard. Je fais un lien vers une méthode que je vais créer qui s’appellera resend et j’ajoute ensuite les attributs utilisés via JavaScript qui permettent de styliser et configurer le bouton pour afficher la modal.

Je crée ensuite ma fonction resend toujours dans mon RequestCrudController.php

public function __construct(
    private readonly AdminContextProvider $adminContextProvider,
    private readonly AdminUrlGenerator $adminUrlGenerator,
    private readonly EntityManagerInterface $entityManager,
    private readonly RequestMessageService $requestMessageService,
) {
}

// ... reste du code de la classe

public function resend(AdminContext $context): Response
{
    $url = $this->adminUrlGenerator
        ->setAction(Action::INDEX)
        ->removeReferrer()
        ->setController($context->getCrud()?->getControllerFqcn() ?? '')->generateUrl();

    /** @var Request|null $request */
    $request = $context->getEntity()->getInstance();
    if (!$request) {
        $this->addFlash('danger', 'easy.admin.flash.resend.danger');
    } else {
        $data = $request->getData();
        $data['manuel'] = 'Oui';

        $this->requestMessageService->dispatchFormRequest($data);

        $request->setData($data);
        $request->setResent(true);
        $this->entityManager->persist($request);
        $this->entityManager->flush();
        $this->addFlash('success', 'easy.admin.flash.resend.success');
    }

    return $this->redirect($url);
}

Le code devrait pouvoir etre lisible de soi-même, mais en gros, je récupère l’instance qui a été choisie. Si elle existe, alors je dispatch un message qui permet à mon code de gérer la logique métier. Ensuite je mets à jour ma requête et j’enregistre les changements.
Une fois que tout s’est bien effectué, j’affiche un message de succès à l’utilisateur pour qu’il ait une confirmation visuelle que son action s’est bien déroulée.

Création du template de la modal

Il faut maintenant créer la modal. Pour cela, je vais reprendre le code de la modal qui existe déjà sur EasyAdmin. C’est la modal qui permet de supprimer une entité avec l’action “Supprimer”. Elle est par défaut déjà inclue dans le thème de l’administration. Si on cherche dans le template qui est utilisé vendor/easycorp/easyadmin-bundle/src/Resources/views/crud/index.html.twig, on voit à la ligne 230 le code suivant :

{% block delete_form %}
        {{ include('@EasyAdmin/crud/includes/_delete_form.html.twig', with_context = false) }}
    {% endblock delete_form %}

Ce qu’on va donc faire, c’est copier le template _delete_form.html.twig et hériter ce block pour rajouter notre template que l’on va créer. Je crée donc un fichier dans mon code qui se nomme templates/bundles/EasyAdminBundle/crud/index.html.twig

J’y met le code suivant:

{% extends '@!EasyAdmin/crud/index.html.twig' %}

{% block delete_form %}
    {{ parent() }}
    {{ include('admin/modal/resend_form_request.html.twig', with_context = false) }}
{% endblock delete_form %}

Je récupère donc le contenu du block delete_form, car je ne souhaite pas supprimer cette modal de suppression. J’y rajoute juste un nouveau fichier que je place dans templates/admin/modal/resend_form_request.html.twig.
C’est une copie de _delete_form.html.twig ou je vais changer toutes les références de “delete” en “resend”.


{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
<form class="d-none" method="post" id="resend-request">
    <input type="hidden" name="token" value="{{ ea_csrf_token('ea-resend') }}" />
</form>

<div id="modal-resend" class="modal fade" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-body">
                <h4>{{ 'easy.admin.modal.resend.title' | trans }}</h4>
                <p>{{ 'easy.admin.modal.resend.content' | trans }}</p>
            </div>
            <div class="modal-footer">
                <button type="button" data-bs-dismiss="modal" class="btn btn-secondary">
                    <span class="btn-label">{{ 'easy.admin.modal.resend.cancel' |  trans }}</span>
                </button>

                <button type="button" data-bs-dismiss="modal" class="btn btn-warning" id="modal-resend-button" form="resend-request">
                    <span class="btn-label">{{ 'easy.admin.modal.resend.submit' | trans }}</span>
                </button>
            </div>
        </div>
    </div>
</div>

Voici le code HTML qui se retrouve sur l’index de mon controller CRUD. On remarque bien deux modals côte-à-côte.

Création du script pour gérer l’affichage de la modal

Maintenant il faut gérer le clic sur le bouton de confirmation de la modal. Pour cela, je viens créer un fichier dans public/static/admin/scripts/confirmResendRequest.js. J’y met le code suivant :

document.addEventListener("DOMContentLoaded", () => {
    document.querySelectorAll(".confirm-action").forEach(action => {
        action.addEventListener("click",e => {
            e.preventDefault();
            document.querySelector("#modal-resend-button").addEventListener("click",() => {
                location.replace(action.getAttribute("href"));
            });
        });
    });
});

Ce script va seulement écouter sur le bouton de confirmation de la modal. Lors du clic, on va rediriger l’utilisateur vers la page définie dans l’attribut href de notre bouton.

Finaliser le tout avec des messages

Pour finir le tout, il n’y a plus qu’à rajouter les traductions des messages, car jusqu’à présent on a utilisé des identifiants. Il faut donc rajouter les labels dans le fichier translations/messages.yaml :

easy:
    admin:
        actions:
            resend: Renvoyer
        modal:
            resend:
                title: Renvoyer la requete ?
                content: Renvoyer la requête au CRM. Une propriété “manuel” avec la valeur “Oui” sera ajouté au cors de la requête pour distinguer que cet envoi à été fait manuellement.
                cancel: Annuler
                submit: Je veux renvoyer la requete
        flash:
            resend:
                success: La requête à bien été renvoyée
                danger: La requête n'a pas pu être renvoyée

Commentaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *