19-14 Confirmation de suppression et un peu de magie

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.
Modal rendu serveur
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:
- En cliquant sur supprimer, htmx fera une requête
GET
async vers une action du contrôleur - Le contrôleur répondra avec une vue partielle (modal)
- htmx remplacera le contenu de
app-modal-manager
par delui de la vue partielle (modal) - 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.
- Créez sous
Web.Mvc/Views/Questions
une vue partielle (Add
->Razor MVC Partial View
) - Nommez la vue
_ModalConfirmationDelete
Vous pouvez mettre le code suivant à l'intérieur:
@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>
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êmeform
que dans la vue index des questions. Bref, le bouton de confirmation du formulaire fait une requêteDELETE
vers l'actionDelete
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
.
// 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.
<!-- ... -->
@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>
}
<!-- ... -->
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.
<!-- ... -->
</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!
De plus, vous remarquerez que le modal a été ajouté au div
app-modal-manager
:
Il nous faut un peu de JavaScript pour déclencher l'ouverture de la fenêtre modale.
JavaScript pour délencher l'ouverture
- Créez un fichier
Snowfall.Web.Mvc/assets/js/modal.js
- Réagir à l'événement
afterSwap
dehtmx
et afficher la fenêtre modaleSnowfall.Web.Mvc/assets/js/modal.jsimport { 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();
}
}); - Importez
modal.js
dansapp.js
Snowfall.Web.Mvc/assets/js/app.jsimport '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à!

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