Fra input til gemte data
Web Dev, efterår 2026
aria-pressed="false"
state forsvinder
Database via API
Database via API
Database via API
Hent data fra Mockapi.io og render produktkort.
Øvelse
Opskrift:
GET, POST, PUT, PATCH, DELETE m.fl.
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json()
}function getProducts() {fetch(endpoint)asyncawaitGET er default
const response =GET, POST, PUT, PATCH, DELETE m.fl.
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json()
}
function getProducts() {
fetch(endpoint)
async
await
asynkront
const response =
headers
async function getProducts() {
const response = await fetch(endpoint, {
method: "get",
headers: {
"Content-Type": "application/json"
}
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json()
}api key mm.
post, delete, put
render m/ createElement()
async function renderData() {
const data = await getProducts()
const fragment = document.createDocumentFragment()
data.forEach(entry => {
fragment.append(createEntryElement(entry))
})
entries.replaceChildren(fragment)
}function createEntryElement(entry) {
const article = document.createElement("article")
const heading = document.createElement("h3")
const body = document.createElement("p")
article.className = "entry"
heading.textContent = entry.name
body.textContent = entry.description
article.append(heading, body)
return article
}Vi kommer til at bruge <template>
cloneNode()
<template>
<article class="entry">
<h2>Overskrift-eksempel</h2>
<!-- ... -->
</article>
</template>Bliver ikke renderet
cloneNode()
eksempel
async function renderProducts() {
const products = await getProducts();
list.replaceChildren(...products.map(createProduct));
}
renderProducts();function createProduct(product) {
const clone = template.content.cloneNode(true);
const article = clone.querySelector(".entry");
article.querySelector("h3").textContent = product.name;
article.querySelector("p").textContent = product.description;
return clone;
}replaceChildren(elem1, elem2)Brug PUT-metoden til at opdatere favorite-status og afspejl denne i aria-pressed.
Øvelse
Opskrift:
PUT-metoden
async function toggleFavorite(id, isFavorite) {
const response = await fetch(`${endpoint}/${id}`, {
method: "put",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ favorite: isFavorite })
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json()
}PUT-metoden
async function handleFavoriteClick(event) {
// ...
try {
const isHearted = button.ariaPressed === "true";
const product = await toggleFavorite(id, !isHearted);
button.ariaPressed = product.favorite;
} catch (error) {
console.error("Error toggling favorite:", error);
}
}læs status fra API
PUT-metoden
async function handleFavoriteClick(event) {
// ...
try {
const isHearted = button.ariaPressed === "true";
const product = await toggleFavorite(id, !isHearted);
button.ariaPressed = product.favorite;
} catch (error) {
console.error("Error toggling favorite:", error);
}
}Opdater UI
.favourite {
/*...*/
.heart-filled,
.heart-empty {
display: none;
}
&[aria-pressed="true"] {
background: var(--ink);
color: var(--accent);
}
&[aria-pressed="true"] .heart-filled,
&[aria-pressed="false"] .heart-empty {
display: block;
}
}Struktur
Login
Search
Facebook post
Checkout
<label><label>form lingo
<form>
<label>Label</label>
<input>
<p>Error message</p>
<button>Submit button</button>
</form><input type=" ">text
number
radio
checkbox
tel
date
<input type=" ">text
number
radio
checkbox
tel
date
Enkelt linje tekstfelt (standard)
<input type=" ">text
number
radio
checkbox
tel
date
Numre/cifre
<input type=" ">text
number
radio
checkbox
tel
date
<input type=" ">text
number
radio
checkbox
tel
date
Radioknap
<input type=" ">text
number
radio
checkbox
tel
date
Afkrydsningsfelt
<input type=" ">text
number
radio
checkbox
tel
date
Telefonnummer
<input type=" ">text
number
radio
checkbox
tel
date
Dato
<form>
<label for="comment">Kommentarer</label>
<textarea name="comment" id="comment"> </textarea>
</form>rows, cols, resize etc.
Ligesom tekst-input – bare med flere linjer
<form>
<label for="pet-select">Yndlingsdyr</label>
<select id="pet-select" name="pet">
<option value="">--Vælg et dyr--</option>
<option value="dog">Hund</option>
<option value="cat">Kat</option>
</select>
</form>Dette gør feltet tilgængeligt for skærmlæsere og giver brugeren en større klik-flade, da man kan klikke på <label> for at fokusere input-feltet.
Label ↔ Input
<input type="checkbox" id="my-id">
<label for="my-id">Whole row</label>Når flere inputs hører sammen (fx radioknapper eller checkboxes), skal de pakkes i et <fieldset> med en <legend>.
Skærmlæsere forstår, at felterne hører til samme overskift.
<form>
<fieldset>
<legend>
<h3>Choose your favorite superhero</h3>
</legend>
<label for="spiderman">
<input type="radio" id="spiderman">
Spider-man
</label>
...
</fieldset>
<button>Submit</button>
</form>
bruges til at identificere felterne i formularindsendelser
nameNogle input-typer deler også det samme navn, hvilket er vigtigt at bemærke, fordi det tillader den samme dataværdi at have flere værdier.
<div>
<label>Password</label>
<p>Password must be at least 8 characters</p>
<input />
<p>Please enter a value</p>
</div>Ligesom med label/input, så skal fejlbeskeden og input-feltet kobles sammen via aria-errormessage + unikt id. Og i tilfældet af fejl, så skal input også have aria-invalid="true".
<div>
<label>Password</label>
<p>Password must be at least 8 characters</p>
<input aria-errormessage="password-error" aria-invalid="true" />
<p id="password-error">Please enter a value</p>
</div>sættes via JS
Instruktioner knyttes til input-feltet via aria-describedby + p.id
<div>
<label>Password</label>
<p id="password-instruction">Password must be at least 8 characters</p>
<input aria-describedby="password-instruction" />
<p id="password-error">Please enter a value</p>
</div>Et levn fra en svunden tid
147px (Chrome)
148px (Safari)
189px (Firefox)
Vent, hvad..?
Vent, hvad..?
Elementer er inline
<form>
<div class="form-group">
<label for="name">Fulde navn</label>
<input type="text" id="name" name="full-name" />
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" />
</div>
<button>Send formular</button>
</form>Grupper felter
<form>
<div class="form-group">
<label for="name">Fulde navn</label>
<input type="text" id="name" name="full-name" />
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" />
</div>
<button>Send formular</button>
</form>Grupper felter
<form>
<div class="form-group">
<label for="name">Fulde navn</label>
<input type="text" id="name" name="full-name" />
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" />
</div>
<button>Send formular</button>
</form>gapGrupper felter
form {
display: grid;
gap: 2rem;
}
.form-group {
display: grid;
gap: 3px;
}<form>
<div class="form-group">
<label for="name">Fulde navn</label>
<input type="text" id="name" name="full-name" />
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" />
</div>
<button>Send formular</button>
</form>Brug Grid
form {
display: grid;
gap: 2rem;
}
.form-group {
display: grid;
gap: 3px;
}<form>
<div class="form-group">
<label for="name">Fulde navn</label>
<input type="text" id="name" name="full-name" />
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" />
</div>
<button>Send formular</button>
</form>Brug Grid
Form validation
Vis fejlmeddelelser tæt på relevante felter og på det rigtige tidspunkt
Hvad er der galt?
Skjul fejlmeddelelser før interaktion
.error-message {
display: none;
}"Hvis feltet ikke opfylder kriteriet, vis da fejlbesked"
"Hvis feltet ikke opfylder kriteriet, vis da fejlbesked"
*
*
"Feltet skal udfyldes"
<div>
<label>First name</label>
<input required />
<p>Please enter a value</p>
</div>"Feltet skal udfyldes, og der skal mindst være 8 tegn"
<div>
<label>Password</label>
<input minlength="8" required />
<p>Please enter a value</p>
</div>:user-valid
:user-invalid
:required
.form-group:has(:required) {
label::after {
content: " *";
color: red;
}
}"Hvis .form-group har et obligatorisk felt,
så tilføj en stjerne til label"
.form-group:has(:user-invalid) {
input {
outline: 3px solid #f005;
border-color: darkred;
}
}"Hvis .form-group har et ugyldigt felt,
så tilføj rød kant omkring input-feltet"
.form-group:has(:user-invalid) {
input {
outline: 3px solid #f005;
border-color: darkred;
}
.error-message {
display: block;
}
}"Hvis .form-group har et ugyldigt felt,
så vis fejlmeddelelsen"
HTML -> CSS -> JavaScript -> Framework
Byg en lille workshop-tilmelding med POST.
Hent tilmeldinger fra API'et med GET, og vis dem på samme side.
Øvelse
Opskrift:
const form = document.querySelector("form");
form.addEventListener( , ...);'invalid''submit'const form = document.querySelector("form");
form.addEventListener( , ...);'invalid''submit'const form = document.querySelector("form");
form.addEventListener( , ...);'invalid''submit'Lyt efter, om der er felter, der er ugyldige
'focusout''input'const form = document.querySelector("form");
form.addEventListener( , ...);'invalid''submit'Lyt efter mens der tastes
'focusout''input'const form = document.querySelector("form");
form.addEventListener( , ...);'invalid''submit''focusout'Lyt efter, når du forlader et felt
(eller 'blur')
'input'function handleInvalid(event) {
event.preventDefault();
form.querySelector(":user-invalid").focus();
event.target.ariaInvalid = "true";
}
form.addEventListener('invalid', handleInvalid, true);Fjern browser-popup og tilføj aria-invalid
const form = document.querySelector("form");
function handleSubmit(event) {
}
form.addEventListener("submit", handleSubmit);const form = document.querySelector("form");
function handleSubmit(event) {
// 1. undgå refresh
// 2. samle data op
// 3. sende data
// 4. nustil formular
// 5. opdater UI
}
form.addEventListener("submit", handleSubmit);Hvad skal der ske, når jeg submitter?
const form = document.querySelector("form");
function handleSubmit(event) {
// 1. undgå refresh
event.preventDefault();
// 2. samle data op
// 3. sende data
// 4. nustil formular
// 5. opdater UI
}
form.addEventListener("submit", handleSubmit);Undlad browser refresh
const form = document.querySelector("form");
function handleSubmit(event) {
// 1. undgå refresh
event.preventDefault();
// 2. samle data op
// 3. sende data
// 4. nustil formular
form.reset();
// 5. opdater UI
}
form.addEventListener("submit", handleSubmit);Nulstil formularen v/ submit
const form = document.querySelector("form");
function handleSubmit(event) {
const formData = new FormData(form);
}FormData-syntaks
const form = document.querySelector("form");
function handleSubmit(event) {
const formData = new FormData(form);
console.log( formData.get("firstname") )
}.get()
Konsollen udskriver nu strengen "Peter"
<input name="firstname" />function handleSubmit(event) {
// Hent værdier fra form
const formData = new FormData(form);
const data = {
firstname: formData.get("firstname"),
lastname: formData.get("lastname"),
email: formData.get("email")
};
}getAll() hvis flere
function handleSubmit(event) {
// Hent værdier fra form
const formData = new FormData(form);
const data = Object.fromEntries(formData);
/*const data = {
firstname: formData.get("firstname"),
lastname: formData.get("lastname"),
email: formData.get("email")
};*/
}skal matche API-feltnavn
async function handleSubmit(event) {
const formData = new FormData(form);
const data = Object.fromEntries(formData);
const { firstname } = await addEntry(data);
statusMessage.textContent = `${firstname} was added`;
}Husk try/catch
async function handleDelete(event) {
// ...
await deleteEntry(button.dataset.id);
await renderEntries();
list.focus();
}<ul
class="entries"
tabindex="-1"
aria-labelledby="entries-heading">
</ul>async function handleDelete(event) {
// ...
await deleteEntry(button.dataset.id);
await renderEntries();
list.focus();
}<ul
class="entries"
tabindex="-1"
aria-labelledby="entries-heading">
</ul>Byg en lille workshop-tilmelding med POST.
Hent tilmeldinger fra API'et med GET, og vis dem på samme side.
Øvelse
Opskrift: