mockshop-project

→ Setup

First, we create a TypeScript project with Vite and clean up unnecessary files:👉 Relevant GitHub code link: https://github.com/snahmd/mockshop/tree/3d2bf168ec89b5985cc3895f11f04b4551094120

npm create vite@latest
npm install
npm run dev


We create the project with these commands.

→API information

https://fakestoreapi.com/

is an API where products are listed. In our project, we will use this API with HTML DOM elements.

We'll see what this API returns in JSON data and structure our project accordingly.

https://app.quicktype.io/

We'll copy the JSON data here, and it will provide us with the necessary type, interface, enum, or whatever type of data we need.

→Interfaces File

We create the necessary type information as interfaces with the same names.

type Rating = {
    rate: number;
    count: number;
}

interface IProduct {
    id: number;
    title: string;
    price: number;
    category: string;
    image: string;
    rating: Rating;
}

export default IProduct;

Here, Rating comes as an object within our Products, so we define it as a separate type and declare it within IProduct.

👉Relevant GitHub code link: https://github.com/snahmd/mockshop/tree/a701656654974b6a6ae5311bb871872cafb263fc

→ Writing the Index.html file

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/src/img/mockshop.png" />
    <script type="module" src="src/main.ts" defer></script>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>MockShop</title>
  </head>
  <body>
    <h1>MockShop</h1>
    <section>
      <input id="itemTitle" type="text" />
      <select name="" id="sortBy">
        <option value="0">Sort By</option>
        <option value="1">⬆️ Lowest Price</option>
        <option value="2">⬇️ Highest Price</option>
        <option value="3">⭐️ Best Rating</option>
      </select>
      <div class="filterCategories">
        <p>Filter by Categories:</p>
        <button id="electronics">Electronics</button>
        <button id="jewelery">Jewelery</button>
        <button id="mens">Men's clothing</button>
        <button id="womens">Women's clothing</button>
      </div>
    </section>

    <section id="itemsSection">
      <div id="itemContainer">
        <img id="img" src="" alt="" />
        <h2 id="productName"></h2>
        <div class="priceCard">
          <h3 id="price"></h3>
          <button id="addToCart">Add to cart</button>
        </div>
      </div>
    </section>
  </body>
</html>


This HTML code creates the main page of an e-commerce website called MockShop. The page includes:

  • Title and favicon
  • Product search, sorting, and category filtering options
  • A section for the product list (containing image, name, price, and add to cart button)

👉Related GitHub code link: https://github.com/snahmd/mockshop/tree/a1e2115acffde5b2f190d10b12171988dc77adab

→ Displaying products from the API in HTML:

First, let's call our interface and select the elements in index.html.

import IProduct from "./interfaces/IProduct";
const titleInput = document.getElementById("itemTitle") as HTMLInputElement;
const sortByButton = document.getElementById("sortBy") as HTMLButtonElement;
const filterElectronicsButton = document.getElementById("electronics") as HTMLButtonElement;
const filterJewelerysButton = document.getElementById("jewelery") as HTMLButtonElement;
const filterMensButton = document.getElementById("mens") as HTMLButtonElement;
const filterWomensButton = document.getElementById("womens") as HTMLButtonElement;
const productsContainer = document.getElementById("itemsSection") as HTMLDivElement;
fetchAndDisplay();

const BASE_URL = "<https://fakestoreapi.com>";
const PRODUCT_URL = `${BASE_URL}/products`;

function fetchAndDisplay(){
  fetch(PRODUCT_URL)
  .then((response: Response) => {
    if(!response.ok){
      throw new Error("Failed to fetch Data");
    }
    return response.json();
  })
  .then((data: IProduct[]) => {
      data.forEach((product: IProduct) => {
        console.log(product.image)
        const cardContainer = document.querySelector("#itemContainer")!.cloneNode(true) as HTMLElement;

        (cardContainer.querySelector("#img") as HTMLImageElement).src = product.image;

        (cardContainer.querySelector("#productName") as HTMLElement).textContent = product.title;

        (cardContainer.querySelector("#price") as HTMLElement).textContent = product.price.toString();

        productsContainer.appendChild(cardContainer);
      })
  })
}


