La fenêtre modale est sans aucun doute l’un des IU (Interface usager) les plus simples et les plus utiles. À la différence de la fenêtre PopUp hors DOM (Document Object Model) qu’on peut perdre sous la fenêtre principale, la fenêtre modale beaucoup plus ergonomique et flexible à l’avantage d’être membre du DOM de la page.
Fenêtre modale Bootstrap 4.1.3 avec iFrame
Par contre, la fenêtre modale n’a pas les attributs ni les méthodes de l’objet fenêtre (window), c’est un simple DIV qu’il faut créer et manipuler. C’est pourquoi ce tutoriel utilise comme base le cadriciel Bootstrap (version 4). Voilà la modale Bootstrap de base avec ces dépendances CSS et jQuery :
<!doctype html> <html lang="fr"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> <title>Fenêtre modale Bootstrap 4.1.3</title> </head> <body> <h1>Fenêtre modale Bootstrap 4.1.3</h1> <button type="button" class="btn btn-secondary" data-toggle="modal" data-target="#oMessagerie"> Ouvrir la fenêtre modale </button> <div class="modal fade" id="oMessagerie" tabindex="-1" role="dialog" aria-labelledby="oMessagerieLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="oMessagerieLabel">Titre</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Fermer"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> Contenu </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Fermer</button> </div> </div> </div> </div> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script> </body> </html>
Outre le fait d’utiliser beaucoup de code, HTML, CSS et de dépendance aux JavaScript tant Bootstrap que jQuery, le problème avec cette fenêtre modale c’est qu’il faut recréer le même code pour chaque fenêtre modale. Du simple message d’erreur à la fenêtre modale des plus complexe. Si c’est excellent pour afficher une invitation de partager de la page d’accueil, c’est complètement absurde pour une application qui demanderait un grand nombre de fenêtres modales. Voilà pourquoi la fenêtre modale devrait toujours être dynamique, c’est-à-dire une seule fenêtre modale utilisée comme conteneur avec un contenu dynamique intégré en JavaScript. Son contenu peut alors être ajouté directement dans le lien qui ouvre la fenêtre modale, dans un iFrame ou mieux, à l’aide d’un appel Ajax. Avec un appel Ajax au serveur, c’est possible d’intégrer un contenu illimité à notre fenêtre modale, à partir d’une base de données par exemple.
Personnellement, j’utilise cette technique pour afficher tous mes messages d’erreur et d’information, simplifiant et surtout normalisant tant l’ergonomie du site que la programmation. Mais aussi pour ajouter une première couche de sécurité aux communications ! Un formulaire dans une fenêtre modale dynamique à l’avantage d’être caché et du coup protégée des nombreux engins capable d’en abuser. En profitant du manque d’intelligence des engins et surtout des abuseurs, la réponse aussi peut être gérée en arrière-plan sans offrir la possibilité aux pourrielleurs à la merci de leur application de capturer l’adresse du formulaire. Bien entendu, il suffit d’un programmeur pour récupérer cette adresse, mais vous serez ainsi protégé de la majorité des abuseurs...
Voyons comment dynamiser cette fenêtre modale à priorité statique.
Dimensions de la fenêtre modale
Avant d’aller plus loin, Bootstrap offre 3 grandeurs de fenêtre modale, la normale comme l’exemple plus haut, la petite avec la classe .modal-sm
et la grosse avec la classe .modal-lg
. Vous pouvez ajouter la classe .modal-xl
toujours compatible mais si c’est de la version 3. Mais n’ajoutez pas ces classes directement dans la fenêtre modale. Utilisez plutôt la fonction « twMessagerie » pour définir le type de fenêtre modale. J’y ajoute d’autres classes pour mes exemples. Vous pouvez créer vos propres classes.
- .cModalIframeConteneur : Pour intégrer un iFrame ;
- .cModalPlein : Pour une fenêtre modale plein écran (éviter avec un iFrame) ;
- .cModalMedium : Pour une grosse fenêtre modale (éviter avec un iFrame) ;
- .cModalSansEntete : Pour retirer l’entête et le pied (idéale avec un iframe) ;
- .cModalRouge : Une fenêtre modale avec entête rouge ;
- .cModalInfo : Une fenêtre modale entièrement bleue.
<style> .cModalIframeConteneur { overflow: hidden; padding-top: 56.25%; position: relative; width: 100%; } .cModalIframeConteneur iframe { border: 0; height: 100%; left: 0; position: absolute; top: 0; width: 100%; } .cModalPlein {min-width: 100%;margin: 0;} .cModalPlein .modal-header {-webkit-border-radius: 0px;-moz-border-radius: 0px;border-radius: 0px;} .cModalPlein .modal-content {min-height: 100vh;border-radius: 0;-moz-border-radius: 0;-webkit-border-radius: 0;} .cModalPlein .modal-footer {-webkit-border-radius: 0px;-moz-border-radius: 0px;border-radius: 0px;} .cModalMedium {min-width: 90%;margin: 0 auto;} .cModalMedium .modal-content {min-height: 80vh;border-radius: 0;-moz-border-radius: 0;-webkit-border-radius: 0;} .cModalSansEntete .modal-header {display:none;} .cModalSansEntete .modal-footer {display:none;} .cModalRouge .modal-header {-webkit-border-radius: 0px;-moz-border-radius: 0px;border-radius: 0px;background-color: #dc3545;color: #ffffff;} .cModalRouge .modal-footer {-webkit-border-radius: 0px;-moz-border-radius: 0px;border-radius: 0px;background-color: #dc3545;color: #ffffff;} .cModalInfo .modal-header {background-color: #17a2b8;color: #ffffff;border:none;} .cModalInfo .modal-body {background-color: #17a2b8;color: #ffffff;} .cModalInfo .modal-footer {background-color: #17a2b8;color: #ffffff;border:none;} </style>
Fenêtre modale avec contenu dynamique
Bien que Bootstrap 4 vient avec une base jQuery, le premier exemple est en pur JavaScript dit « vanilla » sans jQuery. Il suffit de spécifier data-toggle="modal"
avec la cible de la fenêtre modale, c’est-à-dire son ID data-target="#oMessagerie"
pour ouvrir la fenêtre modale conventionnelle. Ajouter la classe « cBoutonModal » pour identifier le bouton et l’attacher à la fonction, plus 4 paramètres :
- data-titre : Le titre de la fenêtre modale ;
- data-contenu : Le contenu de la fenêtre modale ;
- data-src : Le URL d’une page Web. Si spécifier, la fonction intègrera un iFrame ;
- data-class : Classes supplémentaires.
<button type="button" class="btn btn-primary cBoutonModal" data-toggle="modal" data-target="#oMessagerie" data-titre="Exemple sans iFrame 1" data-class="cModalInfo cModalPlein" data-contenu="Exemple de contenu 1"> Test direct 1 </button> <button type="button" class="btn btn-primary cBoutonModal" data-toggle="modal" data-target="#oMessagerie" data-titre="Exemple sans iFrame 2" data-class="cModalRouge modal-lg" data-contenu="Exemple de contenu 2"> Test direct 2 </button> <button type="button" class="btn btn-primary cBoutonModal" data-toggle="modal" data-target="#oMessagerie" data-titre="Exemple iFrame" data-class="cModalRouge modal-xl cModalSansEntete" data-contenu="Erreur" data-src="http://neural.quebec"> Test iFrame </button> <script> // Associer la fonction à chaque bouton let aBoutons = document.querySelectorAll(".cBoutonModal"); aBoutons.forEach(function(elem) {elem.addEventListener("click", function() {twMessagerie("oMessagerie",this);}, false);}); </script>
Et finalement la fonction « twMessagerie »
<script> function twMessagerie(oModalTemp,_this) { // Récupérer la fenêtre modale. var oObjet = document.getElementById(oModalTemp); // Vider le contenu. oObjet.querySelector(".modal-body").innerHTML = ""; // S’il y a un titre, ajouter le titre à la fenête modale. if (_this.hasAttribute("data-titre")) {oObjet.querySelector(".modal-title").innerHTML = _this.getAttribute("data-titre");} // S’il y a une ou plusieurs classes, ajouter les classes. // Noter que j’utilise la méthode « className » au lieu du nouveau « classList » incompatible IE 10... // Et pour éviter de tomber sur une fenêtre avec une vielle classe, // supprimer les classes en s’assurant de gardant « modal-dialog » bien sûr. if (_this.hasAttribute("data-class")) { oObjet.querySelector(".modal-dialog").className = "modal-dialog "+_this.getAttribute("data-class"); }else{ oObjet.querySelector(".modal-dialog").className = "modal-dialog" } // Si l’attribut « data-src » est présent avec une adresse // Ajouter le iFrame if (_this.hasAttribute("data-src")&&_this.getAttribute('data-src')!='') { // Avec iFrame, intégrer un iFrame et supprimer le « padding ». oObjet.querySelector(".modal-body").innerHTML = "<div class='cModalIframeConteneur'><iframe src='"+_this.getAttribute('data-src')+"'></iframe></div>"; oObjet.querySelector(".modal-body").className = "modal-body p-0"; } else { // Sinon intégrer le contenu et remettre le « padding ». oObjet.querySelector(".modal-body").innerHTML = _this.getAttribute('data-contenu'); oObjet.querySelector(".modal-body").className = "modal-body"; } } </script>
Avec jQuery
Un exemple plus simple mais cette fois avec jQuery et l’événement « onclick ». Mais attention les fonctions Ajax de jQuery ne sont pas disponible avec la version « slim » avec Bootstrap. Vous devez ajouter la librairie jQuery complète AVANT le code :
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#oMessagerie" onclick="twMessagerieJquery2('#oMessagerie','Exemple jQuery','<p>Test JavaScript jQuery</p>','text-white','bg-danger');">Test jQuery</button> <script> function twMessagerieJquery2(oModalTemp,oTitreTemp,sCouleurTemp,sCouleurFondTemp) { $(oModalTemp).find(".modal-title").html(oTitreTemp); $(oModalTemp).find(".modal-title").css("font-weight", "bold"); $(oModalTemp).find(".modal-header").addClass(sCouleurTemp); $(oModalTemp).find(".modal-header").addClass(sCouleurFondTemp); $(oModalTemp).find(".modal-body").html('<p><a href="" onclick="twJqueryAjax(event,'modal.txt','#oMessagerie','.modal-body')">Plus de texte</a></p>';); } function twJqueryAjax(e,sUrlTemp,sDivTemp,sDivBodyTemp) { e.preventDefault(); $.ajax({url: sUrlTemp, error: function(xhr){ alert("Une erreur est survenue : " + xhr.status + " " + xhr.statusText); }, success: function(result){ // Ajout du contenu à la suite du texte de la modale. $(sDivTemp).find(sDivBodyTemp).html(result+$(sDivTemp).find(sDivBodyTemp).html()); }}); } </script>
Contenu dynamique avec Ajax en pure JavaScript (vanilla)
C’est ici que le fun commence, contenu dynamique mais externe c’est encore mieux. Dans mon exemple j’utilise un simple fichier texte avec un paragraphe « Lorem ipsum ». Mais ce pourrait être un script serveur avec une requête SQL par exemple...
Le lien dans le texte pour faire l’appel Ajax :
<a href='modal.txt' data-div='oMessagerie' onclick='twAjax(event,this)'>Plus de texte</a>
Exemple complet Ajax
<!doctype html> <html lang="fr"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> <title>Fenêtre modale Bootstrap 4.1.3</title> <style> .cModalIframeConteneur { overflow: hidden; padding-top: 56.25%; position: relative; width: 100%; } .cModalIframeConteneur iframe { border: 0; height: 100%; left: 0; position: absolute; top: 0; width: 100%; } .cModalPlein {min-width: 100%;margin: 0;} .cModalPlein .modal-header {-webkit-border-radius: 0px;-moz-border-radius: 0px;border-radius: 0px;} .cModalPlein .modal-content {min-height: 100vh;border-radius: 0;-moz-border-radius: 0;-webkit-border-radius: 0;} .cModalPlein .modal-footer {-webkit-border-radius: 0px;-moz-border-radius: 0px;border-radius: 0px;} .cModalMedium {min-width: 90%;margin: 0 auto;} .cModalMedium .modal-content {min-height: 80vh;border-radius: 0;-moz-border-radius: 0;-webkit-border-radius: 0;} .cModalSansEntete .modal-header {display:none;} .cModalSansEntete .modal-footer {display:none;} .cModalRouge .modal-header {-webkit-border-radius: 0px;-moz-border-radius: 0px;border-radius: 0px;background-color: #dc3545;color: #ffffff;} .cModalRouge .modal-footer {-webkit-border-radius: 0px;-moz-border-radius: 0px;border-radius: 0px;background-color: #dc3545;color: #ffffff;} .cModalInfo .modal-header {background-color: #17a2b8;color: #ffffff;border:none;} .cModalInfo .modal-body {background-color: #17a2b8;color: #ffffff;} .cModalInfo .modal-footer {background-color: #17a2b8;color: #ffffff;border:none;} </style> </head> <body> <h1>Fenêtre modale Bootstrap 4.1.3</h1> <button type="button" class="btn btn-primary cBoutonModal" data-toggle="modal" data-target="#oMessagerie" data-titre="Exemple sans iFrame 1" data-class="cModalInfo cModalPlein" data-contenu="Exemple de contenu 1"> Test direct 1 </button> <button type="button" class="btn btn-primary cBoutonModal" data-toggle="modal" data-target="#oMessagerie" data-titre="Exemple sans iFrame 2" data-class="cModalRouge modal-lg" data-contenu="Exemple de contenu 2"> Test direct 2 </button> <button type="button" class="btn btn-primary cBoutonModal" data-toggle="modal" data-target="#oMessagerie" data-titre="Exemple iFrame" data-class="cModalRouge modal-xl cModalSansEntete" data-contenu="Erreur" data-src="http://neural.quebec"> Test iFrame </button> <button type="button" class="btn btn-primary cBoutonModal" data-toggle="modal" data-target="#oMessagerie" data-titre="Exemple Ajax en JavaScript Vanille" data-class="cModalRouge" data-contenu="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. <a href='modal.txt' data-div='oMessagerie' onclick='twAjax(event,this)'>Plus de texte</a>">Test Ajax Vanilla</button> <div class="modal fade" id="oMessagerie" tabindex="-1" role="dialog" aria-labelledby="oMessagerieLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="oMessagerieLabel">Titre</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Fermer"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> Contenu... </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Fermer</button> </div> </div> </div> </div> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script> <script> // Associer la fonction à chaque bouton let aBoutons = document.querySelectorAll(".cBoutonModal"); aBoutons.forEach(function(elem) {elem.addEventListener("click", function() {twMessagerie("oMessagerie",this);}, false);}); function twMessagerie(oModalTemp,_this) { // Récupérer la fenêtre modale. var oObjet = document.getElementById(oModalTemp); // Vider le contenu. oObjet.querySelector(".modal-body").innerHTML = ""; // S’il y a un titre, ajouter le titre à la fenête modale. if (_this.hasAttribute("data-titre")) {oObjet.querySelector(".modal-title").innerHTML = _this.getAttribute("data-titre");} // S’il y a une ou plusieurs classes, ajouter les classes. // Noter que j’utilise la méthode « className » au lieu du nouveau « classList » incompatible IE 10... // Et pour éviter de tomber sur une fenêtre avec une vielle classe, // supprimer les classes en s’assurant de gardant « modal-dialog » bien sûr. if (_this.hasAttribute("data-class")) { oObjet.querySelector(".modal-dialog").className = "modal-dialog "+_this.getAttribute("data-class"); }else{ oObjet.querySelector(".modal-dialog").className = "modal-dialog" } // Si l’attribut « data-src » est présent avec une adresse // Ajouter le iFrame if (_this.hasAttribute("data-src")&&_this.getAttribute('data-src')!='') { // Avec iFrame, intégrer un iFrame et supprimer le « padding ». oObjet.querySelector(".modal-body").innerHTML = "<div class='cModalIframeConteneur'><iframe src='"+_this.getAttribute('data-src')+"'></iframe></div>"; oObjet.querySelector(".modal-body").className = "modal-body p-0"; } else { // Sinon intégrer le contenu et remettre le « padding ». oObjet.querySelector(".modal-body").innerHTML = _this.getAttribute('data-contenu'); oObjet.querySelector(".modal-body").className = "modal-body"; } } function twAjax(e,_this) { // empèche le lien de s’éxécuter e.preventDefault(); // Récupère le lien et le nom du DIV let sUrlTemp = _this.getAttribute("href"); let sDivTemp = _this.getAttribute("data-div"); // Appel Ajax var xhr = new XMLHttpRequest(); // Appelle du lien xhr.open("GET", sUrlTemp, true); xhr.send(); console.log("Requête envoyée!"); xhr.onreadystatechange = function() { if (this.readyState === 4) { if (this.status === 200){ console.log("Chargé!"); // Ajout du contenu au début du texte de la modale. var oObjet = document.getElementById(sDivTemp).querySelector(".modal-body"); oObjet.innerHTML = this.responseText + oObjet.innerHTML; } else { alert("Une erreur est survenue : " + this.status + " " + this.statusText); } } else { console.log("En processus!"); } }; } </script> </body> </html>