Search code examples
javascripthtmlmodel-view-controllersingle-page-application

why JavaScript SPA application not rendering


I'm trying to build an SPA application but it's not rendering the "renderItem" and "renderTotalPrice" functions. I will not add the style file note that when I was just using one js file it was rendering when I separated the files into 4 files it stops render the mentioned two functions I know that I need to create a js file for the navbar instead of coding it in the HTML file but for the next step after I solve this issue.

the code is:

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" type="text/css" href="styles.css" />
    <title>Flying Dutchman</title>
  </head>
  <body>
    <!-- Navigation Bar -->
    <nav>
      <ul>
        <!-- Logo -->
        <li id="logo">
          <img src="flying_logo.png" alt="" />
        </li>

        <!-- Navigation Links -->
        <li><a href="#" id="drinksLink">Drinks</a></li>
        <li><a href="#" id="foodLink">Food</a></li>
        <li id="blankSpace"><a href=""></a></li>

        <!-- Login Button -->
        <li><button id="loginBtn">Login</button></li>
      </ul>
    </nav>

    <!-- Main Content Container -->
    <div id="container">
      <!-- Drinks and Foods Section -->
      <div id="drinksFoods">
        <!-- Content for Drinks and Foods will be dynamically populated here -->
      </div>

      <!-- Payment Section -->
      <div id="payment">
        <!-- Content for payment will be dynamically populated here -->
        
      </div>
    </div>

    <!-- JavaScript Controller -->
    <script src="./controller.js"></script>
  </body>
</html>

controller.js:


import model from "./model.js";
import itemView from "./itemView.js";
import paymentPanel from "./paymentPanel.js"; 
// Controller
const controller = {
  /**
   * Controller module responsible for managing user interactions and updating the model and view.
   *
   * @type {Object}
   */

  /**
   * Handles data transfer during dragstart and sets the dragged item.
   *
   * @param {Event} event - The dragstart event.
   * @param {Object} item - The item being dragged.
   */
  dragStart: function (event, item) {
    event.dataTransfer.setData("text/plain", JSON.stringify(item));
  },

  /**
   * Prevents the default behavior to enable drop in the dragenter event.
   *
   * @param {Event} event - The dragenter event.
   */
  dragEnter: function (event) {
    event.preventDefault();
  },
  /**
   * Prevents the default behavior to enable drop in the dragover event.
   *
   * @param {Event} event - The dragover event.
   */

  dragOver: function (event) {
    event.preventDefault();
  },
  /**
   * Handles the drop operation, adding the dropped item to the order summary.
   *
   * @param {Event} event - The drop event.
   */
  drop: function (event) {
    event.preventDefault();

    const data = event.dataTransfer.getData("text/plain");
    const droppedItem = JSON.parse(data);

    model.selectedItems.push(droppedItem);
    model.total += droppedItem.price;
    paymentPanel.renderTotalPrice();
  },
  /**
   * Initializes the application, sets up event listeners, and renders initial content.
   */
  init: function () {
    itemView.renderItems(model.drinkItems, "drinksFoods");
    paymentPanel.renderTotalPrice();

    document
      .getElementById("drinksLink")
      .addEventListener("click", function () {
        itemView.renderItems(model.drinkItems, "drinksFoods");
      });

    document.getElementById("foodLink").addEventListener("click", function () {
      paymentPanel.renderItems(model.foodItems, "drinksFoods");
    });

    document
      .getElementById("checkoutBtn")
      .addEventListener("click", function () {
        alert("Processing payment. Total amount: $" + model.total.toFixed(2));
      });

    const orderSummary = document.getElementById("orderSummary");
    orderSummary.addEventListener("dragenter", controller.dragEnter);
    orderSummary.addEventListener("dragover", controller.dragOver);
    orderSummary.addEventListener("drop", controller.drop);
  },

  /**
   * Adds an item to the cart, updates the model, and triggers a view update.
   *
   * @param {Object} item - The selected item to be added to the cart.
   */
  addToCart: function (item) {
    model.selectedItems.push(item);
    model.total += item.price;
    paymentPanel.renderTotalPrice();
  },
};

