A useful app is more than a happy-path demonstration. Our finished planner lets a person add a topic and duration, rejects invalid input without adding a record, marks sessions complete, and reports planned and completed minutes. It deliberately does not save data across reloads.
A form’s submit event covers both clicking its submit button and pressing Enter in an input. event.preventDefault() prevents the default page navigation. An input’s value is text, even for a numeric field. Validate it before putting it into your plan. HTML input constraints help, but JavaScript validation makes the application’s rules explicit too.
The app below uses functions you have practiced, plus a few DOM tools. document.createElement creates an element. append adds it to a parent. replaceChildren clears the list before rebuilding it from state. Each row’s click callback remembers the session index from the render call that created it. When a person clicks later, that callback uses the remembered index to select the record. Keeping access to such surrounding local variables is called a closure. Each callback remembers its own row’s index. The list is never reordered here; larger applications should use stable record IDs.
Read the remaining browser tools
<ul>is an unordered list and<li>is one list item.<span>groups text within a row.sessions.forEach((session, index) => { ... })calls the callback once per record. The second parameter is its position, just as with map. Here we create visible rows rather than a returned array.document.createElement("li")creates a list item in memory.list.append(row)places it on the visible page. Creating an element alone does not display it.list.replaceChildren()removes the previous rows. render then builds the current rows from sessions so old rows do not accumulate.() => { ... }is an arrow callback with no parameters. A click handler can ignore the event object when it does not need it.form.reset()clears the form fields to their initial values. It does not erase sessions.topicInput.focus()moves keyboard focus to the topic field for the next entry.
In Day 6 we updated records by producing new arrays. The full app follows that convention: assign the revised array to sessions, then render it. A Complete button is a one-way action here; after completing its row, that button disappears. Adding an undo action would be a separate extension.
Predict before running
Before trying the complete webpage, check its remaining-time calculation. Why must incomplete sessions still count toward planned minutes?
const sessions = [
{ minutes: 25, done: true },
{ minutes: 20, done: false }
];
let remaining = 0;
for (const session of sessions) {
if (!session.done) {
remaining = remaining + session.minutes;
}
}
console.log(remaining);Check your prediction
20Trace each statement in order. If your prediction differed, describe the first place your mental model and the result diverged before trying again.
Verify the whole user journey
Read the app in this order: initial state, validation, state update, render, then event wiring. Trace one successful submission through those stages. Trace a rejected submission separately and confirm it does not add a record. Test empty input, 1 and 240 minutes, 0 and 241, a decimal, and a topic containing angle brackets. Complete one session and confirm that its button disappears while the other incomplete sessions keep their buttons. This version does not offer an undo action.
const sessions = [];
function add(topic, minutes) {
const cleaned = topic.trim();
if (
cleaned === "" ||
cleaned.length > 40 ||
!Number.isInteger(minutes) ||
minutes < 1 ||
minutes > 240
) {
return false;
}
sessions.push({ topic: cleaned, minutes });
return true;
}
console.log(add(" ", 20));
console.log(sessions.length);
console.log(add("Loops", 20));
console.log(sessions.length);Check your prediction
false
0
true
1This smaller model applies the app’s topic and duration rules without any HTML. It mutates its local list with push; the full app instead assigns a new array. Both must reject invalid input before changing the list.
Your turn
Capstone: Study Planner: practise
Write countPending(sessions). It should return 1 for the example below and 0 for an empty plan. This is a new summary you can add to the webpage.
const sessions = [{ done: true }, { done: false }];
// Write countPending and print its result.Hint 1: the idea
Count records, not minutes.
Hint 2: a first step
Initialize a count to zero and add one only for !session.done.
Show one solution
function countPending(sessions) {
let count = 0;
for (const session of sessions) {
if (!session.done) {
count = count + 1;
}
}
return count;
}
console.log(countPending([{ done: true }, { done: false }]));
console.log(countPending([]));1
0Use the full app below as a reference you can explain, not a block to memorize. Make your own pending-count feature before opening a new project.
What you should be able to explain
- Can you trace submission without skipping a function?
- Does invalid input leave state intact?
- Can you explain the app after closing its source?
Try the webpage
Press Run webpage to preview. Each run starts fresh. Edits are not saved automatically.
A manual acceptance check
- Start with an empty plan: all totals should be zero.
- Add Loops for 25 minutes and Arrays for 20: planned is 45, completed is 0.
- Complete Loops: completed is 25, remaining is 20.
- Try an empty topic, zero, 1.5, and 241 minutes: each must show feedback without adding a row.
- Try 1 and 240: both boundaries are accepted.
- Enter a topic containing
<b>hello</b>: it must display as text, not bold HTML. - Submit with Enter and operate the buttons using a keyboard.
The conditional expression condition ? valueIfTrue : valueIfFalse selects the done label, using the same syntax introduced with the planner core on Day 6. To trace a row, follow its creation, its label, its button callback, and its append to the list.
Make it yours
Add a pending-session count using your exercise function. Then add a Clear plan button. Decide whether clearing should ask for confirmation, and explain your choice. Test empty, one-session, and all-completed plans.
Take it with you
Use Download HTML, then open the downloaded file in a browser. It needs no server or installation. Your edited source is downloaded; the current preview’s in-memory sessions are not. No information survives a reload unless you add storage. Do not put private information into practice examples.
Where next
Keep building a small app you actually want. Learn HTML and CSS more deeply for websites, then modules and asynchronous JavaScript for larger applications. TypeScript is a later option once you understand JavaScript behavior; its course is planned, not available yet. Try the independent brief next before deciding you need more syntax.
Completion is saved in this browser only. It does not measure mastery.