Custom Elements

Bei Custom Elements, also selbstdefinierten HTML-Elementen, haben wir es in der Hand, diese mit Eigenschaften und Verhaltensweisen nach unseren Wünschen auszustatten. Eine gut verständliche Beschreibung finden wir im MDN.
In diesem Beitrag wollen wir in erster Linie herausfinden, wie man ein Custom Element definiert und welche JavaScript-Features uns helfen, eigene Funktionalitäten zu unterlegen.

Ein Beispiel vorbereiten

Wir beginnen wieder mit einem Beispiel. Dazu bauen wir ein HTML-Dokument auf, nehmen das in HTML und CSS beschriebene Grunddokument als Vorlage und bringen folgende Codeschnipsel ein:

in den HTML-Bereich

<button onclick="window.history.back()">Zurück</button>
<h3>Custom Element Beispiel</h3>

<my-div>Ein Custom Element</my-div>
<div id="display">Protokoll:<br></div>

in den CSS-Bereich

body { background-color: #fff;  }

#display, my-div {
  margin:5pt; padding:2pt;
  width: min-content; 
  white-space: nowrap;
}

#display { border:1pt solid green; }
my-div   { border:1pt solid red;   }

in den Scriptbereich 2

"use strict";

const myDisplay = document.getElementById("display");

class myDivElement extends HTMLElement {
  static observedAttributes = ["value"];
  constructor() {
    super();
  }

  connectedCallback() {
    myDisplay.innerHTML += "Das Custom Element wurde zur Seite hinzugefügt.<br>";
  }

  attributeChangedCallback(AttrName, alterWert, neuerWert) {
    myDisplay.innerHTML += `${AttrName} geändert: von ${alterWert} zu ${neuerWert}.<br>`;
  }
  
  disconnectedCallback() {
    myDisplay.innerHTML += 'Custom Element wurde vernichtet.<br>';
  }

  adoptedCallback() {
    myDisplay.innerHTML += 'Custom Element wurde adoptiert.<br>';
  }

}
Es wird eine Klasse mit frei wählbaren Namen (hier: myDivElement) erstellt, die vom DOM-Interface HTMLElement alle Properties und Methoden erbt. Hinzu kommen vier Callback-Methoden, die mit eigenem Programmcode gefüllt werden können.
Diese Methoden feuern automatisch (werden automatisch ausgeführt), je nachdem, was wir mit unserem Custom Element anstellen. Genau diese Momente sollten wir herausfinden und nutzen die Methoden zum Protokollieren.

Screenshot 1

Darstellung wie beschriebenIm Browser sieht unsere HTML-Seite jetzt so aus.

Ein Custom Element definieren

Mit dem HTML-Code
<my-div>Ein Custom Element</my-div>
haben wir bereits ein benutzerdefiniertes Element erstellt, nur eben ohne eigene Funktionalität. Ein Bindestrich im Elementnamen ist Pflicht, um nicht mit zukünftigen HTML-Elementnamen in Konflikt zu kommen. Auch Kleinbuchstaben sind Pflicht.
Damit das Custom Element und die Klassendefinition zusammenkommen, müssen wir das Element registrieren, und zwar mit diesem Code:
// Custom Element registrieren mit Elementname, Klassenname
customElements.define("my-div", myDivElement); 

Screenshot 2

Darstellung wie beschrieben
Nach Reload haben wir das erste Protokoll im Browser, und wir sehen, dass connectedCallback() gefeuert hat.
Registrierte Custom Elemente werden mit einem Objekt der angegebenen Klasse unterlegt, egal wie viele Elemente desselben Typs angelegt werden. Und sobald ein neues solches Element hinzugefügt wird, feuert connectedCallback().

Attribute im Custom Element

