Stand: 20.05.2019

Lernziele

In diesem Kapitel setzen wir JavaScript im Kontext von Webseiten ein. Man nennt das auch Frontend-Programmierung im Gegensatz zur Backend-Programmierung, wo es um die Programmierung eines Servers geht.

Wenn wir uns nochmal vor Augen führen, wie Webseiten mit Hilfe von HTTP abgerufen werden: ein Benutzer (Client) schickt eine Anfrage an einen Server und bekommt ein HTML-Dokument als Antwort. Javascript ist im HTML-Dokument eingebettet und wird auf der Seite des Clients - genauer gesagt vom Browser - ausgeführt. Deshalb also Frontend-Programmierung, da der Code im Frontend (beim User) ausgeführt wird.

Frontend und Backend

Dies steht im Gegensatz zur Backendprogrammierung (z.B. mit PHP), wo der Code bereits im Server ausgeführt ist. Bei der Backendprogrammierung sieht der User keinen Programmiercode, da dieser bereits vor dem Versenden ausgeführt und in der Regel durch Text/HTML ersetzt wurde.

Wie Sie wissen, kann man sich alle HTML-Elemente als Knoten in einem Baum vorstellen. Man nennt diesen Baum auch Document Object Model oder kurz DOM. Das DOM ist unser wichtigstes Tool, um mit Hilfe von JavaScript Elemente der Webseite zu manipulieren.

Denken Sie auch daran, dass Sie viele der folgenden Beispiele direkt im Browser ausprobieren können, indem Sie die JavaScript-Konsole aktivieren. In Chrome sieht das so aus:

JavaScript-Konsole in Chrome

(Löschen Sie die Historie von eventuellen Fehlermeldungen einfach mit CTRL-K).

9.1 JavaScript einbinden

Wie bekommen Sie Ihren Code auf die Webseite? Ähnlich wie bei CSS gibt es verschiedene Möglichkeiten.

9.1.1 In HTML einbetten

Sie können sowohl im head als auch im body jederzeit ein Element script einbauen, wo Sie JavaScript-Code einstellen.

Der folgende Code lässt ein Fenster aufgehen, das erst weggeklickt werden muss, bevor es weitergeht:

<script>
  alert('hello');
</script>

Hin und wieder sieht man das Attribut type:

<script type="text/javascript">
  alert('hello');
</script>

Dies ist aber spätestens seit HTML 5 nicht mehr zwingend notwendig.

Oft werden in diesem Teil nur Funktionen definiert, die dann jeweils über ein Event (siehe nächsten Abschnitt) aufgerufen werden.

<script>
  function foo() {
    alert('hello');
  }

  function bar() {
    alert('welt');
  }
</script>

9.1.2 Events und Event-Handling

Sie können jedes HTML-Element interaktiv machen, indem Sie auf bestimmte Events reagieren, z.B. auf das Anklicken eines Elements. Prinzipiell funktioniert das so, dass Sie ein Stück JavaScript-Code an das Element binden. Diesen Code nennt man auch Event-Handler oder Event-Listener.

Es gibt zwei Möglichkeiten, diesen Handler an das Element zu binden. Empfohlen ist die zweite Option.

In HTML

Sie können den Code direkt im HTML-Element platzieren. Das hat den Nachteil, dass JavaScript und HTML vermischt sind, kann aber dennoch zum Testen sinnvoll sein. Schauen wir uns ein Beispiel an:

<p onclick="console.log('CLICK')">
  Klick mich an!
</p>

Es gibt verschiedene Attribute für verschiedene Events. Drei der wichtigsten Events/Attribute sind:

Sie schreiben i.d.R. eine eigene JavaScript-Funktion, die Sie mit onclick aufrufen. Sie können dieser Funktion mit this das angeklickte Element als Parameter mitgeben. Dazu schreiben Sie

<p onclick="myHandler(this)">
  Klick mich an!
</p>

Sie müssen entsprechend eine Funktion haben:

<script>
  function myHandler(el) {
    alert('You clicked the element ' + el.tagName);
  }
</script>

Hier eine komplette Webseite, damit Sie sehen, wo das script steht und wo das Event ausgelöst wird.

<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="utf-8">
    <title>JavaScript: Event</title>
  </head>
  <body>
    <h1>JavaScript: Event</h1>

    <button onclick="myHandler(this)">
      Klick mich an!
    </button>

    <script>
      function myHandler(el) {
        alert('You clicked the element ' + el.tagName);
      }
    </script>
  </body>
</html>

In JavaScript

Im Allgemeinen ist es "sauberer", den Handler nicht direkt in das HTML-Element hineinzuschreiben, sondern in JavaScript das Element (oder die Elemente) herauszusuchen, an die ein Handler gebunden werden soll. Das hat den Vorteil, dass die Definition des Handlers und die Bindung an einem Ort (in JavaScript) sind.

Nehmen wir das Beispiel von oben, wo wir die Bindung in HTML vorgenommen haben:

<p onclick="myHandler(this)">
  Klick mich an!
</p>

Wir nehmen hier also den Handler raus:

<p>
  Klick mich an!
</p>

Stattdessen erledigen wir das in JavaScript: wir binden mit addEventListener den Handler (= JavaScript-Code) an das Element:

<script>
  document.getElementsByTagName('p')[0].addEventListener('click',
    function(event) {
      alert('You clicked the element ' + this.tagName);
    });
</script>

Sie sehen hier das Schlüsselwort 'click', welches für einen Mausklick steht. Zwei weitere interessante Events sind 'mouseenter' und 'mouseleave'. Eine ausführliche Liste von Events finden Sie bei w3schools.

Sie definieren den Handler als anonyme Funktion mit einem Parameter. Dieser Parameter bekommt bei jedem Aufruf ein MouseEvent-Objekt, wo z.B. hinterlegt ist, welcher Mausbutton gedrückt wurde oder bei welchen Koordinaten sich der Mauszeiger befand. Weitere Infos finden Sie in der MouseEvent API. Sie sehen hier auch, dass Sie mit this innerhalb der Funktion auf das HTML-Element zugreifen können, welches das Event ausgelöst hat.

Wenn Sie die Event-Infos nicht benötigen, können Sie den Parameter auch weglassen:

<script>
  document.getElementsByTagName('p')[0].addEventListener('click',
    function() {
      alert('You clicked the element ' + this.tagName);
    });
</script>

Im obigen Code haben wir schon vorgegriffen. Dort werden alle p-Elemente gesammelt, dann das erste genommen und daran die Funktion gehängt.