export default controller;
// Initialize the application
controller.init();

model.js

const model = {
  /**
   * Initializes the model with data for drink and food items, selected items, and the total cost.
   */
  drinkItems: [
    {
      id: 1,
      name: "Drink 1",
      price: 5.99,
      details: "Refreshing beverage",
    },
    {
      id: 2,
      name: "Drink 2",
      price: 3.99,
      details: "Crisp and cool",
    },
    {
      id: 3,
      name: "Drink 3",
      price: 4.99,
      details: "Satisfying drink",
    },
    {
      id: 4,
      name: "Drink 4",
      price: 6.99,
      details: "Delicious drink",
    },
    {
      id: 5,
      name: "Drink 5",
      price: 7.99,
      details: "Tasty drink",
    },
    {
      id: 6,
      name: "Drink 6",
      price: 8.99,
      details: "Refreshing beverage",
    },
    {
      id: 7,
      name: "Drink 7",
      price: 9.99,
      details: "Crisp and cool",
    },
    {
      id: 8,
      name: "Drink 8",
      price: 10.99,
      details: "Satisfying drink",
    },
    {
      id: 9,
      name: "Drink 9",
      price: 11.99,
      details: "Delicious drink",
    },
    {
      id: 10,
      name: "Drink 10",
      price: 12.99,
      details: "Tasty drink",
    },
    {
      id: 11,
      name: "Drink 11",
      price: 13.99,
      details: "Refreshing beverage",
    },
    {
      id: 12,
      name: "Drink 12",
      price: 14.99,
      details: "Crisp and cool",
    },
    {
      id: 13,
      name: "Drink 13",
      price: 15.99,
      details: "Satisfying drink",
    },
    {
      id: 14,
      name: "Drink 14",
      price: 16.99,
      details: "Delicious drink",
    },
    {
      id: 15,
      name: "Drink 15",
      price: 17.99,
      details: "Tasty drink",
    },
    // Add more items as needed
  ],
  foodItems: [
    { name: "Food 1", price: 7.99, details: "Delicious meal" },
    { name: "Food 2", price: 9.99, details: "Satisfying dish" },
    { name: "Food 3", price: 8.99, details: "Tasty food" },
    { name: "Food 4", price: 10.99, details: "Delicious food" },
    { name: "Food 5", price: 11.99, details: "Satisfying food" },
    { name: "Food 6", price: 12.99, details: "Tasty dish" },
    { name: "Food 7", price: 13.99, details: "Delicious dish" },
    { name: "Food 8", price: 14.99, details: "Satisfying meal" },
    { name: "Food 9", price: 15.99, details: "Tasty meal" },
    { name: "Food 10", price: 16.99, details: "Delicious meal" },
    { name: "Food 11", price: 17.99, details: "Satisfying dish" },
    { name: "Food 12", price: 18.99, details: "Tasty food" },
    { name: "Food 13", price: 19.99, details: "Delicious food" },
    { name: "Food 14", price: 20.99, details: "Satisfying food" },
    { name: "Food 15", price: 21.99, details: "Tasty dish" },
    // Add more food items as needed
  ],
  selectedItems: [],
  total: 0,
};

export default model;

itemView.js

