Capstone: Reading Queue

The Course
•
4 min read

Follow one search through the system

The finished app has four modules. model.js owns book decoding and pure state transitions. controller.js owns attempts, cancellation, and stale-result protection. service.js supplies a local catalogue with controlled failure scenarios. app.js finds the HTML elements, registers listeners, and renders state. The same source modules power the preview and downloadable project.

For one successful search, trace: form submit → controller.load → loading action → search dependency → decodeBooks → loaded action → render. For invalid data, decoding throws before a loaded action exists. For cancellation, generation changes before the old attempt rejects, so the obsolete catch cannot publish an error.

The demo’s service error is a simulated HTTP-style failure, not an actual outgoing HTTP request. You can choose success, malformed data, or service error and a fast or slow response. This makes every required behavior reproducible without a third-party account, CORS setup, or internet connection.

Saved books live only in memory. Search results can be replaced without erasing the saved list. Saving again removes an existing id, so the action is a toggle. Unlike the beginner planner’s one-way Complete action, this app deliberately supports both directions.

Read one module at a time. Do not memorize the combined HTML listing. Locate a rule in model, the moment it is called in controller, and the visible consequence in app. A working screen is not enough if you cannot identify which layer owns a failure.

Browser syntax to recognize

The scenario controls are HTML select elements: each option has a value, read through the select’s .value property. The delay value is text, so the adapter converts it with Number. setAttribute("aria-busy", String(isLoading)) writes a text-valued accessibility attribute; it does not change the application status itself.

In app.js, void controller.load(query.value) starts the async action and deliberately discards its returned promise value. The controller handles expected request/decoding failures internally. An unexpected programming exception should still be investigated. window is the browser-provided page environment; its pagehide listener disposes the controller when the page is being discarded, while allowing a page retained in the browser’s back/forward cache to resume. This is the lifecycle distinction from Day 5.

Predict and trace

Write the output before running. Follow the values through each call; if your prediction differs, identify the first step that surprised you.

Predict, then run JavaScript
function toggleSaved(saved, book) {
  if (saved.some(item => item.id === book.id)) {
    return saved.filter(item => item.id !== book.id);
  }

  return [...saved, { ...book }];
}

const book = { id: "js", title: "Notes" };
const first = toggleSaved([], book);
const second = toggleSaved(first, book);

console.log(first.length);
console.log(second.length);
Check your prediction
1
0

Adding and removing are explicit value transformations. The first saved snapshot remains intact after the second toggle.

Run the finished app

Press Run webpage to preview. Each run starts fresh. Edits are not saved automatically.

Press Run webpage, search web, and save The Small Web. Then search testing. Inspect the two lists separately. All labels use textContent; a title containing angle brackets stays literal text.

Build it yourself

Exercise

Capstone: Reading Queue: practise

Write selectedLabel(saved, id), returning “Remove saved” for an existing id and “Save” otherwise. Print both cases.

Your working space JavaScript
// Derive labels from saved membership.
Hint 1: the idea

The label is a projection of state, not the saved flag itself.

Hint 2: a first step

Use some and return the matching text.

Show one solution
function selectedLabel(saved, id) {
  return saved.some(book => book.id === id) ? "Remove saved" : "Save";
}

console.log(selectedLabel([{ id: "js" }], "js"));
console.log(selectedLabel([], "js"));
Remove saved
Save

In the preview, save a book, search another topic, and confirm it remains in Saved books. Remove it there and check the label when it appears in results again.

Test a changed requirement

In the preview, save a book, search another topic, and confirm it remains in Saved books. Remove it there and check the label when it appears in results again.

Explain before moving on

Which layer knows about HTTP? Which layer knows about DOM elements? Where does validation happen?

Was this article helpful?

Your response is saved on this device.