Build a Simple Shopping List App with JavaScript
Tags: webdev, javascript, beginners, tutorial
Published: 2023-06-11 15:28:05

##Introduction:
In this tutorial, we'll walk through the process of building a simple shopping list app using JavaScript. 

![writing list](https://c.tenor.com/UxTmlMq2lgMAAAAd/writing-notes.gif)

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.

Happy coding!
![Happiness](http://www.reactiongifs.com/r/hsk.gif)