First, it fetches product data from https://fakestoreapi.com/products.

If the data fetching process fails, it throws an error.

If successful, it processes the incoming data in JSON format.

For each product, it performs the following operations:

  • Clones the product template from the HTML.
  • Places the product image, name, and price into the relevant HTML elements.
  • Adds the created product card to the main product container.

This function dynamically adds product cards to the web page, displaying the products to the user.

.cloneNode() is a DOM (Document Object Model) method in JavaScript. This method is used to create a copy of a DOM node. In the code above, this method is used for the following purpose:

  • To create a copy of an existing HTML element (presumably a product card template)
  • To customize this copy for each product and add it to the page

This approach is an effective method for dynamically creating repetitive structures (for example, multiple product cards). Instead of creating a new card for each product, copying a template and changing its content can be more efficient.

2nd Method; an alternative to cloneNode() is:

function fetchAndDisplay() {
  fetch(PRODUCT_URL)
    .then((response) => {
      if (!response.ok) {
        throw new Error("Failed to fetch Data");
      }
      return response.json();
    })
    .then((data: IProduct[]) => {
      data.forEach((product: IProduct) => {
        const cardContainer = document.createElement('div');
        cardContainer.id = 'itemContainer';

        const img = document.createElement('img');
        img.id = 'img';
        img.src = product.image;
        cardContainer.appendChild(img);

        const productName = document.createElement('h2');
        productName.id = 'productName';
        productName.textContent = product.title;
        cardContainer.appendChild(productName);

        const priceCard = document.createElement('div');
        priceCard.className = 'priceCard';

        const price = document.createElement('h3');
        price.id = 'price';
        price.textContent = product.price.toString();
        priceCard.appendChild(price);

        const addToCartButton = document.createElement('button');
        addToCartButton.id = 'addToCart';
        addToCartButton.textContent = 'Add to cart';
        priceCard.appendChild(addToCartButton);

        cardContainer.appendChild(priceCard);

        productsContainer.appendChild(cardContainer);
      });
    });
}

In this approach, we create new DOM elements for each product and connect them to form the product card. This method allows us to achieve a similar result without using cloneNode().

The advantage of this method is that it provides more control over each element. The disadvantage is that it can be slightly slower compared to the cloneNode() method, especially for a large number of products.

→Now the products are being displayed on the HTML page. We are going to perform various sorting and filtering operations, for which we need to write a clear function to delete the values displayed in HTML. We need to send this to the fetchAndDisplay function using setAttribute.

#...

function fetchAndDisplay(){
  fetch(PRODUCT_URL)
  .then((response: Response) => {
   #...
    return response.json();
  })
  .then((data: IProduct[]) => {
      data.forEach((product: IProduct) => {
       #...
    cardContainer.setAttribute("id", "delete");
        productsContainer.appendChild(cardContainer);
       #... 
      })
  })
}

function clearItemCards() {
  const itemCard = document.querySelectorAll("#delete");
  itemCard.forEach((itemCard) => itemCard.remove());
}

This last piece of code serves two important functions:

  1. fetchAndDisplay() function assigns a "delete" ID to each product card. This will be used to easily select and remove these cards later.
  2. clearItemCards() function clears all product cards from the page. This function:
    • Selects all elements with the "delete" ID
    • Iterates through these elements and removes each one from the page

This code is useful when you want to re-sort or filter products, allowing you to clear existing product cards and display newly sorted or filtered products.

👉Related GitHub code link: https://github.com/snahmd/mockshop/tree/fb0ae87e68e3641ffbdee583a445c28154c839fa

Calling Products in a function:

Previously, we were displaying all products on the screen. We need to recreate the products every time we clear. Following the DRY principle, we will create a single printProduct function for products, which we will call when we want to list products again.

function fetchAndDisplay() {
  fetch("<https://fakestoreapi.com/products>")
    .then((response: Response) => {
      if (!response.ok) {
        throw new Error("Failed to fetch Data");
      }
      return response.json();
    })
    .then((data: IProduct[]) => {
      data.forEach((product: IProduct) => {
        printProduct(product);
      });
    });
}

