HTML templates
Med metoderna från förra artikeln kan du bygga vilken struktur som helst.
Frågan är om du vill det. Den här artikeln handlar om <template>-elementet, en av de fyra
standarderna bakom webbkomponenter, och om varför HTML helst ska skrivas som HTML.
Tidsåtgång: Ungefär 10 minuter.
När JavaScript blir HTML
Section titled “När JavaScript blir HTML”Så här kan det se ut när en liten lista med två länkar byggs med DOM-metoder:
const post = document.createElement('div')post.classList.add('post')const list = document.createElement('ul')const first = document.createElement('li')first.classList.add('active')const firstLink = document.createElement('a')firstLink.setAttribute('href', '#')firstLink.textContent = 'Första länken'first.append(firstLink)const second = document.createElement('li')const secondLink = document.createElement('a')secondLink.setAttribute('href', '#')secondLink.textContent = 'Andra länken'second.append(secondLink)list.append(first, second)post.append(list)Och så här ser samma struktur ut som HTML:
<div class="post"> <ul> <li class="active"><a href="#">Första länken</a></li> <li><a href="#">Andra länken</a></li> </ul></div>Den första versionen måste du läsa rad för rad för att förstå vad den bygger. Den andra ser du på några sekunder. JavaScript-koden är egentligen HTML, bara skriven på ett sätt som är svårt att läsa, svårt att ändra i och lätt att göra fel i.
Att hålla isär det som hör ihop
Section titled “Att hålla isär det som hör ihop”Du har hela kursen delat upp kod i funktioner, klasser och moduler. Samma tänkande gäller webbläsarens tre språk, och brukar kallas separation of concerns:
| Språk | Ansvar |
|---|---|
| HTML | struktur och innehåll |
| CSS | presentation |
| JavaScript | beteende |
Det du har gjort hittills, med HTML i index.html, CSS i css/ och JavaScript i js/, är ett sätt
att sortera: efter teknik. Det fungerar bra så länge applikationen är liten. Ett annat sätt är att
sortera efter funktion, så att allt som hör till en viss del av gränssnittet ligger samlat, oavsett
språk. Det är så webbkomponenter är organiserade, och det är dit du är på väg. I båda fallen gäller
dock att HTML skrivs som HTML och inte byggs element för element i JavaScript.
Elementet <template>
Section titled “Elementet <template>”Elementet <template> innehåller HTML som webbläsaren tolkar men inte visar. Innehållet är
inert, overksamt:
- Det renderas inte, och syns inte på sidan.
- Skript i det körs inte, och bilder hämtas inte.
- Det hittas inte av
document.querySelector(), eftersom det inte är en del av dokumentets träd.
Innehållet ligger i stället i egenskapen content, ett DocumentFragment. Det används genom att
du klonar det och sätter in kopian där du vill ha den:
<template id="user-template"> <li class="user"> <span class="name"></span> <a class="profile">Profil</a> </li></template>const template = document.querySelector('#user-template')const list = document.querySelector('#users')
for (const user of users) { const item = template.content.cloneNode(true) item.querySelector('.name').textContent = user.name item.querySelector('.profile').href = user.url list.append(item)}Lägg märke till att kopian fylls i med textContent och href efter kloningen. Mallen
innehåller strukturen, koden innehåller data. Det är samma regel som för innerHTML i förra
artikeln: data utifrån sätts alltid som text.
template.content.cloneNode(true) är en rad du kommer att skriva många gånger. Det är lätt att
glömma .content, och då klonar du <template>-elementet i stället för dess innehåll.
Fråga: varför syns ingenting?
Section titled “Fråga: varför syns ingenting?”const template = document.querySelector('#user-template')const item = template.content.cloneNode()list.append(item)Koden ger inget fel, men inget nytt <li> dyker upp i listan. Varför?
cloneNode() utan argument gör en grund kopia, som cloneNode(false). Det som klonas är
fragmentet template.content, och en grund kopia av ett fragment är ett tomt fragment. När ett
tomt fragment sätts in i listan händer ingenting.
Med cloneNode(true) kopieras hela trädet i fragmentet, och <li> kommer med. Samma misstag
gjordes för övrigt live i inspelningen.
Fråga: behövs kloningen?
Section titled “Fråga: behövs kloningen?”Anta att du hoppar över kloningen och sätter in mallens innehåll direkt, tre gånger:
for (let i = 0; i < 3; i++) { list.append(template.content)}Hur många <li> får listan?
Ett.
När ett fragment sätts in flyttas dess barn in i dokumentet, och fragmentet blir tomt. Efter
första varvet är alltså template.content tomt, och de två sista anropen sätter in ett tomt
fragment. Mallen är dessutom förbrukad för resten av sidans livstid.
Det är därför du alltid klonar: kopian flyttas in i dokumentet, och originalet finns kvar till nästa gång.
Mallar som strängar i JavaScript
Section titled “Mallar som strängar i JavaScript”En <template> i index.html fungerar bra så länge den används på en enda sida. När du nästa
vecka bygger webbkomponenter vill du att allt som hör till en komponent ligger i komponentens egna
filer, så att komponenten går att använda var som helst. Då skapar du mallen från JavaScript i
stället:
const template = document.createElement('template')template.innerHTML = ` <li class="user"> <span class="name"></span> <a class="profile">Profil</a> </li>`
const item = template.content.cloneNode(true)Strängen är skriven med backticks, en template-sträng, så att den kan sträcka sig över flera rader.
Här är innerHTML på sin plats: strängen är skriven av dig, i källkoden, utan data utifrån. Data
sätts precis som förut med textContent efter kloningen. Skulle du i stället stoppa in data med
${...} i strängen är du tillbaka i problemet från förra artikeln.
Att HTML-koden nu står i en sträng känns som ett steg tillbaka, eftersom editorn visar den i samma färg som all annan text. Det finns tillägg till Visual Studio Code som färgkodar HTML i template-strängar.
Mallen i en egen fil
Section titled “Mallen i en egen fil”Det finns ännu inget standardiserat sätt att importera en .html-fil till en JavaScript-modul. I
kursen löser Vite det åt dig: med tillägget ?raw importeras filens innehåll som en vanlig sträng.
Det är så den färdiga komponenten i Tiny Tunes är byggd:
import htmlText from './tiny-tune.html?raw'
const template = document.createElement('template')template.innerHTML = htmlText?raw är en funktion i Vite, inte i webbläsaren. Vite skriver om importen till en sträng innan
koden når webbläsaren. Mer om det, och om hur komponentens filer är organiserade, kommer i
artikeln En komponents filer i kursvecka 10.
Mallmotorer och ramverk
Section titled “Mallmotorer och ramverk”Behovet av mallar är äldre än <template>. Innan elementet fanns byggdes mallmotorer som
Mustache, Handlebars och Pug, och de används fortfarande mycket, framför allt på servern. Ramverk
som React har egna sätt att skriva HTML i JavaScript, som JSX. De kan mer än <template>, till
exempel villkor och loopar direkt i mallen.
Kursen håller sig till webbstandarderna. De fungerar i alla webbläsare utan bibliotek, och det du
lär dig blir inte inaktuellt när nästa ramverk kommer. Förstår du hur <template> fungerar känner du
igen idén i alla mallmotorer du stöter på.
Att ta med sig
Section titled “Att ta med sig”- Skriv HTML som HTML. Långa kedjor av
createElementär svåra att läsa och underhålla. - Innehållet i
<template>tolkas men visas inte, och ligger itemplate.content. - Klona alltid med
template.content.cloneNode(true), fyll i kopian och sätt in den. - Data sätts med
textContentefter kloningen, aldrig i mallens HTML-sträng. - En mall kan skapas från en sträng i JavaScript. Det är så du kommer att göra i webbkomponenterna.
Vidare läsning
Section titled “Vidare läsning”- Läsanvisningarna för veckan, i synnerhet kapitel 15.6.2 i kursboken.
- <template>: The Content Template element på MDN.
- Using templates and slots på MDN. Slots kommer i kursvecka 10.