LEARN TO CODE | JAVASCRIPT | 03 INTERACTIVE PAGES

03 Interactive pages: challenges

Build an interactive page and demonstrate that input checks, rendered output and saved state stay consistent.

START HERE | BUILD IN VS CODE

Use one project folder for each independent exercise. Create the required HTML controls before adding their JavaScript listeners, and use a deferred external script. Save and inspect both the page and Console.

Download the complete examples and capstone . The earlier lesson folders contain the exact DOM and event IDs referred to in these tasks.

For dependable storage tests, use a local HTTP preview server rather than opening a file URL. If your VS Code setup already has a teacher-approved preview extension, open the folder and use its preview command. With Live Server installed, right-click index.html and choose Open with Live Server. Keep the same address and port for later tests.

No Node installation is needed for the lesson programs. Direct file opening works for the earlier DOM/event examples, but file-URL storage behaviour varies. The storage example should be tested over HTTP.

Attempt each challenge before opening guidance. Record input/action, expected result and actual result. Completion ticks last while this page is open.

CHALLENGE 01 | SELECT THE RIGHT ELEMENT

Create a paragraph with ID status and change its text from Waiting to Ready when the script runs.

Need a hint?

Use a deferred external script and an exact selector.

Reveal guidance
const status = document.querySelector("#status");
if (status !== null) {
  status.textContent = "Ready";
}

Your HTML must contain <p id="status">Waiting</p>. Confirm the source file is unchanged even though the displayed DOM text changes.

CHALLENGE 02 | BUILD A LIST FROM DATA

Render Robotics, Art and Coding as three li elements. Make rerendering replace old items rather than duplicate them.

Need a hint?

Use createElement inside the loop and clear the container first.

Reveal guidance
const clubs = ["Robotics", "Art", "Coding"];
const list = document.querySelector("#club-list");
if (list !== null) {
  list.replaceChildren();
  for (const name of clubs) {
    const item = document.createElement("li");
    item.textContent = name;
    list.append(item);
  }
}

Add <ul id="club-list"></ul> to your page. Test an empty array as well.

CHALLENGE 03 | KEEP TEXT AS TEXT

Render a name containing the literal characters <b>Chess</b> . Prove there is no b child element.

Need a hint?

Use textContent and inspect the DOM.

Reveal guidance
const output = document.querySelector("#status");
if (output !== null) {
  output.textContent = "<b>Chess</b>";
  console.log(output.querySelector("b") === null);
}

The visitor sees the angle-bracket text and the console logs true. Do not replace textContent with innerHTML.

CHALLENGE 04 | REGISTER, DO NOT CALL

Connect a button to a named function that logs Activated only when the button is activated.

Need a hint?

Pass the function reference as the second argument.

Reveal guidance
const button = document.querySelector("#activate");
function activate() {
  console.log("Activated");
}
button.addEventListener("click", activate);

Supply <button id="activate" type="button">Activate</button>. Do not write activate() in addEventListener.

CHALLENGE 05 | RESET THE STATE

Extend the 03.2 counter with Subtract one, never allowing a negative count. Verify Add, Add, Reset, Subtract.

Need a hint?

Update count, then renderCount.

Reveal guidance
const subtractButton = document.querySelector("#event-subtract");
subtractButton.addEventListener("click", () => {
  if (count > 0) {
    count -= 1;
  }
  renderCount();
});

Add the matching type=button element to the complete counter page. The test ends at zero, not -1.

CHALLENGE 06 | RESPOND TO INPUT

Use the 03.2 greeting to display the current field value after typing and pasting. A spaces-only input should show the prompt.

Need a hint?

Use the input event, trim and textContent.

Reveal guidance

Use the complete live-greeting listener from 03.2. Test text input, paste, deletion and only spaces. A keydown-only solution misses some ways a field can change.

CHALLENGE 07 | TOGGLE HELP ACCESSIBLY

Create a help button whose label, aria-expanded and controlled panel visibility agree. Test with keyboard activation.

Need a hint?

Use a real button and keep the hidden boolean as the state.

Reveal guidance

Use the 03.2 help control. Hidden panel means aria-expanded=false and Show help; visible panel means true and Hide help. The controls should still work with Tab and Enter or Space.

CHALLENGE 08 | VALIDATE A COST PREVIEW

Test the complete 03.3 form with quantities blank, 0, 1, 6, 7 and 1.5. Record expected and actual outcomes.

Need a hint?

Use valid fictional name/email details so each test isolates quantity.

Reveal guidance

Only 1 and 6 succeed, producing 6 and 36 credits. Rejected input should replace an earlier success message. Check blank input before Number conversion and enforce an integer range.

CHALLENGE 09 | EXPLAIN THE BROWSER/SERVER BOUNDARY

Explain why a successful preview does not prove a booking or an email was created, and why the server must revalidate real requests.

Need a hint?

Inspect which operations the program actually performs.

Reveal guidance

The program changes DOM text only. There is no network booking/email operation. Browser checks can be changed or bypassed; a real server must verify received values, permission and availability itself.

CHALLENGE 10 | SAVE AND RESTORE

Save HTML and 35 minutes in the complete 03.4 example, reload on the same origin and confirm restoration.

Need a hint?

Use a stable HTTP address and the app-specific key.

Reveal guidance

The stored value is JSON text. Loading parses it, validates its version/subject/integer duration and fills the controls. A different host or port may have a different storage area.

CHALLENGE 11 | FAIL SAFELY AND FORGET PRECISELY

Test malformed JSON, a valid scalar such as 42 and a wrong-type duration. Then forget this preference without deleting an unrelated test key.

Need a hint?

Edit only this app’s key in your practice origin’s storage panel.

Reveal guidance

