Popover mit erweiterter Funktionalität (Bootstrap 5)

In diesem Tutorial erstellen wir ein Bootstrap5-Popover, welches durch zwei verschiedene Javascript-Events ausgelöst wird. Beim Auslösen über das Hover-Event (Mouse-Over) soll das Popover nach 3 Sekunden automatisch geschlossen werden. Beim Click-Event soll das Popover so lange geöffnet bleiben, bis es manuell geschlossen wird.

Titelzeile und Inhalt des Popovers sollen aus HTML-Template-Elementen übernommen werden. Dadurch lassen sich serverseitig dynamische Inhalte und komplexere Darstellungen generieren, als es beispielsweise mit dem Standard-Mechanismus data-Attribut oder per JSON-Konfiguration möglich wäre. In der Titelzeile soll außerdem automatisch ein Schließen-Button erzeugt werden.

Als Ausgangsbasis dient die Bootstrap 5 Dokumentation für Popovers .

Beispiel-Screenshot:

Screenshot des geöffneten Popovers

Der zugehörige Quelltext befindet sich in den beiden Dateien improvedPopover.html und improvedPopover.js .

Initialisierung des Popovers im HTML

Das Popover wird mit einem Info-Button verknüpft. Dafür muss dieser Button, wie durch Bootstrap 5 vorgegeben, das Data-Attribut data-bs-toggle=popover besitzen. Der Info-Button enthält zusätzlich in den Data-Attributen data-ir-popover-title-template und data-ir-popover-body-template die IDs der HTML-Templates, die für die Generierung des Popovers verwendet werden.

<button id="btn_ir_popover_1234" type="button" class="btn"
        data-bs-toggle="popover" data-bs-placement="bottom"
        data-ir-popover-title-template="#tmpl_ir_popover_title_1234"
        data-ir-popover-body-template="#tmpl_ir_popover_body_1234">
        
  <i class="fa-solid fa-circle-info"></i>
</button>

<template id="tmpl_ir_popover_title_1234">
...        
</template>
<template id="tmpl_ir_popover_body_1234">
...        
</template>

JavaScript-Implementierung und Konfiguration

Zuerst selektieren wir alle Elemente, die eine ID haben, die mit btn_ir_popover_ anfängt. Diese Elemente sollen nach unserer Konvention ein Popover erhalten. Anschließend initialisieren wir die Popover für diese Elemente mit dem Trigger-Event Hover und setzt das Delay für das automatische Schließen (z.B. auf 3 Sekunden).

Der Inhalt des generierten Popovers wird um einen Schließen-Button ergänzt. Damit dies möglich ist, müssen wir den Sanitizer deaktivieren: sanitize: false. Dieser prüft normalerweise, dass nur sicherer HTML-Code für das Erstellen des Popovers verwendet wird. Durch das Ausschalten muss HTML-Code der aus nicht vertrauenswürdigen Quellen (z.B. Benutzereingaben, URL-Parametern) übernommen wird, gesondert auf unsicheren Code (Javascript, …) geprüft und eventuell bereinigt werden.

Der generierte Schließen-Button erhält ein Data-Attribut data-ir-popover-trigger, welches auf das Trigger-Element verweist. Die Titelzeile und der Inhalt des Popovers werden durch Javascript-Funktionen aus den HTML-Template-Elementen ausgelesen, deren IDs in den Data-Attributen data-ir-popover-title-template data-ir-popover-body-template, angegeben wurden.

Damit die CSS-Formatierung des Popovers nicht durch das HTML-Element beinflusst wird, in das es eingebettet wurde, wird ein Custom container konfiguriert. Mit container: 'body' wird hier das Popover explizit im <body>-Tag platziert.

