JavaScriptLesson 10 of 10Jump to practice

Events and a to-do app

Run code when people click, type and submit forms, then put it all together in a small to-do app.

An event is something that happens on the page: a click, a key press, a form being sent. You tell the browser which function to run when it happens.

element.addEventListener("click", handleClick);

Note: pass handleClick without brackets. You’re handing over the function to call later, not calling it now.

Common events

Event Fires when
click The element is clicked or tapped
input The value of a field changes
submit A form is sent
keydown A key is pressed
change A checkbox, select or field is changed and left

The event object

Your function receives an event object with details about what happened. event.target is the element involved.

A character counter
Open in editor
<label for="bio">Short bio (max 100 characters)</label><br>
<textarea id="bio" rows="3" cols="40" maxlength="100"></textarea>
<p id="count">0 / 100</p>

<script>
const bio = document.querySelector("#bio");
const count = document.querySelector("#count");

bio.addEventListener("input", (event) => {
  const length = event.target.value.length;
  count.textContent = length + " / 100";
  count.style.color = length > 90 ? "crimson" : "black";
});
</script>
Result

Forms and preventDefault

By default, sending a form reloads the page. event.preventDefault() stops that, so your JavaScript can handle the data instead.

Putting it all together: a to-do app

This small app uses almost everything from the JavaScript course: variables, arrays, objects, functions, loops, conditions, the DOM and events. Read through it, run it, then try the challenges below.

To-do list
Open in editor
<!DOCTYPE html>
<html>
<head>
<style>
  body { font-family: sans-serif; max-width: 420px; padding: 8px; }
  form { display: flex; gap: 8px; }
  input { flex: 1; padding: 8px; font-size: 16px; }
  button { padding: 8px 12px; font-size: 16px; cursor: pointer; }
  ul { list-style: none; padding: 0; }
  li { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid #ddd; }
  li span { flex: 1; cursor: pointer; }
  li.done span { text-decoration: line-through; color: #999; }
</style>
</head>
<body>
<h2>My tasks</h2>
<form id="new-task">
  <label for="task" hidden>New task</label>
  <input id="task" placeholder="What needs doing?" autocomplete="off" required>
  <button type="submit">Add</button>
</form>
<ul id="tasks"></ul>
<p id="summary"></p>

<script>
  const tasks = [
    { text: "Finish the HTML course", done: true },
    { text: "Practice flexbox", done: false },
  ];

  const list = document.querySelector("#tasks");
  const summary = document.querySelector("#summary");

  function render() {
    list.replaceChildren();
    tasks.forEach((task, index) => {
      const item = document.createElement("li");
      if (task.done) item.classList.add("done");

      const label = document.createElement("span");
      label.textContent = task.text;
      label.addEventListener("click", () => {
        task.done = !task.done;
        render();
      });

      const remove = document.createElement("button");
      remove.textContent = "Delete";
      remove.addEventListener("click", () => {
        tasks.splice(index, 1);
        render();
      });

      item.append(label, remove);
      list.append(item);
    });

    const left = tasks.filter((t) => !t.done).length;
    summary.textContent = left === 0 ? "All done!" : left + " left to do";
  }

  document.querySelector("#new-task").addEventListener("submit", (event) => {
    event.preventDefault();
    const input = document.querySelector("#task");
    tasks.push({ text: input.value.trim(), done: false });
    input.value = "";
    render();
  });

  render();
</script>
</body>
</html>
Result

Click a task to tick it off. The key idea is in render(): the data (the tasks array) is the single source of truth, and the page is redrawn from it after every change. Modern frameworks like React are built around this same idea.

Challenges

  1. Add a Clear completed button that removes every finished task. Hint: filter.
  2. Show a different message when the list is empty.
  3. Save the tasks so they survive a page refresh. Look up localStorage and JSON.stringify. It won’t work inside this sandboxed example, so try it in a file on your own computer.

Practice

Check what you just learned. Fill in the missing code, then answer the quiz.

  1. Exercise 1

    Run sayHi when the button is clicked.

    button.("click", sayHi);

  2. Exercise 2

    Name the event that fires every time the text in a field changes.

    field.addEventListener("", updateCount);

  3. Exercise 3

    Stop the browser from sending the form and reloading the page.

    form.addEventListener("submit", (event) => {
      event.();
    });

Quiz: 3 questions

1. What is wrong with button.addEventListener("click", greet())?
2. Inside an event handler, what is event.target?
3. Why prefer addEventListener over an onclick attribute in HTML?

Finish the practice to complete this lesson.

Sign in to keep your progress on any device.