9.1.3 In eigener Datei (script)

Wie bei CSS ist es üblich, sämtlichen JavaScript-Code in eine separate Datei zu packen. Dazu legt man in der Regel ein neues Unterverzeichnis js an und erstellt dort eine Datei z.B. script.js.

Wenn Sie zwei HTML-Seiten haben, könnte Ihre Verzeichnisstruktur anschließend so aussehen:

css/style.css
js/script.js
index.html
kontakt.html

In der HTML-Datei müssen Sie auf Ihre JavaScript-Datei verweisen, dazu verwenden Sie das Element script und zwar am Ende des Body, d.h. direkt vor dem schließenden Tag </body>:

<script src="js/script.js"></script>

Grund ist, dass durch das Laden der JavaScript-Datei nicht das Laden der sichtbaren HTML-Elemente verzögert werden soll.

In der JavaScript-Datei können Sie Funktionen definieren oder auch direkt Befehle aufrufen.

Immediately-invoked Function Expression

Bei komplexerem Code kann es sinnvoll sein, den kompletten JavaScript-Code mit folgedem Ausdruck zu umgeben:

(function() {

  // eigener Code...

}());

Es handelt sich hierbei um eine anonyme Funktion, die direkt ausgeführt wird. In der Funktion können Sie ganz normal Code hineindefinieren, auch weitere Funktionsdefinitionen. Die Ummantelung sorgt dafür, dass Ihre Variablen außen nicht sichtbar sind und somit nicht mit anderem JavaScript-Code kollidieren (z.B. externe JavaScript-Libraries, die Sie importieren). Man nennt diese Konstruktion eine Immediately-invoked Function Expression oder kurz IIFE.

9.2 JavaScript und DOM

9.2.1 DOM: Document Object Model

Wie anfangs gesagt, ist das Document Object Model unser Werkzeug, um auf die Webseite zuzugreifen. Es handelt sich um eine Baumstruktur von JavaScript-Objekten, die Struktur und Inhalte des HTML-Dokuments wiedergeben.

Dokumentbaum und Knoten

Das eine Objekt, das diese gesamte Struktur enthält, heißt document. Hier ein Beispiel, wie ein solches Objekt aussehen könnte:

Beispiel für einen DOM-Baum

Ein Baum besteht aus Knoten (engl. nodes), die hier als Boxen dargestellt sind.

Es gibt genau einen Wurzelknoten (engl. root). Alle Knoten außer der Wurzel haben genau einen Elternknoten (engl. parent). Jeder Knoten kann einen Kindknoten (engl. child) oder mehrere Kindknoten (engl. children) oder auch kein Kinder haben.

In der Abbildung sehen wir die folgenden Arten von Knoten:

Es ist wichtig, zwischen Knoten und Elementen zu unterscheiden. Knoten können HTML-Elemente, Text, Attribute oder das ganze Dokument sein. Wir sprechen aber nur von einem Element, wenn es sich um ein HTML-Element handelt.

Das folgende Stück HTML

<p id='foo'>Hallo</p>

resultiert in drei Knoten (HTML-Element, Attribut, Text), aber nur einem Element.

In JavaScript unterscheidet man drei Typen von Objekten:

Node, Element, HTMLElement

Davon relevant sind für uns nur Node und HTMLElement. Eigenschaften und Methoden werden vererbt. Sie können diese jeweils hier nachschlagen:

(Referenzseiten von Mozilla.)

Elemente

Die Elemente (blau) entsprechen jeweils einem HTML-Element. Sie können über das Objekt document auf alle im Dokument vorhandenen Elemente zugreifen.

Zu beachten ist, dass document selbst kein HTML-Element ist. Wenn Sie auf das HTML-Element <html> zugreifen wollen, nutzen Sie

document.documentElement

Sie haben außerdem Zugriff auf Head und Body mit:

document.body
document.head


Um auf den HTML-Code eines DOM-Elements (inklusive aller enthaltenen Elemente!) als String zuzugreifen, verwendet man innerHTML:

element.innerHTML


Es ist auch möglich, mit innerText nur den reinen Text (wie oben, aber ohne HTML-Markup) zu erhalten. Auch hier wird der Text aller enthaltenen Elemente inkludiert:

element.innerText


Weitere nützliche Eigenschaften von Elementen sind:

Eine vollständige Übersicht über die Eigenschaften finden Sie bei w3schools.

Jedes Element hat auch Methoden, um innerhalb des Objekts zu suchen, z.B. nach Klassen.

9.2.2 Selektion

Ähnlich wie in CSS möchte man auf bestimmte Elemente der Webseite zugreifen, um auszulesen oder zu ändern. Wie wissen, dass man den Elementtyp, Klassen oder IDs verwenden kann, um auf Elemente zuzugreifen. Darüber hinaus kann man über Beziehungen zwischen Knoten (Eltern-Kind, Geschwister) im Baum "navigieren".

Rückgabe: Element oder HTMLCollection

Die Selektion funktioniert über Methoden, die man auf einem Knoten aufruft. Diese Methoden geben entweder ein einzelnes Element zurück oder eine Liste von Elementen, wir nennen sie HTMLCollection (siehe HTMLCollection bei Mozilla).

Eine HTMLCollection funktioniert ähnlich wie ein Array, ist aber kein echter JavaScript-Array. Der Zugriff auf einzelne Elemente erfolgt zwar auch über eckige Klammern und Index, aber es gibt z.B. die Methode forEach nicht für die HTMLCollection. Stattdessen kann man eine for-of-Schleife verwenden, wie wir gleich noch sehen werden.

Elementselektor

Wenn wir nach einem HTML-Elementtyp suchen wie p oder h1 oder em, verwenden wir den Elementselektor.

Mit der Methode getElementsByTagName() können Sie z.B. alle Elemente vom Typ p im Dokument auswählen:

document.getElementsByTagName('p')


Wir erhalten eine HTMLCollection. Sie bekommen den ersten Absatz daher mit

document.getElementsByTagName('p')
[0]

Da der erste Absatz wieder ein Element ist, können Sie dort wieder nach Elementen suchen:

document.getElementsByTagName('p')
[0].getElementsByTagName('strong')


Wollen Sie alle Elemente durchlaufen, verwenden Sie eine for-of-Schleife. Sie können forEach leider nicht nutzen, weil eine HTMLCollection eben kein echter JavaScript-Array ist.

Zum Beispiel wollen wir alle Absätze zählen:

let list = document.getElementsByTagName('p')
;
let count = 0;

