Skip to content

Lab 9: Forms and Storage

Due: Monday, Apr 21st

In this lab, you will create a tiny app to write greeting cards that you can send to your friends and family!

To get started:

  1. Download and unzip lab9.zip.
  2. Open the folder in VS Code.
  3. Look over the provided code to get a feel for what it does and where you need to add your own code.

Part 1: Card Creator

First, you will focus on card-creator.html and createcard.js. The finished application will look like this screenshot:

Screenshot of finished application

Step 1: Creating the Form

We've provided the code and styles for the "preview" area of the page, but you'll need to create the form. Provide the functionality for a user to specify what their card's title, message, to, from, etc. should be. Follow the image and the instructions below:

Screenshot of left side of app

  1. Create an HTML form inside the form-area.
    • Give the form the class card-form.
  2. You will need four text inputs and a textarea, each with its own label.
    • Put each group of label plus input in its own div.
    • Give these divs a class of form-group.
    • Make sure to include placeholder text.
    • Make sure each element has a name and id.
  3. Add buttons at the end of the form.
    • Put them inside a div with the class form-buttons.
    • The preview button should have type="button".
    • The save button should have type="submit".
    • Make sure they have ids to access them in JavaScript.

Step 2: Adding Functionality

Notice the end of the body loads createcard.js. Open this file and implement the following event listeners:

  1. When the "preview" button is clicked, you should set the text in all the appropriate spans to the value of the corresponding inputs in the form.
  2. When the "save" button is clicked, you should add the card to an array and save the array of cards to localStorage:

    • You should start by loading the existing array from localStorage with the key cards.
      • If there doesn't exist an entry with the key, create a new array and assign it to a variable.
      • If there does exist an entry with the key, you should parse it and assign it to a variable.
    • Create a new JavaScript object (aka map/dictionary) to represent the current card.
      • It should have five properties: to, from, title, subtitle, and message.
      • The properties should be set to the corresponding value from the form.
    • Add the new object to the end of the array.
    • Store the array in localStorage to the entry with the key cards, overwriting any existing entry.
      • Don't forget: localStorage only lets you store strings, so you'll need to "stringify" the array!
    • Saving multiple cards should result in an array containing all the cards. You can use the developer console in Chrome/Edge and Firefox to check if you're doing this correctly:

      Screenshot of Firefox DevTools

    Hints

    • localStorage.setItem("cards", ...) will completely replace the existing item in localStorage (meaning you will override the previous array of cards).
    • Thus, you need to make sure to load the existing array of cards with localStorage.getItem("cards"), push a new item to that array, and then save the whole array back to localStorage.
    • Don't forget that localStorage stores values as strings.
      • To convert an array or object to a string: let a_string = JSON.stringify(an_object);
      • To convert a string back to an array or object: let an_object = JSON.parse(a_string);

Part 2: Card Viewer

Next, you will focus on card-viewer.html and viewcards.js. The finished application will look like this screenshot:

Screenshot of finished application

Previously you made a page that lets users create greeting cards and save them to localStorage. But now we need a way to view the cards, edit them, and delete them (read, update, and delete in the CRUD operations).

Step 3: Loading and Looping

First, we will need to load the array of cards from localStorage so that we can loop over and display each card.

  1. In viewcards.js, use localStorage to get the item with the key "cards" and convert it back to an array.
    • Store this to a variable or constant named cards.
    • Don't forget to use JSON.parse(...).
  2. Next, use a "traditional" for loop to loop over the array (using indexes – this will be important later).
    • Recommended: save cards[i] to a variable called card to make it easier to reference.
    • Inside the loop, use console.log(...) to print the current card object in the iteration.
  3. Test your page in the browser and open the developer console.
    • FYI, you should always be testing your code like this to make sure there are no JavaScript errors!
  4. You should see something like this:

    Screenshot of JavaScript console

Step 4: Copying the Template

The next task you will tackle is to display a card for each item in the array. In the HTML, you will find a <main> element with the id of card-list. All the card displays should be a child of this element.

