Programmering nivå 2 med HTML, CSS och JavaScript

Kapitel 2: Bygg quizappen

I det här kapitlet bygger vi det som behövs för quizappen: HTML-strukturen, den viktigaste CSS-stylingen och grunderna för att koppla ihop allt med JavaScript.

Innehåll

Följ stegen och bygg det som quizappen behöver från start.

Kapitelöversikt

Det här kapitlet visar hur du bygger grunden till en quizapp. Fokus ligger på den HTML som måste finnas, den CSS som gör layouten tydlig och den JavaScript-logik som senare rättar svaren.

HTML CSS JavaScript formulär quizapp

2.1 Quizappens HTML-struktur

För att quizappen ska fungera måste HTML innehålla rätt delar från början: en huvudsektion, ett formulär, frågor, svarsalternativ, en knapp för att rätta och platser där resultatet kan visas.

Mål

  • förstå vilka HTML-delar som måste finnas i quizappen
  • se hur formulär och svarsalternativ byggs upp
  • veta var resultat och feedback ska skrivas ut

HTML som måste finnas med

<main class="quiz-app">
  <h1>Quiz om webben</h1>
  <form id="quizForm" class="quiz-form">
    <fieldset class="question" data-correct="B" data-explanation="HTML skapar struktur och innehåll.">
      <legend>1. Vilken tagg används för rubriker?</legend>
      <label><input type="radio" name="q1" value="A"> A. p</label>
      <label><input type="radio" name="q1" value="B"> B. h1</label>
      <label><input type="radio" name="q1" value="C"> C. div</label>
      <p class="quiz-feedback" aria-live="polite"></p>
    </fieldset>

    <button type="submit" class="quiz-submit">Rätta quizet</button>
    <p id="quiz-status" class="quiz-status" aria-live="polite"></p>
    <div id="quiz-summary" class="quiz-summary" hidden></div>
</form>
</main>
<!-- Använd färdiga skriptet från kursen: -->
<script src="../js/quiz-app.js" defer></script>

Du kan också kopiera filen js/quiz-app.js till ditt projekt och länka med <script src="main.js" defer>.

Övning

Bygg upp samma struktur i din egen index.html så att quizappen har ett formulär, en knapp, statusrad och en sammanfattning.

2.2 CSS som måste finnas med

Quizappen behöver CSS som gör formuläret lätt att läsa, frågorna tydliga och knappen enkel att hitta. Den här stilen är en bra grund att starta från.

Mål

  • förstå vilken CSS som behövs för quizappen
  • styla formulär, frågor och feedback på ett tydligt sätt
  • kunna göra sidan lättläst på både mobil och dator

CSS som måste finnas med

.quiz-app {
  max-width: 760px;
  margin: 0 auto;
  padding: 2rem;
}

.quiz-form {
  display: grid;
  gap: 1.5rem;
}

.question {
  border: 1px solid #cbd5e1;
  border-radius: 1rem;
  padding: 1rem;
  background: #fff;
}

.question label {
  display: block;
  margin: 0.4rem 0;
}

.quiz-submit {
  border: 0;
  border-radius: 999px;
  padding: 0.9rem 1.2rem;
  background: #14532d;
  color: #fff;
  font-weight: 700;
}

.quiz-status,
.quiz-summary {
  margin-top: 1rem;
}

Övning

Skapa style.css och lägg in grundstilen ovan. Ändra gärna färger och avstånd så att quizappen blir tydlig och lätt att läsa.

2.3 Frågor, svar och data-attribut

När quizappen byggs behöver varje fråga ha rätt svar och en förklaring. Det kan du lagra direkt i HTML med data-*-attribut, så att JavaScript senare kan läsa dem.

Mål

  • förstå hur data-correct och data-explanation används
  • veta varför rätt svar behöver vara tydligt markerat i HTML
  • se hur frågorna kan rättas automatiskt senare

Exempel på fråga