document.querySelectorAll('[id^="btn_ir_popover_"]').forEach(function(popoverTriggerEl) {
  let popover = new bootstrap.Popover(popoverTriggerEl, {
    trigger: 'hover',
    delay: {
      "show": 0,
      "hide": 3000
    },
    html: true,
    title: function() {
      ...
    }
    content: function() {
      ...
    },
    container: 'body',
    sanitize: false
  });

Click-Event

Beim Öffnen des Popovers über das Click-Event soll das Popover geöffnet bleiben und nicht wie beim Hover-Event automatisch geschlossen werden. Dazu wird der Zustand des Popovers in einem Data-Attribut verwaltet. Bei jedem Auslösen des Click-Events wird geprüft, ob das Data-Attribut data-ir-popover-clicked am auslösenden HTML-Element vorhanden ist. Wenn ja, gehen wir davon aus, dass das Popover bereits angezeigt wird. Dann wird dieses Data-Attribut gelöscht und die CSS-Klasse active entfernt. Andernfalls wird am Trigger-Element das Data-Attribut data-ir-popover-clicked auf true gesetzt, die CSS-Klasse active hinzugefügt und das Popover durch die Methode getOrCreateInstance() initialisiert.

popoverTriggerEl.addEventListener('click', (event) => {
  const popover = bootstrap.Popover.getOrCreateInstance(event.target)
  if (popoverTriggerEl.dataset.irPopoverClicked == "true") {
    delete popoverTriggerEl.dataset.irPopoverClicked;
    popoverTriggerEl.classList.remove("active");
    popover.hide();
  } else {
    popoverTriggerEl.dataset.irPopoverClicked = "true";
    popoverTriggerEl.classList.add("active");
    popover.show();
  }
});

Damit das Popover nicht automatisch geschlossen wird, nutzen wir das Bootstrap Event hide.bs.popover. Wir prüfen darin, ob das auslösende Trigger-Element das Data-Attribut data-ir-popover-clicked besitzt und deshalb dass Popover geöffnet bleiben soll. Durch Aufruf von preventDefault() wird dann die Ausführung des Hide-Events unterbrochen.

popoverTriggerEl.addEventListener('hide.bs.popover', (event) => {
  if (popoverTriggerEl.dataset.irPopoverClicked == "true") {
    event.preventDefault();
  }
});

Aktivierung des Schließen-Button

Da der Schließen-Button dynamisch erzeugt wird, lässt er sich so nicht einfach in das Eventsystem des Browsers einbinden. Deshalb registrieren wir uns am globalen Click-Event und prüfen über die classList , ob es sich bei dem geklickten Element um einen Schließen-Button mit der Klasse btn-close handelt. Wenn ja ermitteln wir aus dessen Data-Attribut die ID des Trigger-Elementes, welches das Popup ursprünglich geöffnet hat. Mit dem Optional Chaining Operator (?.) wird sichergestellt, dass der Code fehlerfrei läuft und undefined zurückliefert, falls das Data-Attribut nicht vorhanden ist. Wenn eine ID ermittelt wurde, holen wir uns das Trigger-Element mit der Funktion document.getElementById() und löschen dessen Data-Attribut ir-popover-clicked sowie die CSS-Klasse active. Dann holen wir uns mit getOrCreateInstance() das zugehörige Popover-Objekt und schließen es mit hide().

  document.addEventListener('click', (eventClick) => {
    const targetEl = eventClick.target;
    const idTriggerEl = targetEl.dataset.irPopoverTrigger?.replace("#", "");
    if (targetEl.classList.contains("btn-close") && idTriggerEl) {
      const triggerEl = document.getElementById(idTriggerEl);
      delete triggerEl.dataset.irPopoverClicked;
      triggerEl.classList.remove("active");
      const popover = bootstrap.Popover.getOrCreateInstance(triggerEl);
      popover.hide();
    }
  });

Fazit

Damit ist da Popover vollständig konfiguriert und funktionsbereit. Ich hoffe, dass mit diesem Tutorial verschiedene Aspekte aus der Arbeit mit Bootstrap und Javascript vermittelt werden konnten und nun Viel Spaß beim Ausprobieren!.