function printProduct(product: IProduct) {
  console.log(product.image);
  const cardContainer = document
    .querySelector("#itemContainer")!
    .cloneNode(true) as HTMLElement;
  (cardContainer.querySelector("#img") as HTMLImageElement).src = product.image;
  (cardContainer.querySelector("#productName") as HTMLElement).textContent =
    product.title;
  (cardContainer.querySelector("#price") as HTMLElement).textContent =
    product.price.toString();
  cardContainer.setAttribute("id", "delete");
  productsContainer.appendChild(cardContainer);
}

We reorganized the forEach loop and called the function.

👉 Related GitHub code link: https://github.com/snahmd/mockshop/tree/a6fe477cda86ec65da590ccbc8af999e83e4d2e7

Sort Logic Section

sortByButton.addEventListener("change", () => {
  clearItemCards();
  fetch("<https://fakestoreapi.com/products>")
    .then((response: Response) => {
      if (!response.ok) {
        throw new Error("Failed to fetch Data");
      }
      return response.json();
    })
    .then((data: IProduct[]) => {
      if (Number(sortByButton.value) === 1) {
        const sortedByLowestPriceItems: IProduct[] = data.sort(
          (a, b) => a.price - b.price
        );
        sortedByLowestPriceItems.forEach((product: IProduct) => {
          printProduct(product);
        });
      } else if (Number(sortByButton.value) === 2) {
        const sortedByHighestPriceItems: IProduct[] = data.sort(
          (a, b) => b.price - a.price
        );
        sortedByHighestPriceItems.forEach((product: IProduct) => {
          printProduct(product);
        });
      } else if (Number(sortByButton.value) === 3) {
        const sortedByBestRatingItems: IProduct[] = data.sort(
          (a, b) => a.rating.rate - b.rating.rate
        );
        sortedByBestRatingItems.forEach((product: IProduct) => {
          printProduct(product);
        });
      }
    });
});


1. Event Listener Definition

The code adds a 'change' event listener to an HTML element named 'sortBySelected' (likely a dropdown menu). This is triggered whenever the user changes the sorting option.

2. Clearing Existing Products

The 'clearItemCards()' function is called. This removes the existing product cards from the page, making room for the newly sorted products.

3. Fetching Data from API

The Fetch API is used to retrieve product data from https://fakestoreapi.com/products. This ensures that the most up-to-date data is used in each sorting operation.

4. Error Checking

The API response is checked for success. If the response is unsuccessful, an error is thrown.

5. Data Processing and Sorting

The data from the API is processed in JSON format and assigned to the 'data' variable. Then, processing is done according to the sorting criterion selected by the user:

  • If the selected value is 1: Products are sorted in ascending order by price (from lowest price to highest).
const sortedByLowestPriceItems: IProduct[] = data.sort(   
(a, b) => a.price - b.price 
);
  • If the selected value is 2: Products are sorted in descending order by price (from highest price to lowest).
const sortedByHighestPriceItems: IProduct[] = data.sort(   
(a, b) => b.price - a.price 
);
  • If the selected value is 3: Products are sorted by rating (from lowest rating to highest).
const sortedByBestRatingItems: IProduct[] = data.sort(   
(a, b) => a.rating.rate - b.rating.rate 
);

6. Displaying Sorted Products

Finally, a loop is run over the sorted product array and the 'printProduct' function is called for each product. This function presumably displays each product on the HTML page.

This code provides the user with the ability to dynamically sort products, thereby improving the user experience and offering a personalized shopping experience.

👉Related GitHub code link: https://github.com/snahmd/mockshop/tree/2998be6faa3120f7774569b722e5763f5cea9deb

→ Filtering Categories:

When the category button is pressed, only the relevant products will appear