<fieldset class="question" data-correct="B" data-explanation="HTML skapar struktur och innehåll.">
  <legend>1. Vilken tagg används för rubriker?</legend>
  <label><input type="radio" name="q1" value="A"> A. p</label>
  <label><input type="radio" name="q1" value="B"> B. h1</label>
  <label><input type="radio" name="q1" value="C"> C. div</label>
  <p class="quiz-feedback" aria-live="polite"></p>
</fieldset>

Övning

Lägg in minst en fråga i din quizapp och skriv rätt svar i ett data-correct-attribut. Skriv också en kort förklaring i data-explanation.

2.4 Koppla JavaScript till formuläret

När HTML och CSS finns på plats kan JavaScript kopplas in för att läsa in svar, jämföra dem och visa resultatet på sidan.

Mål

  • förstå hur formuläret hittas i JavaScript
  • veta att en submit-händelse används för att rätta quizet
  • kunna visa status och sammanfattning på sidan

Vad koden kommer att behöva göra

  • hitta formuläret med getElementById
  • samla alla frågor med querySelectorAll
  • visa om alla frågor är besvarade
  • skriva rätt/fel och förklaringar i quizens feedbackfält

Övning

Förbered din main.js så att den hittar #quizForm, #quiz-status och #quiz-summary. Det är de element som behövs för att rätta quizet.

JavaScript — komplett exempel (lägg i main.js)

Här är den kompletta koden samlad i ett stycke — kopiera rakt av till din main.js om du vill ha en komplett fil direkt.

document.addEventListener('DOMContentLoaded', () => {
    const form = document.getElementById('quizForm');
    const status = document.getElementById('quiz-status');
    const summary = document.getElementById('quiz-summary');

    if (!form) return;

    form.addEventListener('submit', (e) => {
        e.preventDefault();

        const questions = Array.from(form.querySelectorAll('.question'));
        const unanswered = [];
        let correctCount = 0;

        questions.forEach((q, i) => {
            const nameInput = q.querySelector('input[type="radio"]');
            const name = nameInput ? nameInput.name : null;
            const selected = name ? q.querySelector(`input[name="${name}"]:checked`) : null;
            const correct = q.dataset.correct;
            const feedback = q.querySelector('.quiz-feedback');

            if (!selected) {
                unanswered.push(i + 1);
                if (feedback) feedback.textContent = 'Besvara frågan.';
                return;
            }

            const isCorrect = selected.value === correct;
            if (isCorrect) {
                correctCount += 1;
                if (feedback) feedback.textContent = 'Rätt! ' + (q.dataset.explanation || '');
            } else {
                if (feedback) feedback.textContent = `Fel. Rätt svar är ${correct}. ` + (q.dataset.explanation || '');
            }
        });

        if (unanswered.length) {
            status.textContent = 'Alla frågor måste besvaras. Saknade: ' + unanswered.join(', ');
            summary.hidden = true;
            return;
        }

        status.textContent = `Du fick ${correctCount} av ${questions.length} rätt.`;
        summary.hidden = false;
        summary.innerHTML = `

Resultat

Rätt: ${correctCount} / ${questions.length}

`; }); });

JavaScript — uppdelat och förklarat (lägg i main.js)

Nedanför följer samma funktionalitet uppdelad i mindre kodstycken. Efter varje kodstycke finns en kort förklaring, varför det är viktigt enligt lärandeforskning, och ett litet gör själv-förslag.

1) Vänta på att DOM är färdig

document.addEventListener('DOMContentLoaded', () => {
  // allt som berör DOM:en går här
});

Varför: säkerställer att elementen finns innan vi försöker hämta dem.
Gör själv: ta bort lyssnaren och se vad som händer i konsolen.

2) Hämta de element vi behöver

const form = document.getElementById('quizForm');
const status = document.getElementById('quiz-status');
const summary = document.getElementById('quiz-summary');

Varför: vi måste ha referenser för att läsa/skriva i DOM.
Gör själv: logga variablerna med console.log.

3) Avbryt formens standardbeteende

form.addEventListener('submit', (e) => {
  e.preventDefault();
  // rättningslogik här
});

Varför: normalt skickar ett formulär och laddar om sidan — det vill vi undvika så vi kan visa resultat direkt.
Gör själv: kommentera ut e.preventDefault() och återställ efter test.

