Att ändra i DOM:en
I förra artikeln lärde du dig hitta i DOM-trädet. Nu är det dags att ändra i det: skapa nya element, flytta och ta bort befintliga, sätta text, attribut och klasser. Det är precis det du gör i veckans övningsuppgift Tiny Tunes.
Tidsåtgång: Ungefär 20 minuter.
Några korta frågor finns insprängda i texten.
Skapa noder
Section titled “Skapa noder”Nya noder skapas med metoder på document:
const paragraph = document.createElement('p') // an empty <p></p>const text = document.createTextNode('Hej världen!') // a text nodeparagraph.appendChild(text) // <p>Hej världen!</p>Ett nytt element syns inte förrän du har satt in det i dokumentet. Fram till dess är det ett fristående objekt som bara din kod känner till. Det är ofta praktiskt: bygg klart elementet, med text, attribut och klasser, och sätt in det sist.
Sätta in noder
Section titled “Sätta in noder”Den klassiska metoden är appendChild(), som lägger till en nod sist bland ett elements barn.
insertBefore(nyNod, referensnod) sätter in en nod före ett av elementets barn. Båda finns i
otaliga exempel och fungerar utmärkt.
Sedan några år finns också en uppsättning modernare metoder som är enklare att använda. De tar en eller flera noder, och strängar blir automatiskt textnoder:
<!-- target.before(node) --><ul id="target"> <!-- target.prepend(node) --> <li>Befintligt barn</li> <!-- target.append(node) --></ul><!-- target.after(node) -->const list = document.querySelector('#target')const item = document.createElement('li')item.append('Ny punkt') // the string becomes a text nodelist.append(item) // last in the listlist.before('Rubrik: ') // as a sibling before the listInspelningen nämner att det inte finns någon insertAfter(), så att man får kombinera
insertBefore() med nextSibling. Det stämmer fortfarande, men i dag skriver du bara
referens.after(nyNod).
Att flytta är att sätta in
Section titled “Att flytta är att sätta in”En nod kan bara finnas på ett ställe i trädet. Sätter du in en nod som redan finns i dokumentet flyttas den; du behöver inte ta bort den först.
Fråga: vad händer med listan?
Section titled “Fråga: vad händer med listan?”<ol id="queue"> <li>Ada</li> <li>Grace</li> <li>Linus</li></ol>const queue = document.querySelector('#queue')queue.append(queue.firstElementChild)Ada flyttas sist: listan blir Grace, Linus, Ada.
append() får en nod som redan finns i listan, så den tas bort från sin gamla plats och sätts in
på den nya. Det blir ingen kopia. Vill du ha en kopia får du klona noden först (se nedan).
Ta bort, ersätta och tömma
Section titled “Ta bort, ersätta och tömma”| Vill du | Modern metod | Klassisk metod |
|---|---|---|
| ta bort en nod | node.remove() |
parent.removeChild(node) |
| ersätta en nod | oldNode.replaceWith(newNode) |
parent.replaceChild(newNode, oldNode) |
| tömma ett element | element.replaceChildren() |
loopa med removeChild() |
Att tömma ett element är vanligt, till exempel innan du ritar om en lista. Ta bort alla barnnoder,
inte bara alla element; annars blir textnoderna med mellanrum kvar. replaceChildren() utan
argument gör det i ett anrop. Med argument ersätter den det gamla innehållet med det nya.
Kopiera noder
Section titled “Kopiera noder”cloneNode() gör en kopia av en nod. Argumentet avgör hur mycket som följer med:
cloneNode(true)kopierar noden och hela trädet under den, alla barn, barnbarn och textnoder.cloneNode(false), eller utan argument, kopierar bara noden själv. Ett klonat<ul>blir tomt.
Två saker följer inte med eller bör ändras. Händelselyssnare som lagts till med
addEventListener() (kursvecka 11) kopieras inte. Och har originalet ett id får kopian samma, så
byt eller ta bort det innan du sätter in kopian, eftersom id ska vara unika.
cloneNode(true) är också kärnan i hur HTML templates används, som du läser om i
nästa artikel.
Många noder på en gång
Section titled “Många noder på en gång”Ska du skapa många element, till exempel en lista med tio punkter, kan du bygga dem i ett
DocumentFragment. Ett fragment är en lätt behållare som inte är en del av dokumentet. När du
sätter in fragmentet flyttas dess barn in i dokumentet, och fragmentet självt blir tomt.
const fragment = document.createDocumentFragment()
for (const name of ['Ada', 'Grace', 'Linus']) { const item = document.createElement('li') item.textContent = name fragment.append(item)}
list.append(fragment) // the document changes once, not three timesWebbläsaren ritar ändå inte om sidan medan din kod körs, eftersom koden och renderingen delar
huvudtråden (se Webbläsaren som körmiljö).
Fragmentets fördel är i första hand tydlig kod: bygg klart vid sidan av, sätt in en gång. Har du
elementen i en array fungerar också list.append(...items).
Text: textContent eller innerHTML
Section titled “Text: textContent eller innerHTML”Att skapa en textnod och sätta in den blir fort omständligt. Två egenskaper gör det enklare:
element.textContent = 'Ny text' // replaces all content with a text nodeelement.innerHTML = '<p>Ny text</p>' // parses the string as HTMLtextContent skapar alltid bara text. Innehåller strängen < och > visas de som tecken på
sidan.
innerHTML skickar strängen genom HTML-tolken, samma tolk som läser in sidan, och ersätter
elementets innehåll med de element och textnoder som blir resultatet. Det är bekvämt, men farligt
om någon del av strängen kommer utifrån.
Fråga: vad visas på sidan?
Section titled “Fråga: vad visas på sidan?”Anta att name kommer från ett formulärfält som besökaren har fyllt i:
const name = '<img src="x" onerror="alert(\'Hackad!\')">'
first.textContent = `Hej ${name}!`second.innerHTML = `Hej ${name}!`Vad händer i first och i second?
I first visas hela strängen som text, inklusive <img src=...>. Ingenting körs.
I second tolkas strängen som HTML och ett <img>-element skapas. Bilden x finns inte, så
onerror utlöses och JavaScript-koden körs: en ruta med texten “Hackad!” visas.
Det här kallas XSS, cross-site scripting. I stället för en harmlös alert() kan angriparens kod
läsa vad användaren skriver, skicka iväg data eller göra saker i användarens namn. Har strängen
sparats på en server drabbas dessutom alla som visar den.
Inspelningen säger att <script>-taggar i en sträng körs när de sätts in med innerHTML. Det gör
de faktiskt inte, eftersom webbläsaren medvetet hoppar över skript som kommer den vägen. Men som
frågan visar finns andra vägar, som händelseattribut, så slutsatsen står sig:
- Använd
textContentför all text, och alltid för data som kommer från användaren, ett API eller någon annan källa du inte kontrollerar. - Använd
innerHTMLbara med strängar du själv har skrivit i källkoden, utan insprängd data utifrån. Det är så du kommer att använda den när du skapar mallar i nästa artikel.
Du kan också stöta på innerText. Den liknar textContent, men tar hänsyn till CSS, till exempel
dolda element. Den är långsammare och ger sällan något du behöver; använd textContent.
Attribut och egenskaper
Section titled “Attribut och egenskaper”HTML-attribut läser och ändrar du med fyra metoder:
link.getAttribute('href') // reads the value, or nulllink.setAttribute('href', 'https://lnu.se/') // sets or overwriteslink.hasAttribute('target') // true or falselink.removeAttribute('target') // removes the attributeDe flesta standardattribut finns också som egenskaper direkt på elementet: link.href,
image.src, image.alt, element.id. Inspelningen beskriver egenskaperna som en Microsoft-genväg
utanför standarden, men de är standardiserade och lika korrekta att använda. Skillnaden är att
egenskapen ger det tolkade värdet, medan attributet ger det som står i HTML-koden:
// <a href="/kurser">...</a> on the page https://coursepress.lnu.se/link.getAttribute('href') // '/kurser'link.href // 'https://coursepress.lnu.se/kurser'
// <input value="Ada">, and the user has typed "Grace"input.getAttribute('value') // 'Ada', the initial valueinput.value // 'Grace', the current valueEtt exempel på nyttan: sätter du src på ett nytt <img>-element börjar webbläsaren hämta bilden
direkt, redan innan elementet har satts in i dokumentet. Glöm inte en alt-text som beskriver
bilden, eller alt="" om bilden bara är dekoration.
Fråga: är knappen avstängd?
Section titled “Fråga: är knappen avstängd?”button.setAttribute('disabled', 'false')Går det att klicka på knappen efter raden ovan?
Nej, knappen är avstängd.
disabled är ett så kallat booleskt attribut. Det är förekomsten av attributet som räknas, inte
dess värde. Värdet 'false' är bara en sträng, och strängen gör ingen skillnad. Använd egenskapen i
stället, som tar ett riktigt booleskt värde:
button.disabled = falseeller ta bort attributet med button.removeAttribute('disabled').
Egna data på ett element lägger du i attribut som börjar med data-. De nås via egenskapen
dataset: <li data-id="42"> ger item.dataset.id === '42'.
Klasser
Section titled “Klasser”Det bästa sättet att ändra hur ett element ser ut är oftast att byta CSS-klass och låta reglerna
ligga i CSS-filen. Egenskapen classList har metoderna du behöver:
| Metod | Gör |
|---|---|
add('a', 'b') |
lägger till en eller flera klasser |
remove('a') |
tar bort en klass |
toggle('a') |
lägger till om den saknas, tar bort om den finns |
toggle('a', villkor) |
lägger till om villkoret är sant, annars tar bort |
contains('a') |
true om klassen finns |
replace('a', 'b') |
byter en klass mot en annan |
Undvik att sätta className eller setAttribute('class', ...). Båda ersätter hela
klasslistan:
// <div class="important">box.className = 'danger' // <div class="danger">, important is gonebox.classList.add('danger') // <div class="important danger">Du vet inte alltid vilka andra klasser ett element har, eller vem som har satt dem. classList
låter dem vara.
Stil direkt från JavaScript
Section titled “Stil direkt från JavaScript”Varje element har egenskapen style, som motsvarar style-attributet i HTML. CSS-egenskaper med
bindestreck skrivs i camelCase, eftersom bindestreck inte är tillåtet i en identifierare:
element.style.color = '#aa5698'element.style.fontSize = '1.25rem' // font-sizeelement.style.marginLeft = '2em' // margin-leftelement.style.setProperty('--progress', '40%') // custom propertyCSS-egenskapen float heter cssFloat i JavaScript, eftersom float var ett reserverat ord i
äldre versioner av språket. Med setProperty() skriver du egenskapsnamnet precis som i CSS.
Sätt stil direkt bara när värdet verkligen räknas ut i koden, till exempel en position eller en
procentsats. Allt annat hör hemma i CSS, och du byter klass. Det håller isär presentation och
beteende, och du hittar alla stilregler på ett ställe. För att dölja och visa ett element finns
dessutom egenskapen hidden: element.hidden = true. Tänk på att en CSS-regel med display
på elementet tar över, så att elementet syns ändå.
Att ta med sig
Section titled “Att ta med sig”- Skapa med
createElement, bygg klart och sätt in sist. append,prepend,before,after,removeochreplaceChildrentäcker det mesta. Att sätta in en nod som redan finns flyttar den.cloneNode(true)kopierar hela trädet under noden,cloneNode(false)bara noden.- Använd
textContentför text.innerHTMLbara med strängar du själv har skrivit. - Egenskaper som
hrefochvalueger tolkade och aktuella värden, attributen ger det som står i HTML-koden. - Byt klass med
classListi stället för att sätta stil direkt.
Vidare läsning
Section titled “Vidare läsning”- Läsanvisningarna för veckan, i synnerhet kapitel 15.3 och 15.4 i kursboken.
- Manipulating documents på MDN.
- Element: innerHTML property på MDN, avsnittet om säkerhet.
- Element: classList property på MDN.
- Fortsätt med nästa artikel, HTML templates.