for (el of list)
  count++;
}

console.log(count);

Sie können natürlich auch eine traditionelle For-Schleife mit einem Laufparameter i nehmen.

Klassenselektor

Mit der Methode getElementsByClassName() können Sie z.B. alle Elemente mit Klasse "foo" auswählen:

document.getElementsByClassName('foo')


Wir erhalten auch hier eine HTMLCollection als Rückgabe.

ID-Selektor

Mit der Methode getElementById() können Sie z.B. das Element mit ID "textblock" auswählen:

document.getElementById('special')


Hier bekommen wir ein einzelnes Element zurück, falls ein solches existiert. Das heißt, wir können dieses Element direkt manipulieren:

document.getElementById('special')
.style.backgroundColor = 'yellow';

Wenn ein entsprechendes Element nicht existiert, wird null zurückgegeben.

Selektion per CSS und NodeList

Die Methode querySelectorAll() erlaubt Ihnen, CSS-Selektoren zu verwenden, die Sie von den CSS-Regeln her kennen. Zum Beispiel alle Elemente ul innerhalb von nav:

document.querySelectorAll('nav ul')

Hier erhalten wir eine NodeList zurück (siehe NodeList bei Mozilla).

Eine NodeList ist etwas anderes als eine HTMLCollection. Auch die NodeList funktioniert ähnlich wie ein Array, aber im Gegensatz zur HTMLCollection gibt es die Methode forEach, das heißt, man kann sich etwas kürzer fassen:

document.querySelectorAll('nav ul').forEach(function(el) {
  console.log(el)
});

Oder mit Arrow-Notation:

document.querySelectorAll('nav ul').forEach(el => { console.log(el) });

Beachten Sie, dass Sie hier nicht this verwenden können.

Mit querySelector() bekommen wir das erste Element zurück, das "passt":

document.querySelector('a')

Hier können wir also direkt das Element manipulieren, z.B.

document.querySelector('a').style.backgroundColor = 'yellow';

9.2.3 Elementlisten durchlaufen

Wir haben leider die etwas verwirrende Situation, dass es zwei Arten von Elementlisten gibt:

  1. HTMLCollection: Rückgabe von getElementsByTagName oder getElementsByClassName
  2. NodeList: Rückgabe von querySelectorAll

Die HTMLCollection muss man mit einer For-Schleife durchlaufen, es gibt keinen forEach-Mechanismus.

let list = document.getElementsByTagName('p')
;
for (let i = 0; i < list.length; i++) {
  console.log(list[i]);
}

Für die NodeList gibt es das forEach. Zu beachten ist aber, dass innerhalb des forEach nicht mit this, sondern über einen Parameter auf das aktuelle Element zugegriffen werden muss.

document.querySelectorAll('p').forEach(function(el) {
  console.log(el);
});

Ich persönlich empfehle eher die zweite Methode, da hier das funktionale Programmieren zum Einsatz kommt, was der allgemeinen Philosophie von JavaScript entspricht.

9.2.4 Elemente ändern

Inhalte

Um den Inhalt eines Elements zu ändern, können Sie die Eigenschaft innerText wie eine Variable benutzen, der Sie einen neuen Inhalt zuweisen:

document.getElementsByTagName('p')
[1].innerText = "Doktor Foo was here."

Falls Sie auch HTML einbetten wollen, verwenden Sie innerHTML:

document.getElementsByTagName('p')[1].innerHTML = "Doktor <strong>foo</strong> was <a href='http://hs-augsburg.de'>here</a>."

ID ändern

Oft möchten Sie ID oder Klasse eines Elements ändern, um ein anderes Stückchen CSS auf das Element anzuwenden. Wir nehmen mal das Element body als Beispiel:

document.body

IDs funktionieren wie Eigenschaften - Sie können sie einfach setzen. Hier wird der ID des Body-Elements auf "special" gesetzt (egal, ob oder wie der ID vorher gesetzt war):

document.body.id = "special";

Wollen Sie einen ID entfernen, müssen Sie removeAttribute() anwenden:

document.body.removeAttribute('id');

Klassen ändern

Die Klassen eines Elements können über setAttribute() gesetzt werden:

element.setAttribute('class', 'foo');

Mehrere Klassen können natürlich mit Leerzeichen angegeben werden:

element.setAttribute('class', 'foo bar doo');

Will man eine neue Klasse zu den bisherigen Klassen hinzufügen, kann man die Liste aller Klassen eines Elements beziehen

element.classList

und dort eine neue Klasse mit add hinzufügen:

element.classList.add('bar');

Sie entfernen eine Klasse mit remove:

element.classList.remove('foo');

Attribute ändern

Sie können mit setAttribute() beliebige Attribute eines Elements setzen, z.B. einen Handler für das erste p-Element einbauen:

document.getElementsByTagName('p')[1].setAttribute('onclick', 'alert("hello")');

Mit hasAttribute() können Sie prüfen, ob ein Attribut existiert. Alternativ können Sie getAttribute() verwenden und prüfen, ob Sie null zurückbekommen.

Wenn ein Attribut wie href bereits gesetzt ist, können Sie den Attributnamen für den Zugriff benutzen:

document.getElementById('speciallink').href = 'index.html';

Allgemein folgt dies dem Schema

element.attributname

Styling/CSS ändern

Um das Styling (also das CSS) eines Elements zu ändern, verwendet man die Eigenschaft style.

Allgemein:

element.style.eigenschaft = 'wert';

Ein Beispiel:

document.body.style.backgroundColor = 'yellow';

Dabei ist zu beachten, dass CSS-Eigenschaften mit Bindestrich wie background-color in CamelBack-Schreibweise geändert werden müssen, also im Beispiel: backgroundColor.

Sie können auch in der DOM Style Reference alle Eigenschaften nachschlagen, wenn Sie unsicher sind, wie sie geschrieben werden.

Außerdem müssen alle nicht-numerischen Werte in Anführungszeichen gesetzt sein:

document.getElementById('foo').style.margin = '20px';
document.getElementById('bar').style.margin = 0;

Zu beachten ist, dass die beschriebene Methode äquivalent dazu ist, das Style-Attribut direkt in einem HTML-Element anzulegen. Die obigen zwei Befehle wirken sich also wie folgt auf die HTML-Elemente aus:

<p id='foo' style='margin:20px'> ... </p>
<p id='bar' style='margin:0'> ... </p>

Wenn Sie das Style-Attribut verwenden, um den aktuellen Style auszulesen, hilft Ihnen das selten weiter.

