Lab 7 - Card Builder
Categories:
9 minute read
Greeting Cards
In this (two-part) lab, you will write a tiny app to create greeting cards that you can send to your friends, family, and more! You will need the knowledge you’ve built in the past two preps (forms and localStorage).
For this lab, you will submit the assignment individually (but feel free to collaborate with others).
Getting Started
- Download this staring code and unzip it
- 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
Creating the Form
We’ve provided the code and styles for the “preview” area of the page, but you’ll need to create a form and 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:
In card-creator.html
:
- 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 this
div
a class ofform-group
- Give this
- Make sure to include
placeholder
text - You’ll want to 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, so you can access them easier in a script
- Put them inside a
Adding the Functionality
At the end of the body
of card-creator.html
it includes a javascript file. Open this file and write JavaScript to do the following:
- When the “preview” button is clicked, you should set the text in all the appropriate
span
s to the value of the corresponding control 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:
-
Greeting Cards, Part 2
In the previous lab, you made a page that lets users create greeting cards and save them to localStorage. But we need a way to view the cards, edit them, and delete them (read, update, and delete in the CRUD operations).
Let’s create a card viewer that displays the data for each card, with a way to edit and delete each one.
Getting Started
- Start with your code from the previous lab
- Let your instructor know if you do not have a working
card-creator.html
orcreatecard.js
- Let your instructor know if you do not have a working
- Here are some more hints for completing the previous lab:
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:
var a_string = JSON.stringify(an_object);
- To convert a string back to an array or object:
var an_object = JSON.parse(a_string);
- To convert an array or object to a string:
Open up the card-viewer.html
and scripts/viewcards.js
files. You will be working with these for the lab.
Loading and Looping
First, we will need to load the array of cards from localStorage so that we can loop over each card and display it.
-
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 calledcards
.- Don’t forget to use
JSON.parse(...)
!
- Don’t forget to use
-
Next, use a “traditional”
for
loop to loop over the array (using indices – this will be important later!)- Also important: use
let
instead ofvar
for your variables - Recommended: save
cards[i]
to a variable calledcard
to make it easier to reference
- Also important: use
-
Inside the loop, use
console.log(...)
to print the current card object in the iteration. -
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:
Creating Multiple Cards
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, however, is not maintainable. 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 on using templates: HTML
<template>
Tag- Look at the HTML for the examples. Do you see where the templates are defined?
- We can use
document.getElementById()
ordocument.querySelector()
to first select the template - We can then call the
.content.cloneNode(true)
method on that template, which will return a new Node representing a copy of that template - Then, we can append that copy somewhere on our page
-
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
.- (You can do this inside or outside the loop)
- Inside the loop, call
template.content.cloneNode(true)
to create a copy of the template, and then save it to a variable namedcardView
. - Finally, select the element with the
card-list
class, and call.appendChild(cardView)
on it 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:
Displaying the Data
Now, we need to populate the newly created cardView
with the 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: e.g.,
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: e.g.,
-
Test your code. Each card should display its corresponding text:
Deleting Cards
Let’s make the delete button work. To do so, we’ll need to do a few things.
- Continue with 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"
- For the callback function, make an inline function – this is important!
- 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. - If closures scare you, there is an alternative method below
- 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
- Don’t forget to stringify it!
- Reload the page by calling
location.reload()
- We do this because, after deleting an item, the indices in the array are no longer valid, so let’s refresh the page and display all the cards again
- 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, once the page reloads, it should no longer be displayed.
(If you have time) 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 text and an 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 still
appendChild
, let’s create a new function. Yes, right here.- Name the function
updateCard()
- Inside, you should set each of the current card object’s attributes (to, from, title, subtitle, message) to the content from the corresponding
<span>
s- For instance,
card.title = titleText.textContent;
orcards[i].title = ...
- For instance,
- Afterwards, save the entire array to localStorage again.
- Don’t forget to stringify it!
- 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 yourupdateCard
function as the callback:- E.g.,
titleText.addEventListener("input", updateCard);
- E.g.,
-
Test your code by clicking in one of the fields and editing it. You should see the value change in localStorage:
- If you are having issues, double-check:
- Make sure you are using either
let
orconst
for your variables/constants, notvar
- Make sure you are passing
updateCard
as an argument, and not accidentally calling it withupdateCard()
- Make sure you are using either
- If you are having issues, double-check:
Alternative to Using Closures
Instead of using closures, you can also “store” the index of each card in the HTML elements themselves! We 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)- Reference the MDN article for examples
- 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
Submission
Submit your code to gradescope.