Stand: 08.11.2018

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).

8.1 JavaScript einbinden

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

8.1.1 In HTML mit script 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>

Dieses scheint aber spätestens seit HTML 5 nicht mehr notwendig zu sein.

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>

8.1.2 JavaScript über Events auslösen

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.

Handler im HTML-Element

Sie können den Code direkt im HTML-Element als Attribut platzieren:

<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>

Handler an Elemente binden

Oft ist es praktischer, den Handler nicht in das HTML-Element hineinzuschreiben, sondern nachträglich per JavaScript an das richtige Element (oder an viele Elemente) zu binden.

Also nicht so:

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

Sondern so

<p>
  Klick mich an!
</p>

Mit addEventListener binden Sie dann nachträglich den Handler (= JavaScript-Code) an das Element:

<script>
  document.getElementsByTagName('p')[0].addEventListener('click', function() {
    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.

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

Sie sehen hier auch, dass Sie mit this innerhalb der Funktion auf das HTML-Element zugreifen können, welches das Event ausgelöst hat.

8.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. Es wird empfohlen, alles mit folgendem Gerüst zu umgeben:

(function() {

  // eigener Code...

}());

8.1.4 Übung

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.html

Sie können auch diesen Befehl ausprobieren:

alert("Hello, world!");

8.2 JavaScript und DOM

8.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.

Document-Objekt

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). 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 Abb. sehen wir die folgenden Arten von Knoten:

Elemente

Wichtig sind die Elemente (blau), denn sie entsprechen jeweils einem HTML-Element. Sie können über document auf diese Elemente zugreifen. Zu beachten ist, dass document selbst kein HTML-Element ist. Stattdessen greifen Sie über document.documentElement auf das oberste HTML-Element zu (<html>).

Sie haben außerdem Zugriff auf den <body> mit document.body und auf den <head> mit document.head
.

Um auf den HTML-Code (als Text) eines DOM-Elements zuzugreifen, verwendet man innerHTML, z.B.

document.documentElement.innerHTML


Es ist auch möglich, mit innerText nur den reinen Text (wie oben, aber ohne HTML-Markup) zu erhalten:

document.documentElement.innerText


Weitere nützliche Eigenschaften sind:

Jedes DOM-Objekt hat jetzt auch Methoden, um innerhalb des Objekts zu suchen, z.B. nach Klassen.

8.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".

Die Selektion funktioniert über Methoden, die man auf einem Knoten aufruft. Diese Methoden geben entweder einen einzelnen Knoten zurück oder eine Liste von Knoten, wie nennen sie NodeList. Eine NodeList funktioniert exakt so wie ein Array. Wichtig ist noch, dass bei solchen Selektionen die zurückgegebenen NodeListen die Elemente in der gleichen Reihenfolge enthalten, wie sie im HTML-Dokument definiert sind.

Elementselektor

Mit "Element" meinen wir einen HTML-Elementtyp wie p oder h1 oder em.

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

document.getElementsByTagName('p')


Wir erhalten NodeList. Sie bekommen den ersten Absatz daher mit

document.getElementsByTagName('p')
[0]

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

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


Wollen Sie die gesamte Knotenliste durchlaufen, verwenden Sie eine For-Schleife. Achten Sie darauf, dass die Methode getElementsByTagName() Zeit kostet, d.h. sie sollte unnötig innerhalb einer Schleife aufgerufen werden. Merken Sie sich die Liste vorab mit einer Variablen:

ls = document.getElementsByTagName('p')


Jetzt können Sie die Schleife bauen, um z.B. den Text aller Absätze auszugeben:

for (i = 0; i < ls.length; i++) {
  console.log(i + ": " + ls[i].innerText);
}

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 NodeList.

ID-Selektor

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

document.getElementById('textblock')


Hier bekommen wir ein einzelnes Element zurück, falls ein solches existiert. Ansonsten wird null zurückgegeben.

Selektion über CSS

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.

Mit querySelector() bekommen wir den ersten Knoten zurück, der "passt":

document.querySelector('nav ul')

Diese erste Methode ist sehr praktisch, weil das Ergebnis sich gut mit forEach durchlaufen lässt:

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

In dem obigen Code wird der Elementname (also 'P') so oft ausgegeben, wie das p-Element im Dokument auftaucht.

8.2.3 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, Klassen, Attribute

Oft möchten Sie ID oder Klasse eines Elements ändern, um ein anderes Stückchen CSS auf das Element anzuwenden.

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 ganz entfernen, müssen Sie removeAttribute() anwenden:

document.body.removeAttribute('id')

Klassen werden über setAttribute() geändert:

document.body.setAttribute('class', 'foo')

Wenn das Element vorher bereits Klassen angehörte, werden diese so gelöscht. Um eine Klasse hinzuzufügen, muss man also schreiben:

cls = document.body.getAttribute('class')
if (cls) {
  document.body.setAttribute('class', cls + ' foo')
} else {
  document.body.setAttribute('class', 'foo')
}

Sie können mit setAttribute() natürlich beliebige Attribute setzen, z.B. einen Handler 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.

Styling

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

document.body.style.backgroundColor = 'yellow'

Dabei ist zu beachten, dass CSS-Eigenschaft mit Bindestrich wie "background-color" in CamelBack-Schwreibweise geändert werden müssen, also "backgroundColor".

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.

8.2.4 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.

Mit parentNode greifen Sie auf den Elternknoten zu:

document.body.parentNode.nodeName

Mit children auf alle Kinder:

document.body.children

Mit nextElementSibling auf alle Kinder:

document.body.nextElementSibling

Sie müssen aufpassen, ob Sie auch die Texte als "Knoten" zählen möchten oder nicht. Mit nextSibling bekommen Sie evtl. einen Textknoten (#text), mit nextElementSibling bekommen Sie das nächste HTML-Element (was i.d.R. auch gewünscht ist).

8.3 Beispiele

8.3.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:

0

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>

Im JavaScript-Code führen wir eine Variable ein:

ex_counter = 0;

Jetzt binden wir Code an den Button, um die Variable hochzuzählen und den Inhalt in das span-Feld einzutragen:

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.

8.3.2 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

In der HTML-Datei würde man das gesamte Akkordeon mit einem <div> mit Klasse "accordion" umgeben. Jeder aufklappbare Abschnitt besteht aus einer Überschrift h2 und einem weiteren <div>, dem wir die Klasse "section" geben.

<div class="accordion">
  <h2>Vorlesung 1</h2>
  <div class="section">
    <p>
      Lorem ipsum ...
    </p>
    <p>
      Lorem ipsum ...
    </p>
  </div>

  <h2>Vorlesung 2</h2>
  <div class="section">
    <p>
      Lorem ipsum ...
    </p>
    <p>
      Lorem ipsum ...
    </p>
  </div>

  ...
</div>

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

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

Als erstes schreiben wir uns eine Funktion, die alle Abschnitte einklappt. Dazu verwenden wir querySelectorAll, um die Abschnitte einzusammeln und forEach, um das Styling der Abschnitte auf display: none zu setzen.

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

Als nächstes hängen wir an alle h2-Elemente einen Listener, der auf einen Klick reagiert. Bei einem Klick sollen (a) alle Abschnitte mit unserer Funktion collapseAll eingeklappt werden und dann (b) der Abschnitt, der zur angeklickten Überschrift gehört, aufgeklappt werden - diesem bekommen wir 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';
  });
});

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

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

  document.querySelector('h1').addEventListener('click', function() {
    collapseAll();
  });

  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: js_akkordeon.html

8.4 Übungen

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.

(A) 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.

(B) 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.

(C) 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.

(D) 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'.

(E) Ü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".

(F) Ü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").