Styling/CSS auslesen

Um das aktuelle Styling auszulesen, empfiehlt sich die Funktion getComputedStyle, da Sie mit der obigen Methode nur einen im Element definierten Style zurückbekommen. Diese Funktion stellt das Objekt window zur Verfügung (siehe Abschnitt "JavaScript und Browser").

Sie bekommen alle Stilinformationen des Body z.B. mit

getComputedStyle(document.body)

Möchten Sie speziell die Hintergrundfarbe, schreiben Sie

getComputedStyle(document.body).backgroundColor

Das kommt z.B. häufiger vor, dass Sie aufgrund der Sichtbarkeit eines Elements Entscheidungen treffen:

if (getComputedStyle(element).display == 'none') { ... }

Löschen

Man kann auch mit der Funktion remove ein Element löschen:

document.getElementsByTagName('p')[1].remove();

Das Element wird aus dem DOM-Baum entfernt.

9.2.5 Im Baum navigieren

Häufig möchten Sie von einem Element auf das nächste Geschwisterelement oder auf den Elternknoten springen. Dazu gibt es Eigenschaften. Wir müssen hier immer zwischen Elementen (= HTML-Elemente) und Nodes (können auch Text- oder Attributknoten sein) unterscheiden. Das spiegelt sich in den zwei Listentypen HTMLCollection (nur Elemente) und NodeList (Knoten aller Art) wieder.

Mit parentNode oder parentElement greifen Sie auf den Elternknoten als HTML-Element zu:

element.parentNode
element.parentElement

Mit children auf alle enthaltenen Kindelemente zu:

element.children

Sie bekommen also eine HTMLCollection. Wenn Sie stattdessen childNodes verwenden würden, bekämen Sie eine NodeList, die auch alle Text- und Attributknoten enthielte.

Mit nextElementSibling bekommen Sie das nächst folgende Geschwisterelement:

element.nextElementSibling

Auch hier ist die Unterscheidung zwischen Element und Knoten wichtig. Mit nextSibling bekommen Sie evtl. einen Textknoten, mit nextElementSibling bekommen Sie das nächste HTML-Element (was i.d.R. auch gewünscht ist).

Wenn Sie alle Geschwisterelemente bekommen möchten, können Sie einfach über das Elternelement gehen:

element.parentElement.children

Auch hier haben Sie wieder eine HTMLCollection.

9.3 JavaScript und Browser

Der Browser stellt einige Informationen bereit, die Sie über JavaScript-Objekte beziehen können. Das essentielle Objekt heißt window (siehe auch Window bei Mozilla).

Sie können Höhe und Breite des Browsers (ohne Scrollbars und Toolbars) bekommen:

window.innerHeight
window.innerWidth

Das Objekt enthält das Objekt screen, das die Eigenschaften des Bildschirms (nicht nur des Browserfensters) enthält. Hier einige interessante Eigenschaften:

screen.width
screen.height
screen.availWidth
screen.availHeight
screen.colorDepth
screen.pixelDepth

Das window-Objekt enthält noch die aktuelle URL in

window.location

Dies Objekt ist eine assoziative Liste, so dass Sie auf einzelne Aspekte so zugreifen können:

window.location.href
window.location.hostname
...

Mit assign() können Sie den Browser sogar auf eine andere Webseite lenken:

window.location.assign('http://hs-augsburg.de');

In diesem Objekt können Sie auf die Browse-Historie zugreifen:

window.history

Auch hier können Sie den Browser fernsteuern:

history.back()
history.forward()

Methoden

Das window-Objekt stellt einige interessante Methoden bereits. Eine kennen Sie schon:

window.alert('hallo');

Diese Methode zeigt einen modalen Dialog. Sie können das Objekt auch weglassen:

alert('hallo');

Zwei weitere Methoden sind interessant:

setTimeout()
setInterval()

Beide bekommen eine Funktion und eine Zeitangabe (Dauer in ms). Bei setTimeout wird die Funktion nach Ablauf der Zeit ausgeführt. Bei setInterval wird die Funktion immer wieder nach Ablauf dieser Zeit ausgeführt.

Zum Beispiel:

setTimeout(() => { console.log('buh!')}, 2000);

Auch bei diesen Funktionen müssen Sie nicht window for die Funktion schreiben.

9.4 Praktische Beispiele

9.4.1 Zähler

Ein kleines Beispiel für Interaktivität ist ein Zähler, der bei Knopfdruck hochgezählt wird. Das Ergebnis wird direkt auf der Webseite angezeigt:

Demo: Zähler →

Layout

Im HTML-Code erzeugen wir einen Button und ein Feld, in das wir den Zählwert eintragen können. Wir brauchen auch IDs, um die Elemente eindeutig anzusprechen:

<button id="counterbutton">Zähler</button>
<span id="counter">0</span>

Wir schreiben hier erstmal eine 0, aber wir benötigen natürlich eine Variable für diesen Wert.

Programmierung

Den JavaScript-Teil betten wir direkt in die HTML-Datei am Ende des <body> ein:

<body>
  ...
  <script>
    ...
  </script>
</body>

Im JavaScript-Code führen wir eine globale Variable als Zähler ein:

var ex_counter = 0;

Das Hochzählen ist einfach:

ex_counter++;

Um das Ergebnis in das span-Element zu schreiben, benutzen wir innerText:

document.getElementById('counter').innerText = ex_counter;

Jetzt müssen wir diese zwei Codezeilen als Funktion an den Button binden. Den Button bekommen wir so:

document.querySelector('#counterbutton')

Man könnte auch getElementById benutzen (wie wir es bei dem span-Element tun). Jetzt hängen wir unseren Code als Handler für Click-Events an den Button:

document.querySelector('#counterbutton').addEventListener('click', function() {
    ex_counter++;
    document.getElementById('counter').innerText = ex_counter;
});

Beachten Sie, dass der Zähler beim Neuladen der Seite auf Null gesetzt wird.

9.4.2 Modales Dialogfenster

Ein modales Fenster ist ein Fenster, das den restlichen Inhalt überdeckt und erst weggeklickt werden muss, damit der User weiter mit der Webseite interagieren kann. Das kann man visuell unterstützen, indem man die Seite mit einer halbtransparenten Fläche überlagert:

Modales Dialogfenster

Layout

Wir platzieren z.B. oberhalb unserer normalen Webseite ein div, das später die halbtransparente Fläche aufnimmt:

<div id="modal-cover">
  ...
</div>

