Exercise & Solutions

After each section of the course, try the exercises below to practice what you learned.

Exercise 1
We learned alert('hello'); to create a popup with the text hello. Now create a popup with your name in it.

alert('Simon');

Exercise 2
Display today’s date in the Console in Chrome.

console.log('Friday, September 23, 2022');

Exercise 3
Overwrite the webpage using document.body.innerHTML so that the page just displays your name.

document.body.innerHTML = 'Simon';

Exercise 4
Overwrite the webpage to be empty.

document.body.innerHTML = '';

Exercise 1
Create 2 buttons next to each other, one with the label "Up" and another with the label "Down".

<button>Up</button>
<button>Down</button>

Exercise 2
Load Javascript from within the HTML and console.log your name.

<script>
  console.log('Simon');
</script>

Exercise 1
Change the value of the todo1 variable to a number (e.g. 99) and run typeof todo1. What is the type?

let todo1 = 99;
console.log(typeof todo1);    // Result is: "number"

Exercise 2
Create 3 variables: month, dayOfMonth, year. Use these variables to console.log today's date in one line in this format: "November 9, 2021"

let month = 'November';
let dayOfMonth = '9';
let year = '2021';
console.log(month + ' ' + dayOfMonth + ', ' + year);

Exercise 3
Save the result of 4 + 5 * 3 in a variable and console.log the result. Do the same with the result of (4 + 5) * 3. Notice the rules of math are the same in programming (brackets first, then multiply, then add).

let result1 = 4 + 5 * 3;
console.log(result1);   // Result is: 19
let result2 = (4 + 5) * 3;
console.log(result2);   // Result is: 27

Exercise 4
Create a variable age that saves your age (e.g. let age = 25;). Create another variable: let message = 'I am ' + age + ' years old'; What is the type of the message variable?

let age = 25;
let message = 'I am ' + age + ' years old';
console.log(typeof message);    // Result is: "string".
                                // Combining a string and a number results in a string.

Exercise 1
Change the title that shows up in the tab to "Your_Name's Todo App" (use your name).

<html>
  <head>
    <title>Simon's Todo App</title>
  </head>
  ...

Exercise 2
Add your name to the top of the webpage using a div.

...
<body>
  <div>Simon</div>
  <button>Press me</button>
  <div>Get groceries</div>
  ...

Exercise 3
Reverse the todo list by re-arranging the divs in the HTML. Notice that elements are rendered from top to bottom. Reverse the list back after to follow along with the rest of the tutorial.

...
<body>
  <button>Press me</button>
  <div>Make dinner</div>
  <div>Wash car</div>
  <div>Get groceries</div>
  ...

Exercise 1
Use Javascript to add a button containing the text "Click Me" inside the button.

let button = document.createElement('button');
button.innerText = 'Click me';
document.body.appendChild(button);

Exercise 2
Use Javascript to change the title of the webpage that shows up in the tabs (use Google to find the code for doing this).

document.title = 'Some other title';

Exercise 3
Reverse the order of the three todo items on the webpage by re-arranging the Javascript code. Notice how instructions are run one by one and elements are also created one by one.

let todo1 = 'Get groceries';
let todo2 = 'Wash car';
let todo3 = 'Make dinner';

let element = document.createElement('div');
element.innerText = todo3;
document.body.appendChild(element);

element = document.createElement('div');
element.innerText = todo2;
document.body.appendChild(element);

element = document.createElement('div');
element.innerText = todo1;
document.body.appendChild(element);

Exercise 1
Create a function named greeting that takes 1 parameter firstName and uses it to console.log a message saying "hello".

Example: greeting('Simon'); will console.log('hello Simon').

function greeting(firstName) {
  console.log('hello ' + firstName);
}
greeting('Simon');

Exercise 2
Write a function named toUpper that converts 1 string parameter str to uppercase and console.log the result. Use Google to find the code for converting a string to uppercase in Javascript.

Example: toUpper('Simon'); will console.log('SIMON')

function toUpper(str) {
  console.log(str.toUpperCase());
}
toUpper('Simon');

Exercise 3
Write a function that converts a parameter length from inches to centimeters.

function inchToCm(length) {
  console.log(length * 2.54);
}
inchToCm(10);

Exercise 1
Write a function toUpper that takes 1 parameter, an array of strings, and console.logs a new array with all the strings capitalized.