All three unsuitable records lead to defaults and controlled guidance. Forget calls removeItem for this feature’s key only. A blocked or failed save must say Not saved rather than claim success.

CHALLENGE 12 | CAPSTONE: A SAVED WORKSHOP CHOICE

Build a workshop preview with Robotics at 6 credits and Web design at 4 credits per ticket. Allow 1 to 6 whole tickets, preview the cost and save only the chosen workshop ID and quantity. Restore valid saved data and provide a targeted Forget action.

Need a hint?

Use the capstone folder in the download as guidance after attempting your own design.

Reveal guidance

The reference uses an array of workshop objects, selected form elements, submit/click listeners, a numeric calculation and a versioned JSON record. some checks whether at least one workshop ID matches; find retrieves that record for rendering.

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>A saved workshop-cost choice</title>
  <link rel="stylesheet" href="style.css">
  <script src="script.js" defer></script>
</head>
<body>
  <main>
    <h1>A saved workshop-cost choice</h1>
<p>Practice preview only: no booking, payment or personal details. Save remembers only the selected workshop and quantity on this browser.</p>
<form id="plan-form" novalidate>
  <p><label for="plan-workshop">Workshop : </label> <select id="plan-workshop" name="workshop"><option value="robotics">Robotics</option><option value="web">Web design</option></select></p>
  <p><label for="plan-quantity">Tickets, 1 to 6 : </label> <input id="plan-quantity" name="quantity" type="number" min="1" max="6" step="1" value="1" required></p>
  <button type="submit">Preview cost</button>
  <button id="plan-save" type="button">Save this choice</button>
  <button id="plan-forget" type="button">Forget this choice</button>
</form>
<p id="plan-result" role="status" aria-live="polite">Choose a workshop and preview its cost.</p>
<p id="plan-status" role="status" aria-live="polite"></p>
  </main>
</body>
</html>

script.js

const workshops = [
  { id: "robotics", name: "Robotics", price: 6 },
  { id: "web", name: "Web design", price: 4 }
];
const planKey = "csc-js03-workshop-choice-v1";
const planForm = document.querySelector("#plan-form");
const workshopInput = document.querySelector("#plan-workshop");
const quantityInput = document.querySelector("#plan-quantity");
const result = document.querySelector("#plan-result");
const status = document.querySelector("#plan-status");

function validChoice(value) {
  return value !== null && typeof value === "object" && !Array.isArray(value)
    && value.version === 1
    && workshops.some((workshop) => workshop.id === value.workshopId)
    && Number.isInteger(value.quantity) && value.quantity >= 1 && value.quantity <= 6;
}
function readChoice() {
  const text = quantityInput.value.trim();
  const choice = { version: 1, workshopId: workshopInput.value, quantity: Number(text) };
  return text !== "" && validChoice(choice) ? choice : null;
}
function renderChoice(choice) {
  const workshop = workshops.find((item) => item.id === choice.workshopId);
  result.textContent = `${workshop.name}: ${choice.quantity} ticket(s), ${choice.quantity * workshop.price} credits. Preview only.`;
}
function showDefaults() {
  workshopInput.value = "robotics";
  quantityInput.value = "1";
  result.textContent = "Choose a workshop and preview its cost.";
}
planForm.addEventListener("input", () => {
  result.textContent = "Choice changed. Preview again for the current cost.";
  status.textContent = "Changes are not saved until you select Save this choice.";
});
planForm.addEventListener("submit", (event) => {
  event.preventDefault();
  const choice = readChoice();
  if (choice === null || !planForm.reportValidity()) {
    result.textContent = "Choose an available workshop and a whole quantity from 1 to 6.";
    return;
  }
  renderChoice(choice);
});
document.querySelector("#plan-save").addEventListener("click", () => {
  const choice = readChoice();
  if (choice === null || !planForm.reportValidity()) {
    result.textContent = "Choose an available workshop and a whole quantity from 1 to 6.";
    status.textContent = "Not saved: correct the input first.";
    return;
  }
  renderChoice(choice);
  try {
    localStorage.setItem(planKey, JSON.stringify(choice));
    status.textContent = "Choice saved in this browser. No booking was made.";
  } catch (error) {
    status.textContent = "Not saved: browser storage is unavailable or full.";
  }
});
document.querySelector("#plan-forget").addEventListener("click", () => {
  try {
    localStorage.removeItem(planKey);
    showDefaults();
    status.textContent = "This saved choice was removed.";
  } catch (error) {
    status.textContent = "The saved choice could not be removed.";
  }
});
showDefaults();
try {
  const text = localStorage.getItem(planKey);
  if (text !== null) {
    const choice = JSON.parse(text);
    if (validChoice(choice)) {
      workshopInput.value = choice.workshopId;
      quantityInput.value = String(choice.quantity);
      renderChoice(choice);
      status.textContent = "Saved choice restored. This is still a preview.";
    } else {
      status.textContent = "Saved choice is invalid. Defaults shown; Save can replace it.";
    }
  }
} catch (error) {
  status.textContent = "Saved choice could not be loaded. Defaults are available.";
}

Test Web design × 3 = 12 credits, Robotics × 6 = 36, blank/7/fractional rejection, reload restore, invalid stored data and blocked storage. No names or emails are stored and no reservation is created. This small example does not synchronise changes across multiple open tabs.

FINISH | REVIEW THE WHOLE FLOW

Explain how data travels from a control’s value through validation to a calculation, then into textContent. Explain the separate JSON save/load route and why loaded records are checked again.

Next, asynchronous code and fetch will connect browser pages to external data. Keep local previews, browser persistence and actual server operations distinct as your projects grow.

KEEP LEARNING

Previous page 04.1 Promises, async and await

Download Section 03 example programs

Further reading: The DOM · Event listeners · Form validation · Browser storage