Um diese Überdeckung zu erreichen, fixieren wir das div, drehen den z-index hoch und stellen mit top, left, width und height sicher, dass keine Ränder bleiben.

#modal-cover {
  position: fixed;
  z-index: 1;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, .3);
  ...
}

Innerhalb der Fläche definieren wir das modale Fenster und platzieren dort auch das Multiplikationskreuz als Close-Button:

<div id="modal-cover">
  <div id="modal-window">
    <span id="modal-close">&times;</span>
    Dies ist ein modales Dialogfenster.
  </div>
</div>

Den Close-Button positionieren und stylen wir wie folgt:

#modal-close {
  position: absolute;
  top: 0;
  right: 10px;
  color: #aaa;
  float: right;
  font-size: 35px;
  font-weight: bold;
  cursor: pointer;
}

Jetzt stylen wir noch das eigentliche Fenster. Das position:relative benötigen wir, damit der Close-Button mit position:absolute innerhalb des Dialogfensters positioniert wird.

#modal-window {
  position: relative;
  font-size: 40px;
  background-color: white;
  border-radius: 8px;
  padding: 60px 60px 60px 40px;
}

Damit das Fenster später zentiert erscheint, müssen wir das Styling des umgebenden div noch ergänzen:

#modal-cover {
  ...
  display: none; /* später auf flex umstellen */
  justify-content: center;
  align-items: center;
}

Durch die letzten zwei Zeilen erreichen wir das Zentrieren. Zunächst setzen wir das ganze zwar auf display:none, aber im JavaScript setzen wir die Box auf display:flex, sobald das Dialogfenster angezeigt werden soll.

Programmierung

Die Programmierung ist sehr simpel. Es geht lediglich darum, das div mit dem Modalfenster sichtbar zu schalten. Dazu legen wir das div in einer Variablen ab:

var modal = document.getElementById('modal-cover');

Anschließend definieren wir eine Funktion für das Einschalten. Wir wählen hier display:flex statt display:block, weil wir die Flexbox zum Zentrieren des Modalfensters nehmen:

function show_modal() {
  modal.style.display = 'flex';
}

Jetzt schreiben wir noch einen Handler für das Schließen und binden ihn an den Close-Button:

document.getElementById('modal-close').addEventListener('click',
function() {
  modal.style.display = 'none';
});

Das Modalfenster aktivieren wir nach einem Timeout von einer Sekunde:

setTimeout(show_modal, 1000);

Hier zum Anschauen und Runterladen:

Modales Dialogfenster →

9.4.3 Akkordeon

Als "Akkordeon" (engl. accordion) bezeichnet man ein Interface, das von mehreren Textabschnitten nur die Titel anzeigt und die Inhalte erst bei Klick anzeigt. Diese Methode sorgt für Übersichtlichkeit.

Das heißt, bei einem Click auf eine Überschrift wird jeweils der darunter liegende Abschnitt angezeigt. Die anderen Abschnitte werden versteckt.

Beispiel Akkordeon

Wenn Sie z.B. auf Vorlesung 2 klicken, sehen Sie:

Beispiel Akkordeon aufgeklappt

Layout

Das Layout in HTML sieht wie folgt aus:

Akkordeon-Layout

Man umgibt das gesamte Akkordeon mit einem <div> mit Klasse "accordion". Jeder aufklappbare Abschnitt besteht aus einer Überschrift h2 und einem weiteren <div>, dem wir die Klasse "section" geben.

<h1>Meine Vorlesungen</h1>
<div class="accordion">
  <h2>Vorlesung 1</h2>
  <div>
    <p>
      Lorem ipsum ...
    </p>
    <p>
      Lorem ipsum ...
    </p>
  </div>

  <h2>Vorlesung 2</h2>
  <div>
    <p>
      Lorem ipsum ...
    </p>
    <p>
      Lorem ipsum ...
    </p>
  </div>

  ...
</div>

Programmierung

Wir schreiben unsere JavaScript-Code wieder direkt in die HTML-Datei am Ende des <body>:

<body>
  ...
  <script>
    ...
  </script>
</body>

Als erstes schreiben wir uns eine Funktion, die alle Abschnitte einklappt. Dazu verwenden wir querySelectorAll, um die Abschnitte einzusammeln. Dank des querySelectorAll können wir forEach verwenden, um das Styling aller Abschnitte auf display:none zu setzen.

function collapseAll() {
  document.querySelectorAll('.accordion>div')
    .forEach(function(el) {
      el.style.display = 'none';
    });
}

Als nächstes hängen wir an alle h2-Elemente einen Handler, der auf einen Klick reagiert.

document.querySelectorAll('.accordion>h2')
  .forEach(function(x) {
    x.addEventListener('click', function() {
      ...
    });
});

Bei einem Klick sollen

  1. alle Abschnitte mit unserer Funktion collapseAll eingeklappt werden und dann
  2. der Abschnitt, der zur angeklickten Überschrift gehört, aufgeklappt werden.

Wir bekommen den Abschnitt, der zur angeklickten Überschrift gehört, mit nextElementSibling, da er der nächste Geschwisterknoten hinter der h2-Überschrift ist.

document.querySelectorAll('.accordion>h2')
  .forEach(function(x) {
    x.addEventListener('click', function() {
      collapseAll();
      this.nextElementSibling.style.display = 'block';
    });
});

Kompletter Code

Hier nochmal der komplette JavaScript-Code. Er erlaubt zusätzlich das Einklappen aller Abschnitte, wenn man auf die Gesamtüberschrift klickt.

<script>
  // Klappt alles ein
  function collapseAll() {
    document.querySelectorAll('.accordion>div')
      .forEach(function(el) {
        el.style.display = 'none';
      });
  }

  // Klappt alles ein bei Klick auf Gesamtüberschrift
  document.querySelector('h1').addEventListener('click',
    function() {
      collapseAll();
    });

  // Klappt angeklickten Abschnitt auf (alles andere zu)
  document.querySelectorAll('.accordion>h2')
    .forEach(function(x) {
      x.addEventListener('click', function() {
        collapseAll();
        this.nextElementSibling.style.display = 'block';
      });
  });
</script>

Hier finden Sie das Beispiel in einer HTML-Datei zum Betrachten und Runterladen:

Akkordeon →

Variante

Ein Akkordeon funktioniert nicht immer gleich. Eine Variante ist zum Beispiel

  1. Beim Öffnen eines Abschnitts bleiben andere offene Abschnitte offen
  2. Beim Anklicken eines geöffneten Abschnitts wird dieser geschlossen

