Introduction:
In this tutorial, we'll walk through the process of building a simple shopping list app using JavaScript.
This app will allow users to add items to a list, mark them as bought, and clear the entire list. It's a beginner-friendly project that will help you understand the basics of DOM manipulation and localStorage. So, let's get started!
This is the live page
https://cart-companion.netlify.app/
Prerequisites:
Basic understanding of HTML, CSS, and JavaScript.
A Text editor or integrated development environment (IDE) of your choice.
Setting Up the Project:
To begin, create a new directory for your project and set up the following files:
index.html - Copy the provided HTML code into this file.
style.css - Copy the provided CSS code into this file.
app.js - Copy the provided JavaScript code into this file.
site.webmanifest - Copy the provided site manifest code into this file.
Make sure to link the CSS
, JavaScript
, and Site Webmanifest
files to the HTML
file using appropriate <link>
and <script>
tags.
Explaining the HTML Structure:
Let's start by understanding the HTML structure of our shopping list app:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
http-equiv="X-UA-Compatible"
content="IE=edge"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<link
rel="stylesheet"
href="style.css"
/>
<link
rel="preconnect"
href="https://fonts.googleapis.com"
/>
<link
rel="preconnect"
href="https://fonts.gstatic.com"
crossorigin
/>
<link
href="https://fonts.googleapis.com/css2?family=Rubik:wght@300&display=swap"
rel="stylesheet"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link
rel="manifest"
href="/site.webmanifest"
/>
<title>Cart Companion</title>
</head>
<body>
<div class="container">
<img
src="assets/shopping-cart.png"
alt="A cat inside a cart"
/>
<input
type="text"
id="input-field"
placeholder="Item"
/>
<div id="action-button">
<button id="add-button">Add to cart</button>
<button id="clear-button">Clear cart</button>
</div>
<p id="empty-list">No item here... yet</p>
<ul id="shopping-list"></ul>
<div id="toast-container"></div>
</div>
<script
src="app.js"
type="module"
></script>
</body>
</html>
In the code above, we have the basic HTML structure with a container div where our app content will reside.
Styling the App with CSS
html,
body {
margin: 0;
padding: 0;
font-family: "Rubik", sans-serif;
background-color: #eef0f4;
color: #432000;
-webkit-user-select: none;
user-select: none; /*so that users can't select anything*/
}
img {
width: 150px;
margin: 0 auto;
}
input {
color: #432000;
background-color: #dce1eb;
border: 1px solid #000000;
padding: 15px;
border-radius: 8px;
font-size: 20px;
text-align: center;
font-family: "Rubik", sans-serif;
margin: 10px 0;
}
ul {
display: flex;
justify-content: center;
align-items: center;
list-style: none;
gap: 10px;
flex-wrap: wrap;
padding: 0;
}
ul li {
flex-grow: 1;
padding: 15px;
border-radius: 5px;
box-shadow: 1px 2px 3px rgba(0 0 0 /0.5);
color: #a52a2a;
font-weight: bold;
text-align: center;
cursor: pointer;
}
ul li:hover {
background: #81061c;
color: #ffffff;
}
.container {
display: flex;
flex-direction: column;
max-width: 320px;
margin: 30px auto;
}
/* adding fade-in animation */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* adding fade-out animation */
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
/* smooth transition */
.transition {
transition: all 0.3s ease;
}
/* sorting buttons container */
.sort-buttons-container {
margin: 20px 0;
}
#empty-list {
display: none;
text-align: center;
font-style: italic;
color: gray;
}
/* toast container */
#toast-container {
position: fixed;
z-index: 9999;
}
/* toast message */
.toast {
display: inline-block;
padding: 12px 20px;
background-color: #333;
color: #fff;
border-radius: 4px;
opacity: 1;
transition: opacity 0.1s ease-in-out;
}
/* toast message animation */
.toast.show {
opacity: 1;
}
/* bought item */
.bought {
opacity: 0.5;
text-decoration: line-through;
}
#action-button {
display: flex;
padding-inline: 0.3em;
}
/* add / clear button style */
#add-button,
#clear-button {
color: #fdfdfd;
background-color: #ac485a;
border: 0;
padding: 15px;
border-radius: 8px;
font-size: 20px;
text-align: center;
font-family: "Rubik", sans-serif;
cursor: pointer;
}
#clear-button {
margin-left: auto;
}
#add-button:hover {
background-color: #8a2b3d;
font-weight: bold;
}
#clear-button:hover {
background: #a50303;
font-weight: bold;
}
Feel free to customize the styles according to your preferences. You can change the colors, fonts, and layout to match your design aesthetic.
Implementing the JavaScript Functionality
Now comes the exciting part – implementing the JavaScript functionality for our shopping list app. Here's the JavaScript code:
//selectors
const addButtonEl = document.querySelector("#add-button");
const emptyListMsg = document.querySelector("#empty-list");
const inputFieldEl = document.querySelector("#input-field");
const clearListButton = document.querySelector("#clear-button");
const toastContainer = document.querySelector("#toast-container");
let shoppingListEl = document.querySelector("#shopping-list");
let isPageReload = false;
addButtonEl.addEventListener("click", function () {
let inputValue = inputFieldEl.value;
if (inputValue !== "") {
addItemToShoppingList(inputValue); // Add item to shopping list
clearInputField();
}
});
// Load initial shopping list from localStorage
loadShoppingList();
function loadShoppingList() {
isPageReload = true;
const shoppingListFromLocalStorage = localStorage.getItem("shoppingList");
if (shoppingListFromLocalStorage) {
let itemsArr = JSON.parse(shoppingListFromLocalStorage);
clearAddToShoppingList();
if (itemsArr.length > 0) {
for (let i = 0; i < itemsArr.length; i++) {
let currentItem = itemsArr[i].value; // Access the value property of the item object
addItemToShoppingList(currentItem);
}
}
}
isPageReload = false;
clearInputField(); // Clear the input field on page load
updateEmptyListState();
}
function clearAddToShoppingList() {
shoppingListEl.innerHTML = "";
updateEmptyListState();
localStorage.removeItem("shoppingList");
}
function clearInputField() {
inputFieldEl.value = "";
}
//Add item to shopping list
function addItemToShoppingList(itemValue) {
let itemId = Date.now().toString(); // Generate a unique ID for the item
let item = {
id: itemId,
value: itemValue,
};
let itemsArr = [];
if (localStorage.getItem("shoppingList")) {
itemsArr = JSON.parse(localStorage.getItem("shoppingList"));
}
// Check if the item already exists in the shopping list
const existingItem = itemsArr.some(
(existingItem) => existingItem.value === itemValue
);
if (existingItem) {
showToast("Item already exists!", true); // Display a toast message indicating the item already exists
return; // Exit the function to avoid adding the item again
}
itemsArr.push(item);
localStorage.setItem("shoppingList", JSON.stringify(itemsArr));
clearInputField(); // Clear the input field
if (!isPageReload) {
showToast("Item added successfully");
}
createItemElement(item);
updateEmptyListState(); // Update the empty list state
}
//Display the shopping list
function createItemElement(item) {
let itemEl = document.createElement("li");
itemEl.textContent = item.value;
itemEl.classList.add("transition"); // Add transition class for smooth effect
itemEl.addEventListener("dblclick", function () {
removeItemFromShoppingList(item.id);
itemEl.remove();
showToast("Item removed successfully!");
updateEmptyListState(); // Update the empty list state
});
// Add fade-in animation when adding item
setTimeout(() => {
itemEl.classList.add("fadeIn");
}, 0); // Add the fade-in class immediately after creating the element
shoppingListEl.append(itemEl);
}
//Remove item from the shopping list
function removeItemFromShoppingList(itemId) {
let itemsArr = [];
if (localStorage.getItem("shoppingList")) {
itemsArr = JSON.parse(localStorage.getItem("shoppingList"));
}
itemsArr = itemsArr.filter((item) => item.id !== itemId);
localStorage.setItem("shoppingList", JSON.stringify(itemsArr));
// Update the empty list state
updateEmptyListState();
}
// show toast message
function showToast(message, isError = false) {
//Array.from() search for an existing toast message with the same content
const existingToast = Array.from(toastContainer.children).find(
(toast) => toast.textContent === message
);
if (existingToast) {
return;
}
const toast = document.createElement("div");
toast.className = `toast ${isError ? " error" : ""}`;
toast.textContent = message;
toastContainer.appendChild(toast);
toast.classList.add("show");
setTimeout(function () {
toast.classList.remove("show");
setTimeout(function () {
toast.remove();
}, 300); // Remove the toast from the DOM after the animation duration
}, 500); // Delay the toast display to ensure smooth animation
}
// mark an item as bought
function markItemAsBought(itemEl) {
if (!itemEl.classList.contains("bought")) {
itemEl.classList.add("bought");
}
}
// Event listener for clicking on an item
shoppingListEl.addEventListener("click", function (event) {
const clickedItem = event.target;
if (clickedItem.tagName === "LI") {
markItemAsBought(clickedItem);
}
});
// Update the empty list state
function updateEmptyListState() {
const shoppingListFromLocalStorage = localStorage.getItem("shoppingList");
const itemsArr = shoppingListFromLocalStorage
? JSON.parse(shoppingListFromLocalStorage)
: null;
if (itemsArr?.length > 0) {
emptyListMsg.style.display = "none";
} else {
emptyListMsg.style.display = "block";
}
}
clearListButton.addEventListener("click", function () {
localStorage.removeItem("shoppingList");
clearAddToShoppingList();
showToast("Cart cleared successfully!");
updateEmptyListState();
});
The JavaScript code is responsible for handling user interactions, managing the shopping list data, and updating the user interface accordingly. We'll break down the code into sections and explain each part in detail.
- Adding Items to the Shopping List: One of the essential features of our app is the ability to add items to the shopping list. Let's start by adding the necessary code:
//Add item to shopping list
function addItemToShoppingList(itemValue) {
let itemId = Date.now().toString(); // Generate a unique ID for the item
let item = {
id: itemId,
value: itemValue,
};
let itemsArr = [];
if (localStorage.getItem("shoppingList")) {
itemsArr = JSON.parse(localStorage.getItem("shoppingList"));
}
// Check if the item already exists in the shopping list
const existingItem = itemsArr.some(
(existingItem) => existingItem.value === itemValue
);
if (existingItem) {
showToast("Item already exists!", true); // Display a toast message indicating the item already exists
return; // Exit the function to avoid adding the item again
}
itemsArr.push(item);
localStorage.setItem("shoppingList", JSON.stringify(itemsArr));
clearInputField(); // Clear the input field
if (!isPageReload) {
showToast("Item added successfully");
}
createItemElement(item);
updateEmptyListState(); // Update the empty list state
}
The addItemToShoppingList
function takes an itemValue as a parameter and adds it to the shopping list. It also handles checking for duplicate items and displays a toast message to notify the user.
- Displaying the Shopping List: Next, let's implement the code to display the shopping list on the screen:
function createItemElement(item) {
let itemEl = document.createElement("li");
itemEl.textContent = item.value;
itemEl.classList.add("transition"); // Add transition class for smooth effect
itemEl.addEventListener("dblclick", function () {
removeItemFromShoppingList(item.id);
itemEl.remove();
showToast("Item removed successfully!");
updateEmptyListState(); // Update the empty list state
});
// Add fade-in animation when adding item
setTimeout(() => {
itemEl.classList.add("fadeIn");
}, 0); // Add the fade-in class immediately after creating the element
shoppingListEl.append(itemEl);
}
The createItemElement
function creates a new list item element for each item in the shopping list and appends it to the DOM. It also handles the removal of items when double-clicked and updates the empty list state.
- Removing Items from the Shopping List: Now, let's add the code to remove items from the shopping list:
//Remove item from the shopping list
function removeItemFromShoppingList(itemId) {
let itemsArr = [];
if (localStorage.getItem("shoppingList")) {
itemsArr = JSON.parse(localStorage.getItem("shoppingList"));
}
itemsArr = itemsArr.filter((item) => item.id !== itemId);
localStorage.setItem("shoppingList", JSON.stringify(itemsArr));
// Update the empty list state
updateEmptyListState();
}
The removeItemFromShoppingList
function removes the item with the specified ID from the shopping list. It also updates the empty list state and displays a toast message to inform the user.
- Updating the Empty List State: To provide a better user experience, let's add code to update the empty list state based on the presence of items in the shopping list:
function updateEmptyListState() {
const shoppingListFromLocalStorage = localStorage.getItem("shoppingList");
const itemsArr = shoppingListFromLocalStorage
? JSON.parse(shoppingListFromLocalStorage)
: null;
if (itemsArr?.length > 0) {
emptyListMsg.style.display = "none";
} else {
emptyListMsg.style.display = "block";
}
}
The updateEmptyListState
function checks if the shopping list is empty and displays a message accordingly.
Creating the Site Manifest
Let's create the site manifest file (site.webmanifest
) and define the necessary configuration. Here's the code for the site.webmanifest
file:
{
"name": "Cart Companion",
"short_name": "CartCom",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#EEF0F4",
"background_color": "#EEF0F9",
"display": "standalone"
}
In the manifest file, we've specified the app's name, short name, icons for different devices, theme color, background color, and display mode. Adjust the icon URLs, theme colors, and other properties as needed to match your app's design. This will help us add the site to our phone home screen.
Conclusion
Congratulations! You've successfully built a simple shopping list app using HTM
L, CSS
, and JavaScript
. Throughout this tutorial, we covered the basics of web development, including HTML structure, CSS styling, and JavaScript functionality. You learned how to add and remove items from the list, display them on the screen, and persist the data using the browser's localStorage
.
Feel free to enhance this app by adding more features such as item quantities, editing functionality, or even integrating it with a backend server.
I hope you enjoyed this beginner-friendly tutorial and gained valuable insights into web development. Start practicing and exploring new ideas to strengthen your skills!
Get Hands-On
Ready to explore and enhance your web development skills further? Try adding new features to the shopping list app or build your own projects. Experiment with different CSS styles, implement search functionality or create a responsive design. The possibilities are endless!
Remember, practice is the key to mastery. So keep coding, exploring, and building amazing web applications.
Top comments (2)
Hey Nwosa, just browsing over the codes, and even though I'm a complete beginner in this field I just want to commend you on working on this project. It would be great to see it live if possible. Also, let's connect!
Thanks Ola for the feedback, I have included the live link.
Here is the live link
cart-companion.netlify.app/
I'm happy you liked it
Some comments may only be visible to logged-in visitors. Sign in to view all comments.