4) Samla alla frågor till en array

const questions = Array.from(form.querySelectorAll('.question'));
let correctCount = 0;
const unanswered = [];

Varför: form.querySelectorAll('.question') returnerar en NodeList — en samling DOM-noder. En NodeList har vissa metoder (t.ex. forEach) men saknar andra vanliga arraymetoder som map, filter och reduce. Därför använder vi Array.from(...) för att skapa en riktig JavaScript Array som ger full tillgång till arraymetoder.
Gör själv: i webbläsarkonsolen prova:

const nodes = form.querySelectorAll('.question');
console.log(nodes instanceof NodeList); // true
const questions = Array.from(nodes);
console.log(Array.isArray(questions)); // true
Skriv sedan ut questions.length till status före rättningen för att verifiera antal frågor.

5) Iterera och kontrollera svar

questions.forEach((q, i) => {
  const nameInput = q.querySelector('input[type="radio"]');
  const name = nameInput ? nameInput.name : null;
  const selected = name ? q.querySelector(`input[name="${name}"]:checked`) : null;
  const correct = q.dataset.correct;
  const feedback = q.querySelector('.quiz-feedback');

  if (!selected) {
    unanswered.push(i + 1);
    if (feedback) feedback.textContent = 'Besvara frågan.';
    return;
  }

  const isCorrect = selected.value === correct;
  if (isCorrect) {
    correctCount += 1;
    if (feedback) feedback.textContent = 'Rätt! ' + (q.dataset.explanation || '');
  } else {
    if (feedback) feedback.textContent = `Fel. Rätt svar är ${correct}. ` + (q.dataset.explanation || '');
  }
});

Varför: här händer själva rättningen och feedbacken — omedelbar feedback stärker minnet.
Gör själv: ändra feedback-meddelandena och se hur det påverkar elevupplevelsen.

6) Hantera obesvarade och visa sammanfattning

if (unanswered.length) {
  status.textContent = 'Alla frågor måste besvaras. Saknade: ' + unanswered.join(', ');
  summary.hidden = true;
  return;
}

status.textContent = `Du fick ${correctCount} av ${questions.length} rätt.`;
summary.hidden = false;
summary.innerHTML = `

Resultat

Rätt: ${correctCount} / ${questions.length}

`;

Varför: tydlig summering och felhantering ger bättre lärande och mindre frustration.
Gör själv: lägg till procent och rekommendationer beroende på poäng.

7) Tillgänglighet och förbättring

Se till att .quiz-feedback använder aria-live så skärmläsare meddelar uppdateringar. Fundera också på att extrahera delar av koden till små funktioner (t.ex. checkQuestion(q)) — det underlättar repetition och testning.

8) Förslag på tre korta övningar (Gör själv)

  1. Markera varje fråga med en klass .correct eller .incorrect efter rättning och ge olika bakgrundsfärg.
  2. Lägg till en knapp "Rensa svar" som nollställer formuläret, feedback och sammanfattning.
  3. Spara resultatet i localStorage och visa tidigare bästa poäng i sidhuvudet.

Vill du att jag lägger in en färdig övningssektion med dessa tre uppgifter i kapiteltexten? Svara med ja eller nej.

2.5 Sammanfattning

För att bygga quizappen behöver du rätt HTML-struktur, tydlig CSS och JavaScript som kopplar ihop formuläret med resultatet. När grunden finns på plats kan du lägga till fler frågor och förbättra upplevelsen steg för steg.

  • HTML ger quizappen struktur.
  • CSS gör formuläret läsbart och tydligt.
  • JavaScript rättar svaren och visar feedback.
  • Data-attribut gör att rätt svar och förklaringar kan lagras i HTML.

Quiz

Välj ett svar per fråga. Quizet visar resultat och förklaringar när alla frågor är besvarade.

1. Vilken del behövs för att samla svaren i quizappen?

2. Vad behövs CSS till i quizappen?

3. Vad är poängen med data-correct i en fråga?

4. Vad ska statusfältet och sammanfattningen användas till?