Wir benötigen zwei Änderungen. Die erste ist leicht: wir müssen lediglich das collapseAll entfernen (hier auskommentiert):

document.querySelectorAll('.accordion>h2')
  .forEach(function(x) {
    x.addEventListener('click', function() {
      //collapseAll();
      this.nextElementSibling.style.display = 'block';
    });
});

Die zweite Änderung ist, dass ein geöffnetes Element geschlossen wird. Dazu müssen wir also wissen, wie die Display-Eigenschaft gesetzt ist ('block' oder 'none'). Dazu verwenden wir getComputedStyle:

let div = this.nextElementSibling;
getComputedStyle(div).display

Das packen wir in ein If:

let div = this.nextElementSibling;
if (getComputedStyle(div).display == 'none') {
  div.style.display = 'block';
} else {
  div.style.display = 'none';
}

Insgesamt sieht der Handler so aus:

document.querySelectorAll('.accordion>h2').forEach(function(x) {
  x.addEventListener('click', function() {
    let div = this.nextElementSibling;
    if (getComputedStyle(div).display == 'none') {
      div.style.display = 'block';
    } else {
      div.style.display = 'none';
    }
  });
});

Auch hier der Code zu Ansehen und Runterladen:

Akkordeon 2 →

9.4.4 Panel mit Reitern

Eine weitere praktische Anwendung ist eine Anzeige (= Panel), die unterschiedliche Inhalte darstellt, je nachdem, welcher Reiter (auch Tab genannt) angewählt wurde.

Der Startbildschirm könnte so aussehen:

Panel mit Reitern, Startbild

Beim Anklicken des vierten Reiters "Turm", wird der Reiter auf aktiv geschaltet und der Inhalt umgestellt:

Panel mit Reitern, anderer Reiter

Layout

Unser Layout sieht ganz grob so aus: Ein großer div-Container, der die Reiter als ul-Liste enthält und im Anschluss alle Inhalte mit jeweils eigenem div.

Reiter-Layout

Wir fassen das gesamte Element in ein div mit Klasse "reiter":

<div class="reiter">
  ...
</div>

Innerhalb dieses Elements haben wir zunächst die Reiter als einfache Liste mit Links. Beachten Sie, dass wir das Link-Attribute href hier für einen anderen Zweck als üblich verwenden. Dazu später. Außerdem geben wir dem jeweils aktiven Reiter die Klasse "active":

<ul>
  <li class="active"><a href="eins">Pusteblume</a></li>
  <li><a href="zwei">Hase</a></li>
  <li><a href="drei">Dinosaurier</a></li>
  <li><a href="vier">Turm</a></li>
</ul>

Die vier Inhalte packen wir jeweils in ein div, jeweils mit eindeutigem ID. Hier sei nur ein Beispiel gezeigt:

<div id="eins">
  <h1>Pusteblume</h1>
  <p>
    Lorem ipsum ...
  </p>
</div>

Sie sehen vielleicht schon, dass der ID mit dem dazugehörigen href in der Reiterliste übereinstimmt. Wir stellen später über JavaScript die Verbindung her.

Wir gehen hier nicht weiter auf das Styling ein. Schauen Sie sich den Quelltext im Beispiel an, um zu sehen, wie hier gestylt wurde.

Panel mit Reitern →

Programmierung

Für die Programmierung benötigen wir folgende Schritte:

  1. An jeden Reiter einen Handler binden
  2. Im Handler
    • den angeklickten Reiter auf aktiv schalten
    • den dazugehörigen Content anzeigen (alle anderen verstecken)
  3. Content vom ersten Reiter anzeigen

Zunächst müssen wir Handler an die Reiter binden. Wir binden die Handler an die li-Elemente, damit die komplette Box als Link funktioniert. Wenn wir nur das a ansprechen, gibt es Bereiche innerhalb der Box, die nicht klickbar sind.

document.querySelectorAll('.reiter li').forEach(...);

In dem forEach wird jedes Listenelement (= Reiter) durchlaufen. Wir hängen an jedes Element einen Click-Handler:

document.querySelectorAll('.reiter li').forEach(el => {
  el.addEventListener('click',
    event => {
      ...
    });
  })
});

Im Handler müssen wir zunächst verhindern, dass eine normale Link-Aktion ausgeführt wird. Das machen wir mit

event.preventDefault();

Anschließend entfernen wir aus allen Reitern die Klasse "active" und fügen Sie nur dem aktuell angeklickten Element hinzu:

for (let ch of el.parentNode.children) {
  ch.classList.remove('active');
}
el.classList.add('active');

Zu guter letzt beziehen wir das Attribut href aus dem Link (erstes Kind des aktuellen Listenelements).

let ref = el.firstElementChild.getAttribute('href');

Dies ist genau gleich dem ID des div-Elements, das den Inhalt enthält. Also holen wir uns das:

let div = document.getElementById(ref);

Jetzt durchlaufen wir alle div-Einheiten im Reiter und schalten diese auf unsichtbar, mit Ausnahme des ausgewählten div, das wir uns ja gemerkt haben:

let inhalte = document.querySelectorAll('.reiter div')
  .forEach(function(el) {
    if (el !== div) {
      el.style.display = 'none';
    } else {
      el.style.display = 'block';
    }
  });

Der gesamte Code sieht so aus:

document.querySelectorAll('.reiter li')
  .forEach(el => {
    el.addEventListener('click', event => {
      // verhindern, dass Link verfolgt wird
      event.preventDefault();

      // Markierung aktiver Reiter
      for (let ch of el.parentNode.children) {
        ch.classList.remove('active');
      }
      el.classList.add('active');

      // Reiterinhalt zeigen
      let ref = el.firstElementChild.getAttribute('href');
      let div = document.getElementById(ref);
      let inhalte = document.querySelectorAll('.reiter div')
      .forEach(function(el) {
        if (el !== div) {
          el.style.display = 'none';
        } else {
          el.style.display = 'block';
        }
    });
  })
});

Und hier nochmal zum ausprobieren und anschauen:

Panel mit Reitern →

9.4.5 Formular validieren

Bei Formularen möchten Sie häufig eine erste Korrektheits-Kontrolle durchführen, noch bevor Sie die Daten zum Server schicken. Zum Beispiel wollen Sie sicherstellen, dass das Namensfeld nicht leer ist oder bei einer e-Mail-Adresse checken, ob die grobe Struktur stimmt.

Layout

Nehmen wir uns ein einfaches Beispielformular:

<form method="get" action="fertig.html">
  <label>Name:</label>
  <input type="text" name="username"/>
  <br/>
  <input type="submit" value="Abschicken"/>
</form>

<p id="feedback"></p>

Wir haben hier schon ein Feld für mögliche Rückmeldungen eingerichtet.

Programmierung

Wir schreiben jetzt eine JavaScript-Funktion, die das Namensfeld prüft. Dazu verwenden wir document.forms und nehmen dort das erste Element (wir gehen davon aus, dass es das einzige Formular auf der Seite ist). Sie sehen, dass das Formular wiederum als assoziativer Array gespeichert ist und Sie mit "username" auf das Textfeld zugreifen können.

Ist das Namesfeld leer, wir eine Meldung in das bislang leere p-Element geschrieben und es wird der Wert false zurückgegeben.

<script type="text/javascript">
  function validate() {
    let name = document.forms[0]['username'].value;
    let feedback = document.getElementById('feedback');
    if (name == "") {
      feedback.innerText = "Geben Sie bitte Ihren Namen ein.";
      return false;
    } else {
      feedback.innerText = "";
    }
  }
</script>

Es fehlt noch der Aufruf der Funktion. Diesen positionieren wir in form im Attribut onsubmit. Mit dem return können wir steuern, ob das Formular abgeschickt wird. Hier wird bei der Rückgabe von false das Abschicken verhindert.

<form method="get" onsubmit="return validate()" action="js_fertig.html">
  ...
</form>

Alternative

In diesem speziellen Beispiel gibt es auch eine einfachere Lösung: Sie können dem Browser mitteilen, dass das Eingabefeld notwendig ist. Das machen Sie mit dem Attribut required:

<input type="text" name="username" required/>

Dann können Sie sich den JavaScript-Code sparen. Der Browser verhindert jetzt das Abschicken, wenn das Feld leer bleibt und informiert den User.

9.5 Übungen

9.5.1 Erste Schritte

(E1) JavaScript-Datei einbinden

Schreiben Sie eine HTML-Datei und legen Sie eine JavaScript-Datei an. Verknüpfen Sie die beiden und rufen Sie in der JavaScript-Datei die folgende Zeile auf:

console.log("JavaScript is running...");

Schauen Sie sich diese Datei an, wenn Sie Probleme haben:

JS-Demo →

Sie können auch diesen Befehl ausprobieren:

alert("Hello, world!");

(E2) Datum anzeigen

Erstellen Sie eine HTML-Datei "heute.html" mit folgendem leeren Element:

<p id="message"></p>

Fügen Sie unterhalb davon (aber innerhalb von body) ein JavaScript-Element ein:

<script>
  let d = new Date();
</script>

Zurzeit wird dort ein neues Objekt angelegt, welches das aktuelle Datum enthält. Sehen Sie sich die Funktionweise von Date bei JavaScript Date Reference an.

Ergänzen Sie den Code so, dass beim Laden der Seite das aktuelle Datum angezeigt wird:

Webseite mit Datum

Probieren Sie auch, die Uhrzeit mit darzustellen:

Webseite mit Datum und Uhrzeit

Beziehen Sie das p-Element mit document.getElementById() und befüllen Sie es mit innerText.

9.5.2 Manipulation des DOM

Laden Sie sich die Datei js_headlines.html herunter. Sie sehen Überschriften und Texte:

Seite mit Überschriften

Fügen Sie einen JavaScript-Bereich am Ende des <body> ein und versuchen Sie die folgenden Aufgaben zu lösen.

(M1) Alle Absätze einfärben

Färben Sie alle <p>-Elemente mit Hilfe einer For-Schleife ein.

Absätze eingefärbt

Nutzen Sie

document.getElementsByTagName('p')

oder

document.querySelectorAll('p')

um die Elemente einzusammeln.

Anschließend setzen Sie die Eigenschaft style.backgroundColor auf die gewünschte Farbe.

(M2) Absätze interaktiv einfärben

Färben Sie jetzt nur den Absatz ein, den die Maus berührt (Rollover-Effekt). Natürlich könnten Sie das auch ganz einfach im CSS mit :hover erreichen, aber wir wollen es hier mit JavaScript probieren.

Durchlaufen Sie alle p-Elemente und benutzen Sie addEventListener, um bei dem Event 'mouseenter' den Hintergrund auf gelb zu setzen.

Außerdem müssen Sie beim Verlassen (Event 'mouseleave') den Hintergrund wieder auf weiß setzen, sonst verbleiben alle berührten Absätze mit gelben Hintergrund.

(M3) Tabelle einfärben

Laden Sie sich die Datei js_table.html herunter. Dort sehen Sie eine Tabelle:

Tabelle

Ihre Aufgabe ist es, jede zweite Zeile mit der Farbe 'cyan' zu unterlegen, um die Tabelle lesbarer zu machen. Auch hier würde man normalerweise CSS verwenden, aber es ist eine gute Übung.

Tabelle mit Zebramuster

Tipp: Sie können - wie in Java - den Modulo-Operator % verwenden, um zwischen geraden und ungeraden Zahlen zu unterscheiden.

(M4) Tabelle interaktiv

Nehmen Sie die gleiche Tabelle aus der vorigen Aufgabe und machen Sie die Zeilenmarkierung interaktiv, d.h. dort, wo die Maus ist, soll die komplette Zeile mit 'cyan' hinterlegt werden.

Tipp: Nutzen Sie wieder die Events 'mouseenter' und 'mouseleave'.

(M5) Überschriften nummerieren

Schreiben Sie JavaScript-Code, der alle h2-Überschriften mit einer Nummerierung versieht:

Seite mit nummerierten Überschriften

Schauen Sie, ob Sie mehrere Überschriften-Elemente aufsammeln können. Bei der Beispielseite geht das mit

document.getElementsByTagName('h2')

Alternativ können Sie querySelectorAll verwenden. Bei letzterer Funktion können Sie forEach verwenden wie oben beim Akkordeon-Beispiel. Bei getElementsByTagName müssen Sie eine Schleife bauen.

Außerdem benötigen Sie innerText, um den Text zu modifizieren, und offensichtlich müssen Sie die Überschriften "zählen".

(M6) Überschriften der nächsten Ebene nummerieren

Anschließend können Sie versuchen, auch die nächste Ebene zu nummerieren:

Seite mit nummerierten Unterüberschriften

