Aller au contenu principal

19-14 Confirmation de suppression et un peu de magie

http://localhost:4200

La suppression de question est un peu brutale dans le sens qu'elle ne laisse pas de marge d'erreur. Vous accrochez le bouton, pouf, disparu!

Il est pratique courante d'afficher un message de confirmation. On pourrait utiliser une fenêtre dialog supportée nativement par le navigateur en utilisant un peu de JavaScript, mais c'est une bonne prémisse pour présenter les fenêtres modales.

Htmx nous aidera et on ajoutera une autre librairie, Hyperscript, pour nous aider.

On pourrait utiliser un modal rendu client, mais comme chaque lien de suppression est un peu différent, on ne peut pas utiliser exactement la même fenêtre pour toutes les questions.

En effet, chaque question pourrait avoir son modal propre à lui dans la page, mais cela ferait beaucoup de modals, même si ces derniers sont cachés, c'est du HTML de plus dans la page.

On pourrait aussi écrire un peu de JavaScript personnalisé et ficeler quelque chose.

Ou... on pourrait utiliser les requêtes asynchrones htmx et demander une fenêtre modale au serveur! Vous l'aurez compris, ce sera l'approche privilégiée.

Principe général

On aura dans le layout un div vide au départ avec comme id app-modal-manager.

Ce div recevra un modal en provenance du serveur lorsque demandé. Cette action sera faite par htmx et retournera un modal avec le id app-modal.

Le div app-modal-manager sera en quelque sorte programmé pour afficher le modal reçu dès que son contenu changera.

En gros:

  1. En cliquant sur supprimer, htmx fera une requête GET async vers une action du contrôleur
  2. Le contrôleur répondra avec une vue partielle (modal)
  3. htmx remplacera le contenu de app-modal-manager par delui de la vue partielle (modal)
  4. On utilisera un peu de JavaScript pour que, lorsque le modal sera reçu, de l'afficher (app-modal).

Création de la vue partielle _ModalConfirmationDelete

La fenêtre modale de confirmation sera contenue dans une vue partielle.

  1. Créez sous Web.Mvc/Views/Questions une vue partielle (Add -> Razor MVC Partial View)
  2. Nommez la vue _ModalConfirmationDelete

Vous pouvez mettre le code suivant à l'intérieur:

Snowfall.Web.Mvc/Views/Questions/_ModalConfirmationDelete.cshtml
@model Question

<div class="modal" id="app-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Supprimer la question</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Êtes-vous certain de vouloir supprimer cette question?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<form class="card-link d-inline" asp-controller="Questions"
asp-action="Delete"
asp-route-id="@Model.Id"
asp-route-evenementId="@Model.EvenementId">
<input type="hidden" name="_method" value="DELETE" />
<button class="btn btn-danger">Supprimer</button>
</form>
</div>
</div>
</div>
</div>
info

La majeure partie du code est un modal bootstrap standard tel que vous auriez pu prendre directement de la documentation.

Les éléments importants sont:

  • id="app-modal": c'est l'identifiant du modal que nous utiliserons pour le récupérer
  • <form>: il s'agit du même form que dans la vue index des questions. Bref, le bouton de confirmation du formulaire fait une requête DELETE vers l'action Delete du contrôleur.

Action `ConfirmationDelete``

Au contrôleur QuestionsController, il faut une action pouvant retourner la fenêtre modale qui sera une vue partielle.

Ajoutez une action supplémentaire ConfirmationDelete.

Snowfall.Web.Mvc/Controllers/QuestionsController.cs
// GET /evenements/{idEvenement}/questions/{id}/confirmationdelete
[HttpGet("{id:int}/[action]")]
[Authorize]
public async Task<IActionResult> ConfirmationDelete(int evenementId, int Id)
{
var question = await _questionService.FindById(Id);

if (question == null) return NotFound();

return PartialView("_ModalConfirmationDelete", question);
}

Remplacer le bouton supprimer d'une question

On remplacera le bouton supprimer d'un commentaire par un lien htmx. Ce lien fera une requête au serveur afin que ce dernier nous retourne la fenêtre modale.

Snowfall.Web.Mvc/Views/Questions/Index.cshtml
<!-- ... -->

@foreach (Question question in Model.Questions)
{
<div class="card mb-3">
<div class="card-body">
<p class="card-text" style="white-space: pre-line">@question.Contenu</p>
<a class="card-link small"
asp-controller="Questions"
asp-action="Edit"
asp-route-evenementId="@Model.Evenement.Id"
asp-route-id="@question.Id">Modifier</a>
<a class="card-link link-danger small"
href="#"
hx-get
hx-controller="Questions"
hx-action="ConfirmationDelete"
hx-route-evenementId="@question.EvenementId"
hx-route-id="@question.Id"
hx-target="#app-modal-manager"
hx-trigger="click">Supprimer</a>
</div>
</div>
}

<!-- ... -->
info

Le lien fait une requête asynchrone vers /evenements/{idEvenement}/questions/{id}/confirmationdelete

Ensuite, élément important:

  • hx-target="#app-modal-manager" le retour HTML du serveur remplacera le contenu de l'élément dans la page ayant le id #app-modal-manager. Cet élément n'existe pas encore, mais ce sera l'élément dans la page responsable de recevoir les modals et de les afficher.

Ajouter l'élément app-modal-manager

On mettra cet élément dans le layout, à la toute fin.

Snowfall.Web.Mvc/Views/Shared/_Layout.cshtml
<!-- ... -->

</footer>
<div id="app-modal-manager"></div>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

<!-- ... -->

Premier test

On peut faire un premier test. Il n'y aura pas de fenêtre modale d'affichée, mais ouvrez l'onglet réseau avant d'appuyer sur le bouton "supprimer" et vous verrez passer une requête asynchrone retournant du HTML!

img

De plus, vous remarquerez que le modal a été ajouté au div app-modal-manager:

img

Il nous faut un peu de JavaScript pour déclencher l'ouverture de la fenêtre modale.

JavaScript pour délencher l'ouverture

  1. Créez un fichier Snowfall.Web.Mvc/assets/js/modal.js
  2. Réagir à l'événement afterSwap de htmx et afficher la fenêtre modale
    Snowfall.Web.Mvc/assets/js/modal.js
    import { Modal } from 'bootstrap';
    document.addEventListener('htmx:afterSwap', function (event) {
    // On vérifie que le `div` concerné est bien le div de fenêtres modales
    if (event.detail.target.id === "app-modal-manager") {
    // Initialiser le modal et l'afficher
    let modalElement = document.getElementById('app-modal');
    let modal = new Modal(modalElement);
    modal.show();
    }
    });
  3. Importez modal.js dans app.js
    Snowfall.Web.Mvc/assets/js/app.js
    import 'vite/modulepreload-polyfill'

    // Notre Scss, contenant Bootstrap
    import '../scss/app.scss'

    // Importation du JavaScript de Bootstrap
    import * as bootstrap from 'bootstrap'
    import htmx from 'htmx.org';
    import './flash-message';
    import './modal';

Essayer de supprimer une question

Et voilà!

http://localhost:4200

party-cat

Explications

  1. En cliquant sur supprimer, htmx fait une requête GET async vers /evenements/{idEvenement}/questions/{id}/confirmationdelete
  2. Le contrôleur répond avec une vue partielle (modal)
  3. htmx remplace le contenu de app-modal-manager par delui de la vue partielle (modal)
  4. Le script JS détecte lorsque htmx a effectué une opération de changement de contenu et affiche le modal (app-modal)

level up