One way to do this is to create all the elements manually in JavaScript (using document.createElement(...)) to form parts of a card, and then add it to the card list. This approach, however, is more difficult to maintain. We should be able to define what we want a single card to look like in HTML and CSS and then reuse that code for each card.

The <template> element makes our lives much easier. It lets us write HTML that doesn't render in the browser, but can be cloned and inserted into our page.

  1. First, take a look at this article: HTML <template> Tag
    • In the article, look at the HTML for the examples. Do you see where the templates are defined?
  2. Let's apply the same principle to our card viewer:
    • In your JavaScript, select the template element and save it to a variable named template.
    • Inside the loop, call template.content.cloneNode(true) to create a copy of the template. Then save the copy to a variable named cardView.
    • Finally, select the element with the card-list class and call .appendChild(cardView) to add the newly created card to the list.
  3. Test your code. You should have multiple cards displayed, one for each card in localStorage:

    Screenshot with two cards

Step 5: Showing the Data

Now, we need to populate the newly created cardView with data from the current card.

  1. Put your code before you call .appendChild(), but after .cloneNode():
  2. First, select each of the <span>s that are used to display the card's text and save them to variables:
    • For example: let titleText = cardView.querySelector(".title-text");
    • You should have five of these, one for each span.
  3. Set each of those <span>'s textContent to the appropriate values from the current card.
    • Remember that each card is an object, so you can access the attributes directly: card.title
    • The five attributes are: to, from, title, subtitle, message
  4. Test your code. Each card should display its corresponding text:

    Screenshot with data showing

Step 6: Deleting a Card

Let's make the delete button work.

  1. Go to your code inside the loop. Make sure that the following lines are still before you call .appendChild():
  2. We need to make the delete button for the current card do something.
    • First, select the delete button on the cardView.
    • Then, add an event listener of type "click".
  3. When the button is clicked, we want to delete this card. The problem is, how does each delete button know which card its referring to?
    • In the callback function, write console.log(i) to log the current index.
    • Test your page and open the developer console. Click on the delete buttons. Notice how each button correctly prints out the corresponding index!
      • How does this work? When we create a function, JavaScript will "save" the state of our variables in something called a closure.
      • If this isn't working, you may have declared your variables with var instead of let. This is one of the advantages of let. It scopes the variable to the block instead of the function, which lets us keep its current state.
  4. Now, in the button's callback function:
    • Make the button delete the item in the array at the current index
    • Afterwards, save the array of cards back to localStorage.
    • Reload the page by calling location.reload().
      • We do this because, after deleting an item, the indexes in the array are no longer valid.
      • Refreshing the page will display all the cards again and rebuild the indexes.
  5. Test your code. You should be able to delete a card and, after the page reloads, the deleted card should no longer be displayed.

Step 7: Making the Cards Editable

Last but not least, let's make the cards editable. You may have noticed that there's a contenteditable attribute on each of the <span>s! This makes the element into something that works both as output and input.

Elements with contenteditable have an event called "input" which is fired whenever a user changes the value of the element.

  1. Inside the loop, but before appendChild, let's create a new function.
    • Name the function updateCard().
    • Inside the function, set each of the current card object's attributes (to, from, title, subtitle, message) to the content from the corresponding <span>s.
    • Afterwards, save the entire array to localStorage again.
  2. After the function definition, but still before appendChild(), add an event listener for the "input" event to all five <span>s.
    • Use your updateCard function as the callback.
  3. Test your code by clicking in one of the fields and editing it. You should see the value change in localStorage:

    Screenshot of DevTools local storage

Submission

When you are finished, submit the following files to Gradescope: card-creator.html, createcard.js, and viewcards.js. Note this lab will be manually graded.

Going Further

Instead of using closures, you can also "store" the index of each card in the HTML elements themselves. You can create a custom HTML attribute, called a data attribute. Here's how that would work instead of closures:

  1. On each card view and/or each of the <span>s:
    • Add a new data attribute called index using JavaScript (aka "data-index" in HTML).
    • Set the value to the current index of the card.
  2. In your event handlers/callback functions, retrieve the index attribute.
    • Attributes are stored as strings, so you may need to convert it back to an int.
    • Use this value to specify which card to delete/update.