filterElectronicsButton.addEventListener("click", () => {
  clearItemCards();
  fetch("<https://fakestoreapi.com/products>")
    .then((response: Response) => {
      if (!response.ok) {
        throw new Error("Failed to fetch Data");
      }
      return response.json();
    })
    .then((data: IProduct[]) => {
      const filteredByCategories: IProduct[] = data.filter(
        (product: IProduct) => product.category === "electronics"
      );
      filteredByCategories.forEach((product: IProduct) => {
        printProduct(product);
      });
    });
});


This code block adds an event listener to filter products in the electronics category and performs the following operations:

  1. An event listener for clicking is added to a button named 'filterElectronicsButton'.
  2. When the button is clicked, existing product cards are first cleared (using the 'clearItemCards()' function).
  3. Then, all products are fetched from the API (from the address 'https://fakestoreapi.com/products').
  4. The API response is checked, and an error is thrown if unsuccessful.
  5. When a successful response is received, the data is processed in JSON format.
  6. Products are filtered to create a new array containing only those with the 'category' property set to 'electronics'.
  7. Finally, the filtered products are processed in a loop, and the 'printProduct' function is called for each to display them on the screen.

This code allows the user to view only products in the electronics category.

Let's do the same for other categories as well:

filterJewelerysButton.addEventListener("click", () => {
  clearItemCards();
  fetch("<https://fakestoreapi.com/products>")
    .then((response: Response) => {
      if (!response.ok) {
        throw new Error("Failed to fetch Data");
      }
      return response.json();
    })
    .then((data: IProduct[]) => {
      const filteredByCategories: IProduct[] = data.filter(
        (product: IProduct) => product.category === "jewelery"
      );
      filteredByCategories.forEach((product: IProduct) => {
        printProduct(product);
      });
    });
});

filterMensButton.addEventListener("click", () => {
  clearItemCards();
  fetch("<https://fakestoreapi.com/products>")
    .then((response: Response) => {
      if (!response.ok) {
        throw new Error("Failed to fetch Data");
      }
      return response.json();
    })
    .then((data: IProduct[]) => {
      const filteredByCategories: IProduct[] = data.filter(
        (product: IProduct) => product.category === "men's clothing"
      );
      filteredByCategories.forEach((product: IProduct) => {
        printProduct(product);
      });
    });
});

filterWomensButton.addEventListener("click", () => {
  clearItemCards();
  fetch("<https://fakestoreapi.com/products>")
    .then((response: Response) => {
      if (!response.ok) {
        throw new Error("Failed to fetch Data");
      }
      return response.json();
    })
    .then((data: IProduct[]) => {
      const filteredByCategories: IProduct[] = data.filter(
        (product: IProduct) => product.category === "women's clothing"
      );
      filteredByCategories.forEach((product: IProduct) => {
        printProduct(product);
      });
    });
});


👉Related GitHub code link: https://github.com/snahmd/mockshop/tree/bc89dade6bd06b0c97546f32c10a0673a6299d0d

Returning Search Results

titleInput.addEventListener("change", () => {
  clearItemCards();
  fetch("<https://fakestoreapi.com/products>")
    .then((response: Response) => {
      if (!response.ok) {
        throw new Error("Failed to fetch Data");
      }
      return response.json();
    })
    .then((data: IProduct[]) => {
      data.forEach((product: IProduct) => {
        if (product.title.includes(titleInput.value)) {
          printProduct(product);
        }
      });
    });
});


This code block provides a search functionality and works as follows:

  1. Event Listener: A 'change' event listener is added to an input element named 'titleInput'. This is triggered when the user types something into the search box and exits the input.
  2. Clearing: The 'clearItemCards()' function is called to clear existing product cards.
  3. API Call: A request is made to the API to fetch all products.
  4. Error Checking: The API response is checked for success.
  5. Data Processing: Data from the API is processed in JSON format.
  6. Filtering: For each product, it checks if the product title contains the text entered by the user.
  7. Display: If the product title contains the search text, the 'printProduct' function is called to display the product.

This code allows users to search for products by their titles.

👉Related GitHub code link: https://github.com/snahmd/mockshop/tree/efce1038b9cac63d33d31002b28b88bb1efeee10

→The project is functioning properly, now we will write the style part. We will use Tailwind CSS.

Applying Style

