Översikt
Nu börjar du på allvar strukturera upp din kod på klienten genom att paketera den i komponenter! Veckans tre artiklar går igenom hur ett eget element registreras och får data utifrån, hur det får ett inkapslat inre med shadow DOM och hur en komponent organiseras i filer. Information om veckans upplägg annonseras löpande i kursens Zulip-kanal.
Veckostart med klickräknaren
Section titled “Veckostart med klickräknaren”Tisdagen i kursvecka 10, samma dag som deadline för A3, började med frågor om bokningen av den muntliga examinationen. Därefter livekodade Mats klickräknaren steg för steg, från ett odefinierat element till en komponent med shadow DOM, attribut, lyssnare och en publik metod, medan Mikael ställde frågorna. Passet avslutades med en utblick mot ramverk och mot läsperiod två.
Om passet
Section titled “Om passet”Tisdagens pass i kursvecka 10 hölls av Mats och Mikael tillsammans, samma dag som deadline för A3. Det inleddes med frågor om bokningen av den muntliga examinationen. Därefter visade Mats veckans upplägg och livekodade exemplet Klickräknaren i en enda HTML-fil, med Mikael som den som ställde frågorna. Passet avslutades med en utblick mot ramverk och mot läsperiod två, som börjar nästa vecka.
Den muntliga examinationen av A3
Section titled “Den muntliga examinationen av A3”Går det bara att boka tid på torsdag? Ja, än så länge. Tider på fredagen läggs ut först när torsdagens tider är slut, och när passet hölls fanns det torsdagstider kvar. Tar de slut lägger Mats ut tider på fredag förmiddag. Upplägget finns för att tiderna inte ska spridas ut över båda dagarna. Mats vill inte heller lova undantag, eftersom han då blir överfallen av alla som vill ha ett.
De flesta har både gjort en merge request och bokat en tid, men någon enstaka har bokat tid utan att göra en merge request. Deadline är tisdag 3 november kl. 17.00. Hinner du inte, eller blir du underkänd vid förhöret, är nästa deadline 17 november, alltså 14 dagar senare. Eftersom examinationen är muntlig får du besked direkt. Att gå upp på omexamination påverkar inte bedömningen på något sätt, det viktiga är att du når lärandemålen.
En fråga i chatten var om det är vanligt att studenter inte klarar den muntliga examinationen. Mikael svarade i chatten: “Det händer. Sällan.”
Veckans upplägg
Section titled “Veckans upplägg”Mats ser webbkomponenter som det resten av kursen handlar om, och du har närmat dig dem sedan kursvecka 8 och 9. Veckans ordning är:
- Läs artiklarna Custom elements och Shadow DOM.
- Arbeta igenom, eller läs igenom, klickräknaren. Den speglar mycket av det som står i de två artiklarna.
- Läs artikeln En komponents filer. Där är det plötsligt många filer, och Mats ville medvetet inte börja där.
- Gör steg 8 och 9 i övningsuppgiften Tiny Tunes, som du påbörjade förra veckan.
Klickräknaren är byggd på samma sätt som Mikaels Pixeldex förra veckan, med instruktioner i sju steg och en facitfil per steg. Allt ligger i en enda fil, med lite HTML, CSS och JavaScript, så att du inte förvirras av filer i olika kataloger och verktyg som måste köras. Tanken är att du först ska förstå vad en webbkomponent är och vad den består av. När du sedan inser hur mycket kod en enda komponent blir förstår du också varför en sida med 30–40 komponenter måste organiseras som i övningsuppgifterna och examinationsuppgifterna.
En student frågade om inte mycket av detta också står i kurslitteraturen. Jo, svarade Mats, det är samma stoff, skrivet och sammanfattat på ett annat sätt. Vilken text om custom elements och shadow DOM du än tar upp borde innehålla samma saker.
Steg 0–2: ett odefinierat element, klass och uppgradering
Section titled “Steg 0–2: ett odefinierat element, klass och uppgradering”Mats började med en tom katalog med bara index.html, utan konfigurationsfiler, och öppnade filen
direkt i webbläsaren, utan webbserver. Sidan har en röd Sidans knapp och elementet
<click-counter start="5">Antal klick</click-counter>, som ingen av er känner igen eftersom det
inte är inbyggt.
Bindestrecket. Markerar du elementet i Elements ger $0.constructor.name svaret
HTMLElement, trots att elementet inte finns. Bindestrecket är det som skiljer ett custom element
från ett standardelement, eftersom inga inbyggda element har bindestreck i namnet. Ett namn utan
bindestreck, som clickcounter, blir i stället ett HTMLUnknownElement.
Klass och define(). Klassen ClickCounter ärver från HTMLElement, har en konstruktor som
anropar super() och en connectedCallback() som bara loggar. Sist kopplas namnet ihop med klassen
med customElements.define('click-counter', ClickCounter). När sidan laddas om loggas först
konstruktorn och sedan connectedCallback(), trots att du aldrig anropar metoden själv. Den anropas
av webbläsaren. Mikael frågade om det måste vara en klass. Mats svarade att det finns andra sätt i
JavaScript, men att klassyntaxen är den som används. Med customElements.get('click-counter') får
du tillbaka hela klassen, vilket Mikael tyckte påminde om querySelector, fast för custom elements.
Uppgradering. För att göra uppgraderingen synlig lade Mats till regeln
click-counter:not(:defined) { opacity: 0.3; } och fördröjde define() med två sekunder. Texten är
blek tills elementet är definierat. Mats lade dessutom till en egen setTimeout efter tre sekunder
som loggar konstruktorns namn igen, så att skillnaden syns i konsolen: före uppgraderingen
HTMLElement, efter den ClickCounter. Elementet finns i DOM:en så fort det står i HTML-koden,
men först när define() har körts och elementet har uppgraderats kan du kommunicera med det. Den
här illustrationen är ny för i år. Mats tycker att den förklarar varför du i vissa situationer
måste göra saker i en viss ordning. Du fördröjer aldrig define() i riktig kod.
Steg 3: shadow DOM och mall
Section titled “Steg 3: shadow DOM och mall”Komponenten ska visa något, och Mats ville paketera sådant som Mikael byggde med mallar förra
veckan. Mallen innehåller ett <style>-element, ett <output> och en knapp med texten +1.
Selektorn :host riktar sig till själva custom elementet, inte till något inuti det.
Limmet mellan klassen och mallen är shadow DOM. I konstruktorn kopplas en shadow root på elementet
med attachShadow(), och den fylls med template.content.cloneNode(true), en kopia av mallens
innehåll. Mallen läses in en gång och klonas sedan för varje element, vilket går mycket snabbt.
Referenser till output och button sparas i de privata fälten #output och #button. Lägg märke
till att de hämtas med this.shadowRoot.querySelector(), inte document.querySelector(). Mikael
medgav att han hade svårt att greppa just det här första gången. Det är inte många rader, men
tankesättet är helt annorlunda än på en vanlig webbplats.
Efter omladdningen dyker #shadow-root (open) upp under elementet i Elements, med style,
output och button. Mats frågade hur många knappar document.querySelectorAll('button') hittar.
Mikael gissade på en, och det stämde: knappen i shadow root är inkapslad och syns inte utifrån.
Det är också därför +1-knappen inte blir röd som sidans knapp. Sidans CSS når inte in, och
komponentens CSS når inte ut. Mikael ser fördelen på en stor webbplats med krångliga CSS-regler, där
du i komponenten får en egen liten skyddad värld. Mats lade till att det finns sätt att öppna upp
inkapslingen, till exempel för teman, men som standard fungerar det så här. Inifrån sidan kommer du
ändå åt knappen via $0.shadowRoot.querySelector('button').
Texten Antal klick försvann från sidan. Den finns kvar i DOM-trädet men visas inte, eftersom komponenten “käkar upp den”. Det åtgärdas med en slot i steg 7.
Mats påminde om att allt ligger i en fil bara i det här exemplet. I Tiny Tunes och de andra övningsuppgifterna ligger HTML, CSS, JavaScript och registreringen i olika filer, för att koden ska vara testbar och lätt att underhålla, enligt principen separation of concerns.
Steg 4: attribut
Section titled “Steg 4: attribut”Femman i start="5" är ett attribut, och värdet ska bli räknarens startvärde i stället för att
hårdkodas. Attribut är sättet att skicka data in till en komponent, precis som med inbyggda element.
Mats lade till:
- det privata fältet
#count = 0, static observedAttributes = ['start'], listan över attribut som klassen bevakar,attributeChangedCallback(name, oldValue, newValue), som avbryter om värdet inte har ändrats och annars, närnameärstart, sätterthis.#count = Number(newValue ?? 0)och anropar#render(),- den privata metoden
#render(), som skriver ut#countmedtextContent. Mats placerar privata metoder sist i klassen, skilda från de publika.
Mats jämförde det med att börja arbeta i ett ramverk. Du skriver inte all kod från första till sista satsen, utan kopplar in dig på ställen där något händer. Här är det inget ramverk, det är beteende inbyggt i HTML. Mikael noterade att det blir en hel del kod när allt skrivs från grunden, och att ramverk lägger ett lager ovanpå som gör nya komponenter enklare att skriva.
Räknaren visade 5 och följde med när Mats ändrade värdet i Elements eller med
setAttribute('start', '20'). Med removeAttribute('start') blev värdet 0 tack vare ?? 0. Med
start="hej hopp" blev värdet däremot NaN, eftersom felhanteringen är mycket avskalad. Utan
Number() hade ett klick på 5 gett 51 i stället för 6, eftersom attributvärden alltid är
strängar.
Mikael frågade hur Mats hade gjort en komponent som ökar med två eller tre. Mats svarade att han
hade lagt till ett nytt attribut med standardvärdet 1. Det attributet måste då läggas till i
observedAttributes och få en egen if-sats i attributeChangedCallback(), eftersom den ena
metoden tar emot alla bevakade attribut. Mats råd: studera hur de inbyggda elementen beter sig och
låt dina komponenter bete sig likadant, så blir de lättare att använda.
Är render inbyggd? Nej, namnet hittar du på själv, och Mats anropar metoden uttryckligen.
Metoden består än så länge av en rad. Den är ändå utbruten eftersom den ofta anropas från flera
ställen i en komponent, och render är ett vanligt namn för den typen av metod.
Steg 5: lyssnare och AbortController
Section titled “Steg 5: lyssnare och AbortController”Mikael tar inte bort sina lyssnare, och vet du vad du gör kan du resonera så, menar Mats. Han ville
ändå visa när det spelar roll. I connectedCallback() skapas en ny AbortController, och knappen
får en klicklyssnare som ökar #count och anropar #render(). Det andra argumentet,
{ signal: this.#abortController.signal }, kopplar lyssnaren till kontrollern. I
disconnectedCallback() anropas abort(), och då tas lyssnaren bort. Annars får du skriva
betydligt mer kod för att ta bort den på ett säkert sätt.
Mats tog bort elementet ur dokumentet med counter.remove() och lade in det igen med
document.body.append(counter). Räknaren kom ihåg sitt värde, eftersom objektet lever kvar så länge
du har en referens till det. Sedan kommenterade han bort abort() och gjorde om samma sak. Nu ökade
räknaren med två per klick. connectedCallback() hade körts två gånger, och den första lyssnaren
togs aldrig bort, så två lyssnare ökade värdet med ett var. Mikael sammanfattade: städa efter dig.
Mats hoppade över delen av steg 5 som visar att konstruktorn inte får sätta attribut. Den finns beskriven i README:n.
Steg 6: en publik metod
Section titled “Steg 6: en publik metod”Till sist lade Mats till den publika metoden reset(), som sätter #count till värdet av
start, eller 0 om attributet saknas, och ritar om. Från konsolen återställde
document.querySelector('click-counter').reset() räknaren från 13 till 5.
Var kommer remove() ifrån? Den har du inte skrivit själv, svarade Mats, utan den ärvs. Den
kommer från Element, via HTMLElement. När Mats skrev counter. i konsolen fick Mikael se hur
många ärvda metoder ett element har, som append och before. Mikael drog slutsatsen att du ska
vara försiktig med vad du döper dina publika metoder till, så att du inte omedvetet skuggar en
metod som redan finns.
Exemplets steg 7, där en <slot> gör att Antal klick visas igen, hann Mats inte visa. Allt han
visade finns steg för steg i README:n.
Ramverk och komponenter
Section titled “Ramverk och komponenter”Mats ser det här som nyttig baskunskap, oavsett vilket ramverk du sedan arbetar med. Det kursen inte tar upp är state, som många ramverk har. Här nöjer ni er med det tillstånd som finns i form av attribut och fält. Mikael frågade om Mats bygger liknande komponenter i Astro och Starlight, som kursens webbplats är byggd med. Det är en annan sak, svarade Mats, eftersom komponenterna där byggs när webbplatsen byggs och inte lever på klienten. Ibland skickar han dock med JavaScript.
Komponenter, där HTML, CSS och JavaScript klumpas ihop och kanske har ett tillstånd, finns överallt som ett sätt att organisera kod: i React, Vue, Angular och Mithril. Det finns också Lit, ett tunt lager ovanpå webbkomponenter som Mikael testade häromveckan. Det tas inte upp i kursen. Det som saknas än så länge är komponenter som kommunicerar med varandra, och mer om händelsehantering. Det är fokus i kursvecka 11.
Inför läsperiod två
Section titled “Inför läsperiod två”Från och med nästa vecka börjar läsperiod två och kursen 1DV026. Passen ligger kvar på tisdagar och torsdagar. Den första delen ägnas alltid åt 1DV025 och den andra åt 1DV026. Mats har byggt om 1DV026 så att den liknar de senaste veckornas upplägg här, med artiklar anpassade till 2026 och till ActionHub. Du kommer igång tidigt med en egen server och Docker. Förstör du servern i molnet kan du generera om den själv, utan att lärarna behöver laga den.
Ett bra avstamp för veckan: läs de två första artiklarna, arbeta igenom klickräknaren, läs En komponents filer och gör därefter steg 8 och 9 i Tiny Tunes.
Veckans artiklar och exempel
Section titled “Veckans artiklar och exempel”Veckans teori finns samlad i tre artiklar. Läs dem i ordning; varje artikel bygger på den föregående. Mellan den andra och den tredje bygger du själv en komponent, steg för steg.
-
Custom elements – vad som händer när ett element registreras och uppgraderas, livscykelmetoderna och hur elementet tar emot data via attribut.
-
Shadow DOM – hur komponenten får ett eget inkapslat träd, hur dess stilar och sidans stilar hålls isär och hur innehåll skickas in via slots.
-
Exemplet Klickräknaren kan du forka och följa själv. Du bygger webbkomponenten
<click-counter>i en enda HTML-fil, utan Vite och npm, i sju steg med facit. I DevTools ser du hur elementet uppgraderas, hur attribut och livscykelmetoder anropas och hur shadow DOM och slots fungerar, det vill säga allt från de två första artiklarna. -
En komponents filer – från klickräknarens enda fil till en katalog per komponent, varför klassen och registreringen ligger i olika filer och hur komponenten hittar sina egna resurser.
-
Exemplet Klickräknaren – flera filer är samma
<click-counter>, flyttad till samma filstruktur som övningsuppgifterna, med Vite. Du ser vart varje del av den enda filen tog vägen, varför klassen och registreringen ligger i olika filer, hur komponentens markup och CSS läses in med?rawoch?inlineoch hur alla räknare delar på en stilmall. Exemplet avslutas med brödkod du kan utgå från när du skapar en egen komponent.
Läsanvisningar
Section titled “Läsanvisningar”Under läsanvisningar hittar du veckans kapitel som ska läsas i kurslitteraturen. Planera upp och dela upp läsningen under veckan.
Övningar
Section titled “Övningar”Under Övningsuppgifter hittar du övningsuppgifter som det är lämpligt att du arbetar med under veckan.
Äldre inspelningar
Section titled “Äldre inspelningar”Inspelningarna från 2021 tar upp samma stoff som artiklarna, men är delvis inaktuella. Komponenterna i dem är organiserade på ett annat sätt än i kursens övningsuppgifter i dag. Artiklarna räcker, men vill du se genomgångarna i rörlig bild finns de kvar här. Sammanfattningar och presentationer till inspelningarna hittar du i samlingen av inspelningar för delkurs B.
Webbkomponenter – en introduktion till custom elements: mallar som strängar, namngivning och uppgradering. Motsvarar delar av artikeln Custom elements.
Webbkomponenter – filstruktur: en katalog per komponent, import.meta.url och npm-paket. Motsvarar artikeln En komponents filer.
Webbkomponenter – skapa custom elements: shadow DOM, livscykeln, attribut, slots och stil. Motsvarar artiklarna Custom elements och Shadow DOM.
Handledning
Section titled “Handledning”Skicka gärna in frågor på förhand via formuläret som du når här:
Examinationsuppgift A3
Section titled “Examinationsuppgift A3”Inlämning av examinationsuppgift A3 och därtill hörande muntlig examination!
Studiefrågor
Section titled “Studiefrågor”Här är ett antal frågor som du kan försöka besvara och diskutera tillsammans med din AI-assistent. Tveka inte att lyfta någon av frågorna till handledningen för att fortsätta diskussionen med lärare och studiekamrater.
- Förklara kortfattat grunden i webbkomponenter i JavaScript i webbläsaren. Vad händer när ett
element står i HTML-koden innan
customElements.define()har körts? - Vad är skillnaden mellan vad jag gör i konstruktorn och i
connectedCallback()? Varför får konstruktorn inte lägga till barn på elementet? - Hur tar en komponent emot data via attribut? Varför måste attributnamn skrivas med gemener, och varför kan en setter för ett attributvärde tyst sluta fungera?
- Måste, eller bör jag, använda shadow DOM? Vilka av sidans CSS-regler når in i en komponent, och vilka gör det inte?
- Hur fungerar
<slot>? Var finns innehållet som skickas in, i dokumentets träd eller i shadow-trädet? - Varför delas en komponent upp i
<namn>.jsochindex.js, och varför importeras HTML och CSS med?rawoch?inline? - Är webbkomponenter någon form av best practice som används ofta ute i industrin?
Här följer tips till hur du kan prioritera bland kursmaterialet för att komma igång med veckan.
- Missa inte att träna på den teoretiska delen inför examinationen.
- När du har examinationen, se till att du har koll på alla delar i din kod. Om examinatorn frågar dig “var gör du detta” så bör du kunna hitta den delen på ett smidigt sätt och samtidigt översiktligt förklara vad koden gör i sammanhanget. Dykar man ner i detaljer så bör du kunna förklara även detta. Det är ju din kod som du själv har skrivit.
- Läs veckans tre artiklar när A3 är inlämnad, och följ klickräknaren mellan den andra och den tredje. Inspelningarna är frivilliga.
- Gör steg 8–9 i övningsuppgiften Tiny Tunes. Läs komponenten
tiny-tunenoga innan du skrivertiny-time; den är byggd precis som artiklarna beskriver och är den bästa förebilden du har inför examinationsuppgift B2.