(Hint: create a new array and push onto the array. Use google to learn the code for converting a string to uppercase in Javascript.

Example: toUpper(['hello', 'world']); will console.log(['HELLO', 'WORLD'])

function toUpper(stringArr) {
  let result = [];

  stringArr.forEach(function (str) {
    result.push(str.toUpperCase());
  });

  console.log(result);
}
toUpper(['hello', 'world']);

Exercise 2
Write a function arrayDouble that takes an array and console.logs a new array with every value repeated twice.

Example: arrayDouble(['bark', 'meow']); will console.log(['bark', 'bark', 'meow', 'meow'])

function arrayDouble(stringArr) {
  let result = [];

  stringArr.forEach(function (str) {
    result.push(str);
    result.push(str);
  });

  console.log(result);
}
arrayDouble(['bark', 'meow']);

Exercise 3
Write a function arraySum that takes an array of numbers and console.logs the sum of the numbers. (Hint: create a variable and increase its value like this):

let total = 0;
total = total + 10;

Example: arraySum([1, 2, 3]); = 6 and arraySum([5, -2, 7, 0]); = 10

function arraySum(numArray) {
  let total = 0;

  numArray.forEach(function (num) {
    total = total + num;
  });

  console.log(total);
}
arraySum([1, 2, 3]);
arraySum([5, -2, 7, 0]);

Exercise 1

Set up a <button id="todo-button">To Do</button> so that when you click the button, the text inside changes to "Done" (hint: use .innerText)

solution.html
<button id="todo-button" onclick="changeToDone()">To do</button>
<script>
  function changeToDone() {
    let button = document.getElementById('todo-button');
    button.innerText = 'Done';
  }
</script>

Exercise 2
Set up a <div id="counter">0</div>, a button with the label "Up", and a variable: let count = 0; so that when clicking the button, the number in the div increases by 1. (hint: use count = count + 1;)

solution.html
<div id="counter">0</div>
<button onclick="countUp()">Up</button>
<script>
  let count = 0;

  function countUp() {
    count = count + 1;
    let counter = document.getElementById('counter');
    counter.innerText = count;
  }
</script>

Exercise 3
Set up the same counter as above, but with another button "Down", that decreases the count by one. (hint: use count = count - 1;)

solution.html
<div id="counter">0</div>
<button onclick="countUp()">Up</button>
<button onclick="countDown()">Down</button>
<script>
  let count = 0;

  function countUp() {
    count = count + 1;
    updateCount();
  }

  function countDown() {
    count = count - 1;
    updateCount();
  }

  // Not necessary, but BONUS POINTS if you separated this
  // repeated code into a function!
  function updateCount() {
    let counter = document.getElementById('counter');
    counter.innerText = count;
  }
</script>

Exercise 4
Create a button that takes what's inside the textbox, puts it in a div and adds the div to the page.

solution.html
<input type="text" id="todo-title" />
<button onclick="displayTitle()">Display Title</button>
<script>
  function displayTitle() {
    let textbox = document.getElementById('todo-title');
    let title = textbox.value;

    let div = document.createElement('div');
    div.innerText = title;
    document.body.appendChild(div);
  }
</script>

Exercise 1
Create a shopping cart. Set up a <div id="cart"></div> and 4 buttons labeled "Apple", "Tomato", "Eggs", and "Clear". When clicking the first 3 buttons, add a div inside the <div id="cart"> containing your food selection. When clicking "Clear" remove all items from the cart.

Here’s what it looks like:

solution.html
<button onclick="addToCart('Apple')">Apple</button>
<button onclick="addToCart('Tomato')">Tomato</button>
<button onclick="addToCart('Eggs')">Eggs</button>
<button onclick="clearCart()">Clear</button>
<div id="cart"></div>
<script>
  // BONUS POINTS if you saved this variable outside the
  // function so you can reuse it and not have to run
  // document.getElementById() every time.
  const cart = document.getElementById('cart');

  // BONUS POINTS if you wrote this function with 1 parameter.
  // Writing separate functions like addApple(), addTomato(),
  // is fine too, but this make the function more reusable.
  function addToCart(food) {
    const cartItem = document.createElement('div');
    cartItem.innerText = food;
    cart.appendChild(cartItem);
  }

  function clearCart() {
    cart.innerHTML = '';
  }
</script>

Exercise 2
Create a cm-inch converter. Create a textbox, 2 buttons: "Convert to cm" and "Convert to inch", and a <div id="result"></div>.

When clicking the "Convert to cm" button, take the value in the texbox, convert it from a string to a number (hint: use + to convert a string to a number. Example: +'10'), then display the result in cm in the <div id="result">

Examples:
value in textbox = 1, Click "Convert to cm", <div id="result">2.54 cm</div>
value in textbox = 25.4, Click "Convert to in", <div id="result">10 in</div>

solution.html
<input id="measurement" />
<button onclick="convertToCm()">Convert to cm</button>
<button onclick="convertToInch()">Convert to inch</button>
<script>
  const measurement = document.getElementById('measurement');

  function convertToCm() {
    const value = measurement.value;

    // value is a string and strings can't be multiplied. We need
    // to convert it into a number before we can multiply it.
    const convertedValue = +value * 2.54;

    const result = document.createElement('div');
    result.innerText = convertedValue;
    document.body.appendChild(result);
  }

  function convertToInch() {
    const value = measurement.value;
    const convertedValue = +value / 2.54;

    const result = document.createElement('div');
    result.innerText = convertedValue;
    document.body.appendChild(result);
  }
</script>

Exercise 1
Write a function cartTotal that takes an array of objects cartArray where each object contains a name, price, and quantity. console.log the total price of the items in the cart. For example:

cartTotal([
  { name: 'Apple', price: 4, quantity: 2 },
  { name: 'Orange', price: 3, quantity: 3 }
]);
Will console.log(17);
answer.js
function cartTotal(cartArray) {
  let total = 0;

  cartArray.forEach(function (item) {
    total = total + item.price * item.quantity;
  });

  console.log(total);
}
cartTotal([
  { name: 'Apple', price: 4, quantity: 2 },
  { name: 'Orange', price: 3, quantity: 3 }
]);

Exercise 2
Create a <div id="receipt"></div>. Write a function displayReceipt that takes the same array of objects as exercise 1 and display a receipt using these steps:

  1. Reset <div id="receipt"> to be empty using .innerHTML
  2. For each object in the array, add a line to the receipt in this format: <div>Apple $4 * 2 = $8</div>
  3. At the bottom, display the total: <div>Cart total = $17</div>
solution.html
<div id="receipt"></div>
<script>
  const receipt = document.getElementById('receipt');

  function displayReceipt(cartArray) {
    receipt.innerHTML = '';

    cartArray.forEach(function (item) {
      const receiptLine = document.createElement('div');
      receiptLine.innerText = item.name + ' $' + item.price + ' * ' + item.quantity;
      receipt.appendChild(receiptLine);
    });

    // You can also calculate the total using just the loop above, but I like
    // to separate them out so each loop does one thing.
    let cartTotal = 0;
    cartArray.forEach(function (item) {
      cartTotal = cartTotal + item.price * item.quantity;
    });

    const totalLine = document.createElement('div');
    totalLine.innerText = 'Cart total = $' + cartTotal;
    receipt.appendChild(totalLine);
  }

  displayReceipt([
    { name: 'Apple', price: 4, quantity: 2 },
    { name: 'Orange', price: 3, quantity: 3 }
  ]);
</script>

Exercise 1
There are 2 ways to access a value in an object:

let todo = {
  name: 'Apple',
  price: 3
};

todo.price;       // First way
todo['price'];    // Second way

// The second way allows you to use a variable to access the value
let prop = 'price';
todo[prop];   // prop will be replaced by its value so this is the
              // same as todo['price']

Write a function createCart that takes an object representing prices of food.
For example: createCart({ Apple: 3, Orange: 4, Egg: 2 });

  1. Create a variable: let total = 0;
  2. Run the code: Object.keys({ ... }); using the object to get a list of its properties. Example: Object.keys({ Apple: 3, Orange: 4, Egg: 2 }); results in ['Apple', 'Orange', 'Egg']
  3. For each food, render a div containing the name, price, and an "Add" button.
  4. When pressing the add button, add the price of the food to the total variable. Example: <div>Apple $3<button ...>Add</button></div>, Click button, total = total + 3
let total = 0;

function createCart(foodPrices) {
  const foods = Object.keys(foodPrices);

  foods.forEach(function (food) {
    const cartItem = document.createElement('div');

    // Here we get the food price using the food variable.
    // If food = 'Apple', then foodPrices[food] is the same
    // as foodPrices['Apple'], which is the same as foodPrices.Apple
    const foodPrice = foodPrices[food];
    cartItem.innerText = food + ' $' + foodPrice;

    const addButton = document.createElement('button');
    addButton.innerText = 'Add';

    // Sorry I didn't teach this (I'll revise in the 2022 edition)!
    // You'll have to use a function without a name here. If you
    // create a separate addToCart() function, it will not have
    // access to any of the variables in this function.
    addButton.onclick = function () {
      total = total + foodPrice;
    };

    cartItem.appendChild(addButton);

    // This wil render each food with an "Add" button onto the webpage.
    document.body.appendChild(cartItem);
  });
}

createCart({ Apple: 3, Orange: 4, Egg: 2 });

Exercise 1
I accidentally copied this question from one of the earlier exercises. Skip this one!

Remember that array.filter(function (item) { ... }); will loop through the array, keep items if the inner function returns true, and remove items if the inner function returns false.

Exercise 2
Write a function aboveFreezing that takes an array of numbers temps and returns an array with only temperatures that are above the freezing point of water. Temperature can be in Celsius or Fahrenheit, you decide.

function aboveFreezing(temps) {
  // Assume we're using Fahrenheit
  const tempsAboveFreezing = temps.filter(function (temp) {
    return temp > 32;
  });

  return tempsAboveFreezing;
}
aboveFreezing([40, 67, 8, 29, 100, -3, 0]);

Exercise 3
Write a function removeRed that takes an array of objects foodArray with the format: [{ name: 'Apple', color: 'red' }, { name: 'Egg', color: 'white' }] and removes all red-colored foods from the array.

function removeRed(foodArray) {
  // Instead of saving the filtered array in a variable,
  // we can return it in the same line.
  return foodArray.filter(function (food) {
    return food.color !== 'red';
  });
}
removeRed([{ name: 'Apple', color: 'red' }, { name: 'Egg', color: 'white' }]);

Exercise 1
Write a function named max that takes an array of numbers and returns the largest number in the array.
Example: max([1, 5, -2, 4, 3, 5, 0]); will return 5

function max(numArray) {
  let max = -Infinity;

  numArray.forEach(function (num) {
    // You can have an if statement without the else
    if (num > max) {
      max = num;
    }
  });

  return max;
}
max([1, 5, -2, 4, 3, 5, 0]);

Exercise 2
Similarly, write a function min that returns the smallest number in the array.

function min(numArray) {
  let min = Infinity;

  numArray.forEach(function (num) {
    if (num < min) {
      min = num;
    }
  });

  return min;
}
min([1, 5, -2, 4, 3, 5, 0]);

Exercise 3
Write a function that returns the smallest number that’s greater or equal to 0.

function minNonNegative(numArray) {
  let min = Infinity;

  numArray.forEach(function (num) {
    if (num < 0) {
      return;
    } else if (num < min) {
      min = num
    }
  });

  return min;
}
minNonNegative([1, 5, -2, 4, 3, 5, 0]);

Exercise 4
Write a function pickApples that takes an array of strings representing fruits and returns the array with the first 2 occurrences of "apple" removed.

Example: pickApples(['cherry', 'apple', 'orange', 'apple', 'banana', 'apple']);should return ['cherry', 'orange', 'banana', 'apple']

function pickApples(fruitArray) {
  let applesPicked = 0;

  const filteredArray = fruitArray.filter(function (fruit) {
    if (applesPicked >= 2) {
      // Remember, returning true keeps this value in the array.
      return true;
    } else if (fruit === 'apple') {
      applesPicked = applesPicked + 1;
      return false;
    } else {
      return true;
    }
  });

  return filteredArray;
}
pickApples(['cherry', 'apple', 'orange', 'apple', 'banana', 'apple']);

Exercise 5
Similarly, write a function pickFruits that removes 2 apples and 1 orange from the array.

function pickFruits(fruitArray) {
  let applesPicked = 0;
  let orangesPicked = 0;

  const filteredArray = fruitArray.filter(function (fruit) {
    if (fruit === 'apple') {
      if (applesPicked >= 2) {
        return true;
      } else {
        applesPicked = applesPicked + 1;
        return false;
      }
    } else if (fruit === 'orange') {
      if (orangesPicked >= 1) {
        return true;
      } else {
        orangesPicked = orangesPicked + 1;
        return false;
      }
    } else {
      return true;
    }
  });

  return filteredArray;
}
pickFruits(['cherry', 'apple', 'orange', 'apple', 'banana', 'apple']);

Exercise 6
Write a function pickLastApples similar to above that removes the last 2 apples from the array. (Use Google to get the code for reversing an array.)

function pickLastApples(fruitArray) {
  let applesPicked = 0;

  const reversedArray = fruitArray.reverse();

  const filteredArray = reversedArray.filter(function (fruit) {
    if (applesPicked >= 2) {
      return true;
    } else if (fruit === 'apple') {
      applesPicked = applesPicked + 1;
      return false;
    } else {
      return true;
    }
  });

  // Remember to reverse the array back.
  return filteredArray.reverse();
}
pickLastApples(['cherry', 'apple', 'orange', 'apple', 'banana', 'apple']);

Exercise 1
Note: make a copy of the code in progress and add to it. The code in progress can be found here

We’ll add a checkbox to each todo to show that the todo is done.

  1. When rendering each todo, create an input element with .type = 'checkbox';. Note: using .innerText wipes out the contents of the todo so to insert the checkbox before the title, use element.prepend(checkbox)
  2. Unlike textboxes, use .onChange instead of .onClick to handle clicks on the checkbox. Use checkbox.checked instead of checkbox.value to get whether or not the checkbox is checked (true if checked, false if unchecked).
  3. When the checkbox is clicked, find the todo associated with the checkbox and set a property todo.isDone = checkbox.checked;, then rerender. Hint: when creating the checkbox, save the todo id to the checkbox using checkbox.dataset.todoId = todo.id; You can later use checkbox.dataset to retrieve the id that you saved. (To follow MVC, put the code for modifying the .isDone property in a separate function in the Model section.)
  4. Add some code to the render function: if (todo.isDone === true), set the .checked property on the checkbox element to true. Otherwise, set it to false.
solution.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <input id="date-picker" type="date" />
    <button onclick="addTodo()">Add Todo</button>

    <div id="todo-list"></div>

    <script>
      // Model
      let todos = [{
        title: 'Get groceries',
        dueDate: '2021-10-04',
        id: 'id1'
      }, {
        title: 'Wash car',
        dueDate: '2021-02-03',
        id: 'id2'
      }, {
        title: 'Make dinner',
        dueDate: '2021-03-04',
        id: 'id3'
      }];

      // Creates a todo
      function createTodo(title, dueDate) {
        const id = '' + new Date().getTime();

        todos.push({
          title: title,
          dueDate: dueDate,
          id: id
        });
      }

      // Deletes a todo
      function removeTodo(idToDelete) {
        todos = todos.filter(function (todo) {
          // If the id of this todo matches idToDelete, return false
          // For everything else, return true
          if (todo.id === idToDelete) {
            return false;
          } else {
            return true;
          }
        });
      }

      function toggleTodo(todoId, checked) {
        todos.forEach(function (todo) {
          if (todo.id === todoId) {
            todo.isDone = checked;
          }
        });
      }

      // Controller
      function addTodo() {
        const textbox = document.getElementById('todo-title');
        const title = textbox.value;

        const datePicker = document.getElementById('date-picker');
        const dueDate = datePicker.value;

        createTodo(title, dueDate);
        render();
      }

      function deleteTodo(event) {
        const deleteButton = event.target;
        const idToDelete = deleteButton.id;

        removeTodo(idToDelete);
        render();
      }

      function checkTodo(event) {
        const checkbox = event.target;

        const todoId = checkbox.dataset.todoId;
        const checked = checkbox.checked;

        toggleTodo(todoId, checked);
        render();
      }

      // View
      function render() {
        // reset our list
        document.getElementById('todo-list').innerHTML = '';

        todos.forEach(function (todo) {
          const element = document.createElement('div');
          element.innerText = todo.title + ' ' + todo.dueDate;

          const checkbox = document.createElement('input');
          checkbox.type = 'checkbox';
          checkbox.onchange = checkTodo;
          checkbox.dataset.todoId = todo.id;
          if (todo.isDone === true) {
            checkbox.checked = true;
          } else {
            checkbox.checked = false;
          }
          element.prepend(checkbox);

          const deleteButton = document.createElement('button');
          deleteButton.innerText = 'Delete';
          deleteButton.style = 'margin-left: 12px';
          deleteButton.onclick = deleteTodo;
          deleteButton.id = todo.id;
          element.appendChild(deleteButton);

          const todoList = document.getElementById('todo-list');
          todoList.appendChild(element);
        });
      }

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

Exercise 1
Note: make a copy of the code in progress and add to it. The code in progress can be found here

We’ll add the ability to edit todos. (Warning: this one is challenging, but we already learned everything we need. You can do it! Answer is in description).

  1. We'll add a new property .isEditing to each todo. This property starts as undefined. When rendering, if (todo.isEditing === true), don’t render what we had before, instead render a new textbox, a new date picker, and an "Update" button. Else, render what we had before, except add an "Edit" button before the "Delete" button.
  2. When clicking the "Edit" button, find the todo and change todo.isEditing = true, then rerender. Hint: when creating the "Edit" button, save the todo id on to the button using button.dataset.todoId = todo.id; You can retrieve this id in the click handler function using button.dataset
  3. When clicking the "Update" button, find the associated textbox and date picker (hint: set todo.dataset.todoId when creating these 2 elements), grab the value inside the textbox and date picker, find the todo, update the todo with the new values, set todo.isEditing = false, and rerender.
solution.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <input id="date-picker" type="date" />
    <button onclick="addTodo()">Add Todo</button>

    <div id="todo-list"></div>

    <script>
      // Model
      // If localstorage has a todos array, then use it
      // Otherwise use the default array.
      let todos;

      // Retrieve localStorage
      const savedTodos = JSON.parse(localStorage.getItem('todos'));
      // Check if it's an array
      if (Array.isArray(savedTodos)) {
        todos = savedTodos;
      } else {
        todos = [{
          title: 'Get groceries',
          dueDate: '2021-10-04',
          id: 'id1'
        }, {
          title: 'Wash car',
          dueDate: '2021-02-03',
          id: 'id2'
        }, {
          title: 'Make dinner',
          dueDate: '2021-03-04',
          id: 'id3'
        }];
      }

      // Creates a todo
      function createTodo(title, dueDate) {
        const id = '' + new Date().getTime();

        todos.push({
          title: title,
          dueDate: dueDate,
          id: id
        });

        saveTodos();
      }

      // Deletes a todo
      function removeTodo(idToDelete) {
        todos = todos.filter(function (todo) {
          // If the id of this todo matches idToDelete, return false
          // For everything else, return true
          if (todo.id === idToDelete) {
            return false;
          } else {
            return true;
          }
        });

        saveTodos();
      }

      function setEditing(todoId) {
        todos.forEach(function (todo) {
          if (todo.id === todoId) {
            todo.isEditing = true;
          }
        });

        saveTodos();
      }

      function updateTodo(todoId, newTitle, newDate) {
        todos.forEach(function (todo) {
          if (todo.id === todoId) {
            todo.title = newTitle;
            todo.dueDate = newDate;
            todo.isEditing = false;
          }
        });

        saveTodos();
      }

      function saveTodos() {
        localStorage.setItem('todos', JSON.stringify(todos));
      }

      // Controller
      function addTodo() {
        const textbox = document.getElementById('todo-title');
        const title = textbox.value;

        const datePicker = document.getElementById('date-picker');
        const dueDate = datePicker.value;

        createTodo(title, dueDate);
        render();
      }

      function deleteTodo(event) {
        const deleteButton = event.target;
        const idToDelete = deleteButton.id;

        removeTodo(idToDelete);
        render();
      }

      // I forgot to mention: usually for these click handler function we like to name them
      // starting  with "on" (onAdd, onDelete, onEdit, etc.) I'll revise for the 2022 tutorial!
      function onEdit(event) {
        const editButton = event.target;
        const todoId = editButton.dataset.todoId;

        setEditing(todoId);
        render();
      }

      function onUpdate(event) {
        const updateButton = event.target;
        const todoId = updateButton.dataset.todoId;

        const textbox = document.getElementById('edit-title-' + todoId);
        const newTitle = textbox.value;

        const datePicker = document.getElementById('edit-date-' + todoId);
        const newDate = datePicker.value;

        updateTodo(todoId, newTitle, newDate);
        render();
      }

      // View
      function render() {
        // reset our list
        document.getElementById('todo-list').innerHTML = '';

        todos.forEach(function (todo) {
          const element = document.createElement('div');

          // If this todo is being edited, render a textbox, date picker and a
          // button for saving the edits.
          if (todo.isEditing === true) {
            const textbox = document.createElement('input');
            textbox.type = 'text';
            textbox.id = 'edit-title-' + todo.id;
            element.appendChild(textbox);

            const datePicker = document.createElement('input');
            datePicker.type = 'date';
            datePicker.id = 'edit-date-' + todo.id;
            element.appendChild(datePicker);

            const updateButton = document.createElement('button');
            updateButton.innerText = 'Update';
            updateButton.dataset.todoId = todo.id;
            updateButton.onclick = onUpdate;
            element.appendChild(updateButton);

          // If this todo is not being edited, render what we had before
          // and add an "Edit" button.
          } else {
            element.innerText = todo.title + ' ' + todo.dueDate;

            const editButton = document.createElement('button');
            editButton.innerText = 'Edit';
            editButton.style = 'margin-left: 12px';
            editButton.onclick = onEdit;
            editButton.dataset.todoId = todo.id;
            element.appendChild(editButton);

            const deleteButton = document.createElement('button');
            deleteButton.innerText = 'Delete';
            deleteButton.style = 'margin-left: 12px';
            deleteButton.onclick = deleteTodo;
            deleteButton.id = todo.id;
            element.appendChild(deleteButton);
          }

          const todoList = document.getElementById('todo-list');
          todoList.appendChild(element);
        });
      }

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

Exercise 1
Convert our functions to arrow functions (remember we also use functions in todos.filter and todos.forEach). Re-run the code and make sure things still work.

Exercise 2
Remember when we click the "Delete" button, we use event.target to get the button that was clicked and then get the todo id from the button. This time, we’ll use something called a "closure" to achieve the same goal.

Write a function onDelete that takes 1 parameter todoToDelete which is a todo object, and returns a function. The inner function contains the code to remove the todo, but instead of using event.target to get the id, just use the todoToDelete parameter from the outer function.

When creating the "Delete" button, set deleteButton.onclick = onDelete(todo); The result of onDelete(todo) will be a function itself, which can then be used with .onclick. Notice the inner function has access to the todoToDelete parameter from the outer function. This is called a "closure". Run the code and make sure deleting still works.

solution.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <input id="date-picker" type="date" />
    <button onclick="addTodo()">Add Todo</button>

    <div id="todo-list"></div>

    <script>
      // Model
      // If localstorage has a todos array, then use it
      // Otherwise use the default array.
      let todos;

      // Retrieve localStorage
      const savedTodos = JSON.parse(localStorage.getItem('todos'));
      // Check if it's an array
      if (Array.isArray(savedTodos)) {
        todos = savedTodos;
      } else {
        todos = [{
          title: 'Get groceries',
          dueDate: '2021-10-04',
          id: 'id1'
        }, {
          title: 'Wash car',
          dueDate: '2021-02-03',
          id: 'id2'
        }, {
          title: 'Make dinner',
          dueDate: '2021-03-04',
          id: 'id3'
        }];
      }

      // Creates a todo
      const createTodo = (title, dueDate) => {
        const id = '' + new Date().getTime();

        todos.push({
          title: title,
          dueDate: dueDate,
          id: id
        });

        saveTodos();
      }

      // Deletes a todo
      const removeTodo = idToDelete => {
        todos = todos.filter(todo => {
          // If the id of this todo matches idToDelete, return false
          // For everything else, return true
          if (todo.id === idToDelete) {
            return false;
          } else {
            return true;
          }
        });

        saveTodos();
      }

      const saveTodos = () => {
        localStorage.setItem('todos', JSON.stringify(todos));
      }

      // Controller
      const addTodo = () => {
        const textbox = document.getElementById('todo-title');
        const title = textbox.value;

        const datePicker = document.getElementById('date-picker');
        const dueDate = datePicker.value;

        createTodo(title, dueDate);
        render();
      }

      /* ❌ this code can now be deleted
      const deleteTodo = event => {
        const deleteButton = event.target;
        const idToDelete = deleteButton.id;

        removeTodo(idToDelete);
        render();
      }
      */

      // This is a closure (a function that returns another
      // function. The inner has access to the outer function's
      // parameters and variables).
      const onDelete = todoToDelete => {
        return () => {
          removeTodo(todoToDelete.id);
          render();
        };
      };

      // View
      const render = () => {
        // reset our list
        document.getElementById('todo-list').innerHTML = '';

        todos.forEach(todo => {
          const element = document.createElement('div');
          element.innerText = todo.title + ' ' + todo.dueDate;

          const deleteButton = document.createElement('button');
          deleteButton.innerText = 'Delete';
          deleteButton.style = 'margin-left: 12px';

          /* The previous way we did it, deleteTodo does not have
            access to the todo parameter in this for each loop.
            ❌ this code can now be deleted
          deleteButton.onclick = deleteTodo;
          deleteButton.id = todo.id;
          */

          // Closures let you create functions and give them access to
          // variables they normally wouldn't have access to.
          deleteButton.onclick = onDelete(todo);
          element.appendChild(deleteButton);

          const todoList = document.getElementById('todo-list');
          todoList.appendChild(element);
        });
      }

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

Cheatsheet

Download Cheatsheet

Code

Here's a copy of the code at the end of each section.

todo.html
<button>Press me</button>
<script>
  alert('hello');
</script>

todo.html
<button>Press me</button>
<script>
  let todo1 = 'Get groceries';
  let todo2 = 'Wash car';
  let todo3 = 'Make dinner';
</script>

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <button>Press me</button>
    <div>Get groceries</div>
    <div>Wash car</div>
    <div>Make dinner</div>
    <script>
      let todo1 = 'Get groceries';
      let todo2 = 'Wash car';
      let todo3 = 'Make dinner';
    </script>
  </body>
</html>

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <button>Press me</button>
    <script>
      let todo1 = 'Get groceries';
      let todo2 = 'Wash car';
      let todo3 = 'Make dinner';

      let element = document.createElement('div');
      element.innerText = todo1;
      document.body.appendChild(element);

      element = document.createElement('div');
      element.innerText = todo2;
      document.body.appendChild(element);

      element = document.createElement('div');
      element.innerText = todo3;
      document.body.appendChild(element);
    </script>
  </body>
</html>

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <button>Press me</button>
    <script>
      let todo1 = 'Get groceries';
      let todo2 = 'Wash car';
      let todo3 = 'Make dinner';

      function addTodo(todoTitle) {
        let element = document.createElement('div');
        element.innerText = todoTitle;
        document.body.appendChild(element);
      }

      addTodo(todo1);
      addTodo(todo2);
      addTodo(todo3);
    </script>
  </body>
</html>

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <button>Press me</button>
    <script>
      let todos = ['Get groceries', 'Wash car', 'Make dinner'];
      todos.push('another todo');

      todos.forEach(function (todoTitle) {
        let element = document.createElement('div');
        element.innerText = todoTitle;
        document.body.appendChild(element);
      });
    </script>
  </body>
</html>

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <button onclick="addTodo()">Add Todo</button>
    <script>
      let todos = ['Get groceries', 'Wash car', 'Make dinner'];

      todos.forEach(function (todoTitle) {
        let element = document.createElement('div');
        element.innerText = todoTitle;
        document.body.appendChild(element);
      });

      function addTodo() {
        let textbox = document.getElementById('todo-title');
        let title = textbox.value;
        todos.push(title);
      }
    </script>
  </body>
</html>

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <button onclick="addTodo()">Add Todo</button>

    <div id="todo-list"></div>

    <script>
      const todos = ['Get groceries', 'Wash car', 'Make dinner'];

      render();

      function addTodo() {
        const textbox = document.getElementById('todo-title');
        const title = textbox.value;
        todos.push(title);

        render();
      }

      function render() {
        // reset our list
        document.getElementById('todo-list').innerHTML = '';

        todos.forEach(function (todoTitle) {
          const element = document.createElement('div');
          element.innerText = todoTitle;
          const todoList = document.getElementById('todo-list');
          todoList.appendChild(element);
        });
      }
    </script>
  </body>
</html>

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <input id="date-picker" type="date" />
    <button onclick="addTodo()">Add Todo</button>

    <div id="todo-list"></div>

    <script>
      const todos = [{
        title: 'Get groceries',
        dueDate: '2021-10-04'
      }, {
        title: 'Wash car',
        dueDate: '2021-02-03'
      }, {
        title: 'Make dinner',
        dueDate: '2021-03-04'
      }];

      render();

      function addTodo() {
        const textbox = document.getElementById('todo-title');
        const title = textbox.value;

        const datePicker = document.getElementById('date-picker');
        const dueDate = datePicker.value;
        todos.push({
          title: title,
          dueDate: dueDate
        });

        render();
      }

      function render() {
        // reset our list
        document.getElementById('todo-list').innerHTML = '';

        todos.forEach(function (todo) {
          const element = document.createElement('div');
          element.innerText = todo.title + ' ' + todo.dueDate;
          const todoList = document.getElementById('todo-list');
          todoList.appendChild(element);
        });
      }
    </script>
  </body>
</html>

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <input id="date-picker" type="date" />
    <button onclick="addTodo()">Add Todo</button>

    <div id="todo-list"></div>

    <script>
      const todos = [{
        title: 'Get groceries',
        dueDate: '2021-10-04',
        id: 'id1'
      }, {
        title: 'Wash car',
        dueDate: '2021-02-03',
        id: 'id2'
      }, {
        title: 'Make dinner',
        dueDate: '2021-03-04',
        id: 'id3'
      }];

      render();

      function addTodo() {
        const textbox = document.getElementById('todo-title');
        const title = textbox.value;

        const datePicker = document.getElementById('date-picker');
        const dueDate = datePicker.value;

        const id = new Date().getTime();

        todos.push({
          title: title,
          dueDate: dueDate,
          id: id
        });

        render();
      }

      function deleteTodo(event) {
        const deleteButton = event.target;
        const idToDelete = deleteButton.id;

      }

      function render() {
        // reset our list
        document.getElementById('todo-list').innerHTML = '';

        todos.forEach(function (todo) {
          const element = document.createElement('div');
          element.innerText = todo.title + ' ' + todo.dueDate;

          const deleteButton = document.createElement('button');
          deleteButton.innerText = 'Delete';
          deleteButton.style = 'margin-left: 12px';
          deleteButton.onclick = deleteTodo;
          deleteButton.id = todo.id;
          element.appendChild(deleteButton);

          const todoList = document.getElementById('todo-list');
          todoList.appendChild(element);
        });
      }
    </script>
  </body>
</html>

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <input id="date-picker" type="date" />
    <button onclick="addTodo()">Add Todo</button>

    <div id="todo-list"></div>

    <script>
      const todos = [{
        title: 'Get groceries',
        dueDate: '2021-10-04',
        id: 'id1'
      }, {
        title: 'Wash car',
        dueDate: '2021-02-03',
        id: 'id2'
      }, {
        title: 'Make dinner',
        dueDate: '2021-03-04',
        id: 'id3'
      }];

      render();

      function addTodo() {
        const textbox = document.getElementById('todo-title');
        const title = textbox.value;

        const datePicker = document.getElementById('date-picker');
        const dueDate = datePicker.value;

        const id = new Date().getTime();

        todos.push({
          title: title,
          dueDate: dueDate,
          id: id
        });

        render();
      }

      function func() {
        return 'one hundred';
      }

      function deleteTodo(event) {
        const deleteButton = event.target;
        const idToDelete = deleteButton.id;

        todos.filter(function (todo) {
          return true;
        });
      }

      function render() {
        // reset our list
        document.getElementById('todo-list').innerHTML = '';

        todos.forEach(function (todo) {
          const element = document.createElement('div');
          element.innerText = todo.title + ' ' + todo.dueDate;

          const deleteButton = document.createElement('button');
          deleteButton.innerText = 'Delete';
          deleteButton.style = 'margin-left: 12px';
          deleteButton.onclick = deleteTodo;
          deleteButton.id = todo.id;
          element.appendChild(deleteButton);

          const todoList = document.getElementById('todo-list');
          todoList.appendChild(element);
        });
      }
    </script>
  </body>
</html>

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <input id="date-picker" type="date" />
    <button onclick="addTodo()">Add Todo</button>

    <div id="todo-list"></div>

    <script>
      let todos = [{
        title: 'Get groceries',
        dueDate: '2021-10-04',
        id: 'id1'
      }, {
        title: 'Wash car',
        dueDate: '2021-02-03',
        id: 'id2'
      }, {
        title: 'Make dinner',
        dueDate: '2021-03-04',
        id: 'id3'
      }];

      render();

      function addTodo() {
        const textbox = document.getElementById('todo-title');
        const title = textbox.value;

        const datePicker = document.getElementById('date-picker');
        const dueDate = datePicker.value;

        const id = '' + new Date().getTime();

        todos.push({
          title: title,
          dueDate: dueDate,
          id: id
        });

        render();
      }

      function func() {
        const num1 = 1;
        const num5 = 5;

        if (num1 > num5) {
          console.log('run this code');
        } else if (num1 > 100) {
          console.log('not run');
        } else if (num1 > 0) {
          console.log('true');
        } else {
          console.log('other');
        }
      }

      function deleteTodo(event) {
        const deleteButton = event.target;
        const idToDelete = deleteButton.id;

        todos = todos.filter(function (todo) {
          // If the id of this todo matches idToDelete, return false
          // For everything else, return true
          if (todo.id === idToDelete) {
            return false;
          } else {
            return true;
          }
        });

        render();
      }

      function render() {
        // reset our list
        document.getElementById('todo-list').innerHTML = '';

        todos.forEach(function (todo) {
          const element = document.createElement('div');
          element.innerText = todo.title + ' ' + todo.dueDate;

          const deleteButton = document.createElement('button');
          deleteButton.innerText = 'Delete';
          deleteButton.style = 'margin-left: 12px';
          deleteButton.onclick = deleteTodo;
          deleteButton.id = todo.id;
          element.appendChild(deleteButton);

          const todoList = document.getElementById('todo-list');
          todoList.appendChild(element);
        });
      }
    </script>
  </body>
</html>

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <input id="date-picker" type="date" />
    <button onclick="addTodo()">Add Todo</button>

    <div id="todo-list"></div>

    <script>
      // Model
      let todos = [{
        title: 'Get groceries',
        dueDate: '2021-10-04',
        id: 'id1'
      }, {
        title: 'Wash car',
        dueDate: '2021-02-03',
        id: 'id2'
      }, {
        title: 'Make dinner',
        dueDate: '2021-03-04',
        id: 'id3'
      }];

      // Creates a todo
      function createTodo(title, dueDate) {
        const id = '' + new Date().getTime();

        todos.push({
          title: title,
          dueDate: dueDate,
          id: id
        });
      }

      // Deletes a todo
      function removeTodo(idToDelete) {
        todos = todos.filter(function (todo) {
          // If the id of this todo matches idToDelete, return false
          // For everything else, return true
          if (todo.id === idToDelete) {
            return false;
          } else {
            return true;
          }
        });
      }

      // Controller
      function addTodo() {
        const textbox = document.getElementById('todo-title');
        const title = textbox.value;

        const datePicker = document.getElementById('date-picker');
        const dueDate = datePicker.value;

        createTodo(title, dueDate);
        render();
      }

      function deleteTodo(event) {
        const deleteButton = event.target;
        const idToDelete = deleteButton.id;

        removeTodo(idToDelete);
        render();
      }

      // View
      function render() {
        // reset our list
        document.getElementById('todo-list').innerHTML = '';

        todos.forEach(function (todo) {
          const element = document.createElement('div');
          element.innerText = todo.title + ' ' + todo.dueDate;

          const deleteButton = document.createElement('button');
          deleteButton.innerText = 'Delete';
          deleteButton.style = 'margin-left: 12px';
          deleteButton.onclick = deleteTodo;
          deleteButton.id = todo.id;
          element.appendChild(deleteButton);

          const todoList = document.getElementById('todo-list');
          todoList.appendChild(element);
        });
      }

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

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <input id="date-picker" type="date" />
    <button onclick="addTodo()">Add Todo</button>

    <div id="todo-list"></div>

    <script>
      // Model
      // If localstorage has a todos array, then use it
      // Otherwise use the default array.
      let todos;

      // Retrieve localStorage
      const savedTodos = JSON.parse(localStorage.getItem('todos'));
      // Check if it's an array
      if (Array.isArray(savedTodos)) {
        todos = savedTodos;
      } else {
        todos = [{
          title: 'Get groceries',
          dueDate: '2021-10-04',
          id: 'id1'
        }, {
          title: 'Wash car',
          dueDate: '2021-02-03',
          id: 'id2'
        }, {
          title: 'Make dinner',
          dueDate: '2021-03-04',
          id: 'id3'
        }];
      }

      // Creates a todo
      function createTodo(title, dueDate) {
        const id = '' + new Date().getTime();

        todos.push({
          title: title,
          dueDate: dueDate,
          id: id
        });

        saveTodos();
      }

      // Deletes a todo
      function removeTodo(idToDelete) {
        todos = todos.filter(function (todo) {
          // If the id of this todo matches idToDelete, return false
          // For everything else, return true
          if (todo.id === idToDelete) {
            return false;
          } else {
            return true;
          }
        });

        saveTodos();
      }

      function saveTodos() {
        localStorage.setItem('todos', JSON.stringify(todos));
      }

      // Controller
      function addTodo() {
        const textbox = document.getElementById('todo-title');
        const title = textbox.value;

        const datePicker = document.getElementById('date-picker');
        const dueDate = datePicker.value;

        createTodo(title, dueDate);
        render();
      }

      function deleteTodo(event) {
        const deleteButton = event.target;
        const idToDelete = deleteButton.id;

        removeTodo(idToDelete);
        render();
      }

      // View
      function render() {
        // reset our list
        document.getElementById('todo-list').innerHTML = '';

        todos.forEach(function (todo) {
          const element = document.createElement('div');
          element.innerText = todo.title + ' ' + todo.dueDate;

          const deleteButton = document.createElement('button');
          deleteButton.innerText = 'Delete';
          deleteButton.style = 'margin-left: 12px';
          deleteButton.onclick = deleteTodo;
          deleteButton.id = todo.id;
          element.appendChild(deleteButton);

          const todoList = document.getElementById('todo-list');
          todoList.appendChild(element);
        });
      }

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

todo.html
<html>
  <head>
    <title>My Todo App</title>
  </head>
  <body>
    <input id="todo-title" type="text" />
    <input id="date-picker" type="date" />
    <button onclick="addTodo()">Add Todo</button>

    <div id="todo-list"></div>

    <script>
      // Model
      // If localstorage has a todos array, then use it
      // Otherwise use the default array.
      let todos;

      // Retrieve localStorage
      const savedTodos = JSON.parse(localStorage.getItem('todos'));
      // Check if it's an array
      if (Array.isArray(savedTodos)) {
        todos = savedTodos;
      } else {
        todos = [{
          title: 'Get groceries',
          dueDate: '2021-10-04',
          id: 'id1'
        }, {
          title: 'Wash car',
          dueDate: '2021-02-03',
          id: 'id2'
        }, {
          title: 'Make dinner',
          dueDate: '2021-03-04',
          id: 'id3'
        }];
      }

      // Creates a todo
      const createTodo = (title, dueDate) => {
        const id = '' + new Date().getTime();

        todos.push({
          title: title,
          dueDate: dueDate,
          id: id
        });

        saveTodos();
      }

      // Deletes a todo
      const removeTodo = idToDelete => {
        todos = todos.filter(todo => {
          // If the id of this todo matches idToDelete, return false
          // For everything else, return true
          if (todo.id === idToDelete) {
            return false;
          } else {
            return true;
          }
        });

        saveTodos();
      }

      const saveTodos = () => {
        localStorage.setItem('todos', JSON.stringify(todos));
      }

      // Controller
      const addTodo = () => {
        const textbox = document.getElementById('todo-title');
        const title = textbox.value;

        const datePicker = document.getElementById('date-picker');
        const dueDate = datePicker.value;

        createTodo(title, dueDate);
        render();
      }

      const deleteTodo = event => {
        const deleteButton = event.target;
        const idToDelete = deleteButton.id;

        removeTodo(idToDelete);
        render();
      }

      // View
      const render = () => {
        // reset our list
        document.getElementById('todo-list').innerHTML = '';

        todos.forEach(todo => {
          const element = document.createElement('div');
          element.innerText = todo.title + ' ' + todo.dueDate;

          const deleteButton = document.createElement('button');
          deleteButton.innerText = 'Delete';
          deleteButton.style = 'margin-left: 12px';
          deleteButton.onclick = deleteTodo;
          deleteButton.id = todo.id;
          element.appendChild(deleteButton);

          const todoList = document.getElementById('todo-list');
          todoList.appendChild(element);
        });
      }

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