Lab 11 - Card Viewer
Categories:
7 minute read
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
- The assignment is available in GitHub Classroom using your instructor-provided link (in Canvas)
- Let your instructor know if you do not have a working
card-creator.html
orcreatecard.js
- 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
Commit and push your code to GitHub.