Hier ist es ratsam, durch die Menge aller H2- und H3-Überschriften zu laufen und dann mit Hilfe der Eigenschaft nodeName zu schauen, ob man eine H2- oder H3-Überschrift vor sich hat (Vorsicht: Sie müssen bei diesem Text "H2" schreiben, nicht "h2").

9.5.3 Interaktion

(I1) Ask Yoda

Zeigen Sie einen Button, auf dem steht "Ask Yoda". Wenn der User auf den Button drückt, wird eine Weisheit von Yoda angezeigt (googlen Sie nach "yoda quotes", wenn Sie Inspiration brauchen).

Button und Zitat

Verwenden Sie das HTML-Element button für den Button. Binden Sie Ihre Funktion am besten mit Hilfe von addEventListener an das Element. Für das Zitat richten Sie ein leeres p-Element mit einem ID ein und befüllen Sie es im Event-Handler mit Hilfe von innerText.

Schön wäre es, wenn Sie ein Array von Zitaten vorhalten und mit Hilfe von Math.random() ein zufälliges Zitat bei jedem Knopfdruck zeigen.

(I2) Passwortabfrage für Dummies

So wie hier dargestellt, sollte man auf keinen Fall Passwörter behandeln. Dies ist rein für Übungszwecke!

Nehmen Sie folgendes Formular und prüfen Sie in JavaScript, ob als Passwort "secret" eingegeben wurde:

<form method="get" onsubmit="return validate()">
  <label>Name:</label>
  <input type="text" name="username"/><br/>
  <label>Passwort:</label>
  <input type="password" name="passwort" />
  <br/>
  <input type="submit" value="Login"/>
</form>
<p id="feedback"></p>

Wenn das Passwort "secret" war, schreiben Sie "Richtig!" in das Feedback-Feld, sonst schreiben Sie "Sorry, das war falsch".

Passwort-Check

Warum ist diese Abfrage so "dumm"? Ganz klar: jeder kann das Passwort sehen, sobald man den Quellcode der Seite betrachtet.

(I3) Klappbare Seitenleiste

Laden Sie sich das folgende Grid-Seitenlayout herunter:

Aufgabe Seitenleiste →

Aufgabe klappbare Seitenleiste

Fügen Sie JavaScript-Code hinzu, so dass bei Knopfdruck die Seitenleiste verschwindet. Beim nächsten Knopfdruck soll diese wieder erscheinen.

Verändern Sie die Grid-Anordnung und schalten Sie die Seitenleiste (aside) auf display:none oder display:block. Um zu unterscheiden, ob Sie die Leiste ein- oder ausblenden müssen, untersuchen Sie genau diese Eigenschaft display mit einem if.

(I4) Automatische Einblendung

Kennen Sie auch diese nervigen Erinnerungen, die nach einer Weile irgendwo am Rand erscheinen (oder auch in der Mitte). Sie sollen jetzt eine solche Erinnerung programmieren.

Automatische Einblendung

In Ihrem HTML definieren Sie das Fenster zunächst mal:

<div id="window">
  Subscribe! &nbsp;&nbsp;
  <button type="button" id="closebutton">X</button>
</div>

Das Styling aus dem Screenshot bekommt man mit den folgenden CSS-Regeln. Insbesondere wird das Element zunächst unsichtbar geschaltet mit display:none:

#window {
  display: none;
  width: 200px;
  height: 80px;
  position: fixed;
  bottom: 0;
  right: 30px;
  border-radius: 20px 20px 0 0;
  background-color: orange;
  color: white;
  font-size: 25px;    
  justify-content: center;
  align-items: center;
  box-shadow: 4px -6px 30px -10px rgba(0,0,0,0.5)
}

button {
  background-color: white;
  padding: 5px;
  border-style: none;
}

Schreiben Sie JavaScript-Code mit dem (a) das Fenster nach 2 Sekunden erscheint (siehe dazu die Funktion setTimeout) und (b) der Button das Verschwinden des Fensters verursacht (wieder mit display).

(I5) Schriftgröße ändern

Erstellen Sie eine HTML-Seite, die ungefähr so aussieht:

Interaktive Schriftgröße ändern

Die oberen zwei Elemente sind Buttons, mit denen man die Schriftgröße des Dokuments in 5-Pixel-Schritten verstellen können soll.

Verwenden Sie Elemente vom Typ button für die Buttons:

<button type="button" id="plus">+</button>
<button type="button" id="minus">-</button>

Wie immer sollten Sie den JavaScript-Code erst im JavaScript-Teil an die Buttons hängen (mit addEventListener). Noch ein Tipp: wenn Sie die aktuelle Schriftgröße beziehen, hängt da in der Regel ein "px" dran. Arbeiten Sie mit substring, um das abzuschneiden. Mit parseInt können Sie einen String in eine Zahl wandeln.

Sie können den folgenden Basiscode verwenden:

Aufgabe Schriftgröße →

(I6) Elemente nach Tags filtern

Es sei eine Webseite mit Boxen gegeben. Diese Boxen haben Schlagworte oder Tags. Sie sollen eine Reihe von Filter-Buttons generieren, so dass auf Knopfdruck nur die Boxen bleiben, die diese Tags aufweisen.

Seite mit Filterleiste

Nach Drücken auf "cat" zeigt sich dieses Bild:

Seite mit Filterleiste: gefiltert nach cat

Verwenden Sie den folgenden Basiscode und fügen Sie JavaScript hinzu:

Aufgabe Tags →

Bei den Boxen sehen Sie folgende Attribute:

<div class="box" data-tags="panda,big">
  panda,big
</div>

Die Schreibweise beim Attribut "data-tags" erlaubt ein besonders elegantes Auslesen des Attributs in JavaScript. Wenn die Variable bx eine solche Box enthält, greifen Sie so auf das Attribut data-tags zu:

bx.dataset.tags

Siehe auch Using data attributes .

Tipps zum Vorgehen:

  1. Sammeln Sie alle Tags in einem Set auf
  2. Generieren Sie Buttons für alle Tags (und einen speziellen Button "all")
  3. Erstellen Sie einen assoziativen Array, in dem für jeden Tag eine Liste der Elemente gespeichert wird, die diesen Tag haben
  4. Bei Filtern nach einem bestimmten Tag x löschen Sie zunächst alle Elemente (display:none) und stellen dann nur solche Elementen mit dem Tag x wieder sichtbar

Styling-Vorschlag für die Buttons oben:

button {
  font-family: Helvetica, sans-serif;
  font-size: 18px;
  margin: 10px;
  padding: 4px 10px;
  background-color: gray;
  color: white;
  border-radius: 8px;
  border-style: none;
}