import { controller } from "./controller.js";
import { model } from "./model.js";
const itemView = {
  /**
   * Renders a list of items in a specified container.
   *
   * @param {Object[]} items - The array of items to be rendered.
   * @param {string} containerId - The ID of the container element in the HTML.
   */

  //itemView

  renderItems: function (items, containerId) {
    const container = document.getElementById(containerId);
    container.innerHTML = "";

    items.forEach((item) => {
      const itemBox = document.createElement("div");
      itemBox.classList.add("itemBox");
      itemBox.setAttribute("draggable", true); // Make the item box draggable

      const itemName = document.createElement("p");
      itemName.textContent = `Name: ${item.name}`;

      const itemDetails = document.createElement("p");
      itemDetails.textContent = `Price: $${item.price.toFixed(2)}`;

      const itemDescription = document.createElement("p");
      itemDescription.textContent = `Details: ${item.details}`;

      itemBox.appendChild(itemName);
      itemBox.appendChild(itemDetails);
      itemBox.appendChild(itemDescription);

      itemBox.addEventListener("click", function () {
        controller.addToCart(item);
      });
      itemBox.addEventListener("dragstart", function (event) {
        controller.dragStart(event, item);
      });

      container.appendChild(itemBox);
    });
  },

};

export default itemView;

paymentPanel.js

import { model } from "./model.js";
import { controller } from "./controller.js";
const paymentPanel = {
  /**
   * Renders the total price in the HTML element with the ID "totalPrice".
   */
  // paymentPanel
  renderTotalPrice: function () {
    const paymentPanel = document.getElementById("payment");
    let orderSummary = document.getElementById("orderSummary");
    let totalPriceElement = document.getElementById("totalPrice");
    let checkoutBtn = document.getElementById("checkoutBtn");

    // If orderSummary doesn't exist, create and append it to the paymentPanel
    if (!orderSummary) {
      orderSummary = document.createElement("div");
      orderSummary.textContent = "Order Summary";
      orderSummary.id = "orderSummary";
      paymentPanel.appendChild(orderSummary);
    }

    // Clear existing content in orderSummary
    orderSummary.innerHTML = "";

    // Display selected items with counts in orderSummary
    const itemCounts = {}; // Moved the itemCounts object outside the loop
    const renderedItems = {}; // Keep track of items that have been rendered

    model.selectedItems.forEach((selectedItem) => {
      const itemId = selectedItem.id;

      // If the item is already in the orderSummary, update the count
      if (itemCounts[itemId]) {
        itemCounts[itemId]++;
      } else {
        itemCounts[itemId] = 1;
      }

      // Check if the item has been rendered before
      if (!renderedItems[itemId]) {
        // Create a new element for the item with the correct count
        const selectedItemElement = document.createElement("p");
        selectedItemElement.textContent = `${selectedItem.name} x${
          itemCounts[itemId]
        } - $${selectedItem.price.toFixed(2)}`;
        orderSummary.appendChild(selectedItemElement);

        // Mark the item as rendered
        renderedItems[itemId] = true;
      }
    });

    // Update the content of totalPriceElement
    if (!totalPriceElement) {
      totalPriceElement = document.createElement("p");
      totalPriceElement.id = "totalPrice";
      paymentPanel.appendChild(totalPriceElement);
    }
    totalPriceElement.textContent = `Total: $${model.total.toFixed(2)}`;

    // If checkoutBtn doesn't exist, create and append it to the paymentPanel
    if (!checkoutBtn) {
      checkoutBtn = document.createElement("button");
      checkoutBtn.textContent = "Checkout";
      checkoutBtn.id = "checkoutBtn";
      paymentPanel.appendChild(checkoutBtn);
    }
  },
};

export default paymentPanel;


Solution

  • 1st you need to import your controller as a module: Change the script loading from this:

        <script src="./controller.js"></script>
    

    to this:

        <script type="module">
          import { controller } from './controller.js';
          // Initialize the application
          controller.init();
        </script>
    

    And you need some modifications in your JS files: In the controller.js: Change this const controller = { to this: export const controller = {. Remove these lines at the end of the file:

    export default controller;
    // Initialize the application
    controller.init();
    

    This export type works with functions/classes but you have an object.

    And also change the model.js: Change this const model = { to this: export const model = {. Remove this line at the end of the file:

    export default model;
    

    And the result (without styles because you have not shared them us): Result