cloneNode – Element kopieren / klonen
cloneNode ist eine ausgesprochen effektive Technik, um ganze Strukturen in den DOM-Baum einzufügen – viel schneller als das Anlegen von Elementen mit createElement(). Analog kopiert importNode Elemente aus einem anderen Dokument.
cloneNode ()
cloneNode kopiert ein Element des DOM mitsamt seinen Kind-Elementen, um die Struktur des Dokuments zu ändern. Die Kopie kann an einer anderen Stelle des DOM aufgehangen werden.
Der Parameter deep ist vom Typ Boolean und gibt an, ob der Knoten mit all seinen Elementen geklont werden soll (deep = true). Event Listener, die auf Events des Elements warten, werden allerdings auch mit deep = true nicht mitgeliefert.
Ein Klick oder Touch auf das Bild erzeugt einen Overlay oder eine Lightbox, die das Bild größer auf dunklem Hintergrund zeigt. Auch bei komplexen und großen Elementen mit vielen Unterelementen erzeugt cloneNode die Kopie innerhalb eines Augenblicks.
<div class="overlay"> <div class="modal"> <figure> <img loading="lazy" src="orange-citrus-cherry.svg" width="741" height="444" alt="…"> <figcaption>Klick oder Touch zum Vergrößern</figcaption> </figure> </div> </div>
Die Bedingung der Abfrage if (document.querySelector(".modal") != null) prüft, ob ein Element mit class="modal" existiert. Der Check verhindert Javascript-Fehler, wenn die Struktur des HTML-Elements eines Tages geändert werden sollte. Wenn ein Element mit class="modal" existiert, wird das komplette Element mit let newNode = modal.cloneNode(true) geklont. Der geklonte Knoten existiert zunächst nur im Speicher.
if (document.querySelector(".modal") != null) { document.querySelector(".modal").addEventListener ("click", function (eve) { const newNode = this.cloneNode (true); const div = document.createElement ("div"); div.classList.add ("large"); div.appendChild(newNode); this.parentNode.insertBefore (div, this); div.onclick = function () { this.parentNode.removeChild(document.querySelector (".large")); } eve.preventDefault(); }); }
Erst wenn ein neues Element mit modal.parentNode.insertBefore(div, modal) in das DOM eingefügt wird, entsteht der Overlay – die klassische Lightbox. Am Ende entfernt ein Klick auf die Lightbox den Overlay:
this.parentNode.removeChild(document.querySelector(".large"))
cloneNode ist schnell und perfekt geeignet, um große Bereich des DOMs zu kopieren. Hinterher kann das Original mit removeChild gelöscht werden.
HTML-Templates für Javascript
HTML bietet ein spezielles Template-Tag für Javascript, das mit Javascript cloneNode in »echte« HTML-Fragmente umgewandelt und eingebunden wird.
Vorher ist das Markup innerhalt des Template-Elements kein Teil des DOMs und kann auch nicht mit Methoden wie document.getElementById angesprochen werden. Inhalte eines Template-Tags werden erst heruntergeladen, wenn der Template-Code offiziell geklont und eingebunden ist: Bilder in einem Template-Tag werden nicht geladen.
<div id="book-list"> <!-- Platzhalter werden per JS ersetzt --> </div>
<template id="book-template"> <div class="book-card"> <img src="" alt="" loading="lazy" width="" height=""> <hgroup></hgroup> <div class="cards-category"></div> </div> </template>
const container = document.getElementById("book-list"); const template = document.getElementById("book-template"); // Erst Observer definieren! const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { const placeholder = entry.target; const book = JSON.parse(placeholder.dataset.book); const clone = template.content.cloneNode(true); const card = clone.querySelector(".book-card"); const img = card.querySelector("img"); const hgroup = card.querySelector("hgroup"); const p = card.querySelector("p"); const cat = card.querySelector(".cards-category"); img.src = book.img; img.alt = book.titel; img.width = book.width; img.height = book.height; hgroup.textContent = book.titel; p.textContent = book.autor; cat.textContent = book.type; // Klasse für Transition nach dem nächsten Frame requestAnimationFrame(() => { card.classList.add("visible"); }); placeholder.replaceWith(clone); observer.unobserve(placeholder); } }); }, { rootMargin: "200px", threshold: 0.01 }); // Buchdaten laden und Platzhalter erzeugen fetch('books.json') .then(res => res.json()) .then(books => { books.slice(0,8).forEach(book => { const div = document.createElement("div"); div.dataset.book = JSON.stringify(book); div.style.minHeight = "200px"; container.appendChild(div); observer.observe(div); }); });
Das Template-Beispiel für die kleine Liste von Buch-Cards ist schlicht und kann im head- oder an belieber Stelle im body-Element stehen.
Das Schöne an den schlichten Template-Elementen aus purem HTML: Für Wiederholungen der Elemente und für Abfragen werden keine speziellen Direktiven gebraucht, sondern einfach nur Javascript in Reinform.
Loop in Handlebars
{{#each array}} {{@index}}: {{this}} {{/each}}
Loop Javascript
for (const elem of slides) { … }
Template Literals – native Javascript Template Engine
Template Literal ist mit ES6 auf den Plan getreten und ersetzt das Katzenhaarknäuel von verschachtelten einfachen und doppelten Hochkommas, +- und \-Zeichen der Mischung aus Strings und Variablen.
Eine typische Anwendung für Javascript Templates ist das dynamische Erstellen von Listeneinträgen und Tabellenzeilen in Anwendungen, wenn z.B. Elemente in einem JSON-Array geliefert und als Tabelle oder Liste dargestellt werden sollen.
const lager = [ {"frucht":"🌽", "ger":139, "austria":20, "switzerland":11 }, {"frucht":"🍆", "ger":170, "austria":29, "switzerland":9 }, {"frucht":"🍅", "ger":163, "austria":21, "switzerland":12 }, {"frucht":"🍇", "ger":109, "austria":13, "switzerland":4 }, {"frucht":"🍎", "ger":115, "austria":17, "switzerland":8 } ]; let template = lager.map(item => { return ` <div class="entry"> <span class="frucht">${item.frucht}</span> <span class="num ger">${item.ger}</span> <span class="num austria">${item.austria}</span> <span class="num switzerland">${item.switzerland}</span> </div> `; }).join(""); document.querySelector("fruit-shop").innerHTML = template;
Eleganter als HTML-Templates für Javascript allemal, und eine native Javascript-Template-Engine anstelle der wechselnden Template Engines von Handlebar bis Moustach.
cloneNode und EventListener
cloneNode() kopiert keine Event Listener. Keine Technik des DOM übernimmt Event Listener, die einzigen Optionen sind:
- Den Event Listener manuell auf den geklonten Knoten setzen.
- Event-Delegation: Den Event Listener auf einen Node setzen, der sowohl das Original als auch die Kopie enthält.
- Ein Wrapper für Node.addEventListener – so macht es clone() in jQuery, wo man einen Node mitsamt Event Listener kopieren kann (.clone( [withDataAndEvents ] )).
importNode
importNode() importiert Element von einem anderen Dokument (z.B. aus einem iframe). Das Element wird nicht aus dem anderen Dokument gelöscht.
const mynode = document.querySelector("iframe"). contentDocument.getElementById ("foo"); let newNode = document.importNode(mynode, true);