We will use Tailwind CSS, we're pulling the file with CDN:

...
<script src="<https://cdn.tailwindcss.com>"></script>
...

Then we need to define our custom properties for the Tailwind working environment to recognize them, which means we need to create a config file in the root directory.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/src/img/mockshop-logo.png" />
    <link rel="stylesheet" href="src/styles.css" />
    <script src="<https://cdn.tailwindcss.com>"></script>
    <script type="module" src="src/main.ts" defer></script>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>MockShop</title>
  </head>
  <body class="flex flex-col justify-center items-center bg-sky-100">
    <img
      class="w-[30%] md:w-[25%] xl:w-[15%]"
      src="./src/img/mockshop-logo.png"
      alt="logo"
    />
    <section class="bg-sky-200 w-[100vw] p-4 flex flex-col gap-2">
      <div class="flex gap-2 mx-auto">
        <input class="p-2 bg-sky-100" id="itemTitle" type="text" />
        <select class="p-2 bg-sky-100" name="" id="sortBy">
          <option value="0">Sort By</option>
          <option value="1">⬆️ Lowest Price</option>
          <option value="2">⬇️ Highest Price</option>
          <option value="3">⭐️ Best Rating</option>
        </select>
      </div>
      <div class="filterCategories flex flex-col">
        <p class="text-center text-2xl">Filter by Categories:</p>
        <div
          class="text-sm md:text-xl gap-1 md:gap-4 mx-auto flex items-center flex-wrap text-nowrap"
        >
          <button
            class="border border-slate-600 p-1 rounded-sm"
            id="electronics"
          >
            Electronics
          </button>
          <button class="border border-slate-600 p-1 rounded-sm" id="jewelery">
            Jewelery
          </button>
          <button class="border border-slate-600 p-1 rounded-sm" id="mens">
            Men's clothing
          </button>
          <button class="border border-slate-600 p-1 rounded-sm" id="womens">
            Women's clothing
          </button>
        </div>
      </div>
    </section>

    <section
      class="flex flex-wrap justify-center p-4 gap-4 pb-20"
      id="itemsSection"
    >
      <div
        class="flex flex-col justify-center items-center border border-slate-600 w-[300px] h-[400px] sm:w-[450px] sm:h-[600px] first:hidden"
        id="itemContainer"
      >
        <img
          class="w-[200px] h-[270px] sm:w-[320px] sm:h-[400px] object-cover"
          id="img"
          src=""
          alt=""
        />
        <h2 class="text-center" id="productName"></h2>
        <div class="priceCard flex items-center gap-4">
          <h3 class="text-green-500 text-xl" id="price"></h3>
          <button
            class="bg-sky-200 p-2 rounded-md text-slate-600"
            id="addToCart"
          >
            Add to cart
          </button>
        </div>
      </div>
    </section>
    <footer>
      <p class="text-center text-slate-600 pb-10">
        Made with by
        <a
          class="text-slate-800"
          href="<https://github.com/snahmd/>"
          target="_blank"
          rel="noopener noreferrer"
          >snahmd</a
        >
      </p>
    </footer>
  </body>
</html>


This code snippet shows the structure of an HTML page. I'm explaining the main sections below:

  • The page starts with the standard HTML5 structure.
  • In the head section, there are meta tags, favicon, stylesheet link, Tailwind CSS CDN link, and the main TypeScript file.
  • The body tag is styled using Tailwind CSS classes and organized with a flex structure.
  • The page contains a logo, a search and filtering section, an area for displaying products, and a footer.
  • In the product filtering section, there are buttons for category selection and a dropdown menu for sorting.
  • The section for displaying products is designed with a flexible structure and adjusted according to responsive design principles.
  • In the footer, there's a link to the project creator.

The final version of the site will look like this:

Closing:

In this project, we extracted information from the API and displayed it in html. And we performed filtering, sorting and search operations according to the values coming from this API. See you in the next project or article.

Links to the project are below:

https://github.com/snahmd/mockshop/

Live Version of the Project:

snahmd.github.io/mockshop/

mockshop-app

Leave a reply