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:
- Download and unzip lab9.zip.
- Open the folder in VS Code.
- 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:
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:
- Create an HTML
form
inside theform-area
.- Give the form the class
card-form
.
- Give the form the class
- You will need four text
input
s and atextarea
, each with its ownlabel
.- Put each group of
label
plusinput
in its owndiv
. - Give these
div
s a class ofform-group
. - Make sure to include
placeholder
text. - Make sure each element has a
name
andid
.
- Put each group of
- Add
button
s at the end of theform
.- Put them inside a
div
with the classform-buttons
. - The preview button should have
type="button"
. - The save button should have
type="submit"
. - Make sure they have
id
s to access them in JavaScript.
- Put them inside a
Step 2: Adding Functionality¶
Notice the end of the body
loads createcard.js
.
Open this file and implement the following event listeners:
- When the "preview" button is clicked, you should set the text in all the appropriate
span
s to the value of the corresponding inputs in the form. -
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 keycards
.- 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
, andmessage
. - The properties should be set to the corresponding value from the form.
- It should have five properties:
- Add the new object to the end of the array.
- Store the array in
localStorage
to the entry with the keycards
, overwriting any existing entry.- Don't forget:
localStorage
only lets you store strings, so you'll need to "stringify" the array!
- Don't forget:
-
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:
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);
- To convert an array or object to a string:
- You should start by loading the existing array from
Part 2: Card Viewer¶
Next, you will focus on card-viewer.html
and viewcards.js
.
The finished application will look like this screenshot:
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.
- 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(...)
.
- Store this to a variable or constant named
- 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 calledcard
to make it easier to reference. - Inside the loop, use
console.log(...)
to print the current card object in the iteration.
- Recommended: save
- 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!
-
You should see something like this:
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.
- 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?
- 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 namedcardView
. - Finally, select the element with the
card-list
class and call.appendChild(cardView)
to add the newly created card to the list.
- In your JavaScript, select the template element and save it to a variable named
-
Test your code. You should have multiple cards displayed, one for each card in localStorage:
Step 5: Showing the Data¶
Now, we need to populate the newly created cardView
with data from the current card.
- Put your code before you call
.appendChild()
, but after.cloneNode()
: - 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.
- For example:
- Set each of those
<span>
'stextContent
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
- Remember that each card is an object, so you can access the attributes directly:
-
Test your code. Each card should display its corresponding text:
Step 6: Deleting a Card¶
Let's make the delete button work.
- Go to your code inside the loop. Make sure that the following lines are still before you call
.appendChild()
: - 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"
.
- First, select the delete button on the
- 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 oflet
. This is one of the advantages oflet
. It scopes the variable to the block instead of the function, which lets us keep its current state.
- In the callback function, write
- Now, in the button's callback function:
- Make the button delete the item in the array at the current index
- Use the
.splice(...)
method to remove an item at a given index.
- Use the
- 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.
- Make the button delete the item in the array at the current index
- 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.
- 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.
- Name the function
- 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.
- Use your
-
Test your code by clicking in one of the fields and editing it. You should see the value change in localStorage:
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:
- 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.
- Add a new data attribute called
- 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.