Die Methode
attributeChangedCallback(AttrName,alterWert,neuerWert) {}
feuert, wenn überwachte Attribute verändert werden und erhält automatisch drei Werte. Dabei ist es egal, wie wir die Parameter nennen, so wie in unserem Beispiel oder ganz anders. Die Reihenfolge und die Bedeutung der Werte werden dadurch nicht beeinflusst, es ist immer ein Attributname, der bisherige Wert des Attributs und sein neuer Wert.
Um das zu überprüfen, erfinden wir mal das Attribut value. Allerdings bedarf es der Zeile
static observedAttributes = ["value"];
als erste Definition in unserer Klasse. Alle Attribute, auf die reagiert werden soll, müssen in dem Array eingetragen, in dem im Beispiel nur der Name value eingetragen ist.
So, und nun fügen wir folgendes JavaScript-Codeschnipsel hinzu.
// Attribut "value" für my-div
const myElement = document.getElementsByTagName('my-div')[0];
myElement.setAttribute('value','Wert'); // Attribut hinzufügen
myElement.removeAttribute('value');     // Attribut entfernen

Screenshot 3

Darstellung wie beschrieben Die Ausgaben von attributeChangedCallback() kommen zum bisherigen Protokoll hinzu. Und weil die Parameter in die Ausgaben eingebettet sind, erkennen wir auch die Argumente, die die Methode übergeben bekommen hat.

Custom Element entfernen

Das machen wir aber nur per JavaScript und ergänzen unser Programm entsprechend.
// Custom Element vernichten
const bodyElement = document.getElementsByTagName('body')[0];
bodyElement.removeChild(myElement);

Screenshot 4

Darstellung wie beschrieben
Wie erwartet, feuert die Methode disconnectedCallback() und unser Element ist von der Seite optisch verschwunden.

Custom Element adoptieren

Wir wollen dazu kommen, dass adoptedCallback() feuert. Das passiert, wenn unser Custom Element mittels adoptNode() in ein anderes Dokument verschoben wird. Dazu sind einige Vorbereitungen nötig.

Code zum Entfernen des Custom Elements auskommentieren

Zuerst machen wir die beiden Codezeilen zum Vernichten unseres Custom Element durch Auskommentieren unwirksam, sodass wir wieder den Stand wie bei Screenshot 3 haben.

iframe-Element einbringen

Den HTML-Bereich ergänzen wir mit einem iframe-Element.
<iframe src="mini.html" width="360"></iframe>
Das Element soll das Dokument mini.html sichtbar machen.

mini.html

Und hier ist der komplette Code:
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <style>
my-div { border:1pt solid red; }
    </style>
  </head>
  <body id="newBody">
    <h4>minimales Dokument</h4>
    <div id="container"></div>
    <hr> 
    <script>
      const myContainer = document.getElementById('container');
      // Zugriff auf das Custom Element im übergeordneten Dokument
      const myElemForAdopt = parent.document.querySelector('my-div');
    </script>
    <button onclick="myContainer.appendChild(document.adoptNode(myElemForAdopt));">
      Hole my-div
    </button>
  </body>
</html>
In mini.html befindet sich auch ein div-Element mit der Id container, der soll unser Custom Element adoptieren.
Die Konstante myElemForAdopt referenziert unser Custom Element. Das ist möglich; zwar ist mini.html ein eigenes Dokument, aber der iframe ist ja auch ein Element unseres Beispieldokuments.

Zugriff!

Die entscheidende Anweisung steht als Click-Event-Handler im Attribut onclick des Buttons. Beim Anklicken wird der Container unser Custom Element als Child-Element erhalten, das per document.adoptNode() geholt wird.
Schauen wir uns das Beispieldokument erst einmal im Browser an.

Screenshot 5

Darstellung wie beschrieben
So, nun brauchen wir nur noch auf den Button zu klicken.

Screenshot 6

Darstellung wie beschrieben
Wir sehen, dass unser Custom Element oben verschwunden ist, dafür aber im iframe auftaucht. Um im Vergleich zum Screenshot 5 sehen wir im Protokoll die einzelnen Phasen des Vorgangs, und vor allem, dass adoptedCallback() gefeuert hat.