1
\$\begingroup\$

I have completed this mockup eCommerce app using pure Vanilla Javascript. This version can add product into shopping cart and automatically calculate the order summary in the shopping cart. It can also delete one item and all items in the shopping cart.

Certain function like checkout,payment,login,update quantity in cart are not included.

Known bugs are duplicate item will be still added when you add same product into cart.

Appreciate all comments and code review especially on Javascript and coding style and best practices. Thanks a lot in advance.

// Bugs
// 1. If a product already added into shopping cart, and add again.
// there will be duplicate.

var productList = [
  { id: 101, product: "Logitech Mouse", unitprice: 45.0 },
  { id: 102, product: "Logitech Keyboard", unitprice: 50.0 },
  { id: 103, product: "HP Mouse", unitprice: 35.0 },
  { id: 104, product: "HP Keyboard", unitprice: 32.0 },
  { id: 105, product: "Microsoft Mouse", unitprice: 43.0 },
  { id: 106, product: "Microsoft Keyboard", unitprice: 39.0 }
];

var cart = [];

const createCartHTMLElements = object => {
  // Check type
  if (typeof object !== "object") return false;

  // Start our HTML
  var html =
    "<table><tbody><tr>\
            <td>Product</td>\
            <td>Price</td>\
            <td>Unit</td>\
            <td>Total</td>\
            <td></td></tr>";

  // Loop through members of the object
  object.forEach(function(item) {
    html += `<tr><td>${item.product}</td>\
                <td>${item.unitprice.toFixed(2)}</td>\
                <td><button>+</button>${item.quantity}<button>-</button></td>\
                <td>${item.total.toFixed(2)}</td>\
                <td><i class="fa fa-remove del" data-id="${item.id}"></i></td>\
                </tr>`;
  });

  // Finish the table:
  html += "</tbody></table>";

  // Return the table
  return html;
};

document.getElementById("clearAll").addEventListener(
  "click",
  function() {
    document.getElementById("shoppingCart").innerHTML = "";
    cart.length = 0;
    document.getElementById("clearAll").style.display = "none";
    document.getElementById("summary").style.display = "none";
    updateOrderSummary();
  },
  false
);
// false means preventdefault.
// sometimes by default, it will trigger other thing

const populateProducts = arrOfObjects => {
  // Start our HTML
  var html = "";

  // Loop through members of the object
  arrOfObjects.forEach(function(item) {
    html += `<div class="column"><div class="card">\
                <h2>${item.product}</h2>
                <p class="price">RM ${item.unitprice.toFixed(2)}</p>
                <p><button class=AddToCart data-id="${
                  item.id
                }">Add to Cart</button></p>\
                </div></div>`;
  });

  return html;
};

const updateOrderSummary = () => {
  //if (cart.length > 0) {
  document.getElementById("totalItem").innerHTML = cart.length + " item";

  const subTotal = cart
    .reduce(function(acc, obj) {
      return acc + obj.total;
    }, 0)
    .toFixed(2);

  const shippingFee = 10;

  document.getElementById("subTotal").innerHTML = subTotal;
  document.getElementById("shippingFee").innerHTML = shippingFee.toFixed(2);
  document.getElementById("total").innerHTML = (
    parseInt(subTotal) + shippingFee
  ).toFixed(2);
  //}
};

window.addEventListener("load", function() {
  document.getElementById("shoppingCartContainer").style.display = "none";
  window.setTimeout(function() {}, 1000); // prevent flickering
  document.getElementById("productRow").innerHTML = populateProducts(
    productList
  );

  var addToCart = document.getElementsByClassName("AddToCart");

  Array.prototype.forEach.call(addToCart, function(element) {
    element.addEventListener("click", function() {
      debugger;
      // Filter the selected "AddToCart" product from the ProductList list object.
      // And push the selected single product into shopping cart list object.
      productList.filter(prod => {
        if (prod.id == element.dataset.id) {
          prod.quantity = 1;
          prod.total = prod.unitprice;
          cart.push(prod);
          document.getElementById(
            "shoppingCart"
          ).innerHTML = createCartHTMLElements(cart);

          CreateDeleteEventListener();

          document.getElementById("clearAll").style.display = "block";
          document.getElementById("summary").style.display = "block";
          updateOrderSummary();
          return;
        }
      });
    });
  });

  function CreateDeleteEventListener() {
    var del = document.getElementsByClassName("del");

    Array.prototype.forEach.call(del, function(element) {
      element.addEventListener("click", function() {
        debugger;
        console.log(element.dataset.id);


        cart = cart.filter(function(el) {
           return el.id != element.dataset.id;
        });

        if (cart.length === 0) {
          document.getElementById("shoppingCart").innerHTML = "";
          debugger;
          document.getElementById("clearAll").style.display = "none";
          document.getElementById("summary").style.display = "none";
        }
        updateOrderSummary();

        element.closest("tr").remove();

      });
    });
  }
});

document.getElementById("cartIcon").addEventListener("click", function() {
  var x = document.getElementById("shoppingCartContainer");
  if (x.style.display === "none") {
    x.style.display = "block";
  } else {
    x.style.display = "none";
  }
});
.card {
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
  margin: auto;
  text-align: center;
  font-family: arial;
  width: 18em;
}

.price {
  color: grey;
  font-size: 1.5em;
}

.card button {
  border: none;
  outline: 0;
  padding: 12px;
  color: white;
  background-color: #000;
  text-align: center;
  cursor: pointer;
  width: 100%;
  font-size: 1em;
}

.card button:hover {
  opacity: 0.7;
}

.some-page-wrapper {
  margin: 15px;
}

.summaryDetails {
  width: 100px;
  text-align: right;
}

#productRow {
  display: flex;
  flex-wrap: wrap;
}

.column {
  flex: 1;
  margin-top: 12px;
}

@media (max-width: 1333px) {
  .column {
    flex-basis: 33.33%;
  }
}

@media (max-width: 1073px) {
  .column {
    flex-basis: 33.33%;
  }
}

@media (max-width: 815px) {
  .column {
    flex-basis: 50%;
  }
}

@media (max-width: 555px) {
  .column {
    flex-basis: 100%;
  }
}

#header {
  width: 100%;
  display: flex;
  justify-content: space-between;
  height: 50px;
}

#left,
#right {
  width: 30%;
  padding: 10px;
}

#right {
  text-align: right;
}

#main {
  max-width: 1000px;
  border: 1px solid black;
  margin: 0 auto;
  position: relative;
}

#shoppingCartContainer {
  width: 300px;
  background-color: lightyellow;

  border: 1px solid silver;
  margin-left: auto;
  position: absolute;
  top: 50px;
  right: 0;
  padding: 10px;
}

.fa-shopping-cart {
  font-size: 36px;
  color: blue;
  cursor: pointer;
}

.fa-remove {
  font-size: 24px;
  color: red;
  cursor: pointer;
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
    />

    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div id="main">
      <div id="header">
        <div id="left">Login as Joe Doe, <a href="">Logout</a></div>
        <div id="right">
          <i id="cartIcon" class="fa fa-shopping-cart"></i>
        </div>
      </div>

      <hr />
      <div class="some-page-wrapper">
        <div id="productRow"></div>
      </div>          

      <div id="shoppingCartContainer">
        <div id="shoppingCart"></div>
        <button id="clearAll">Clear Cart</button>

        <div id="summary">
          <hr />
          <h3>Order Summary</h3>
          <table>
            <tr>
              <td>Subtotal (<span id="totalItem"></span>)</td>
              <td class="summaryDetails"><span id="subTotal"></span></td>
            </tr>
            <tr>
              <td>Shipping Fee</td>
              <td class="summaryDetails"><span id="shippingFee"></span></td>
            </tr>
            <tr>
              <td>Total</td>
              <td class="summaryDetails"><span id="total"></span></td>
            </tr>
          </table>

          <button>Proceed to checkout</button>
        </div>
      </div>
    </div>
  </body>
</html>
<script src="app.js"></script>

\$\endgroup\$
  • 3
    \$\begingroup\$ @Toby Speight This question is attracting close votes because there are known bugs in it. Code should be working as intended in order to be on-topic for this site. On the other hand, if you hadn't mentioned the bugs, there would be no issue. You are being honest, I give you that. This is the best meta post I could find (codereview.meta.stackexchange.com/a/8581/200620). \$\endgroup\$ – dfhwze Sep 9 '19 at 7:27
  • 1
    \$\begingroup\$ Rolled back to a previous edit; the fix to having bugs isn't to not tell us, its to resolve the bugs and edit the question. If the bug is not actually a bug (e.g. a feature that you have left out of scope for your current version), then you should clarify that instead. \$\endgroup\$ – Dannnno Sep 9 '19 at 19:25
  • \$\begingroup\$ I have fixed the bug. Should I update it or create a new post? \$\endgroup\$ – Steve Ngai Sep 10 '19 at 2:08
  • 1
    \$\begingroup\$ It's better to edit and make an off-topic question on-topic than to have two questions, one of which needs to be closed and deleted. \$\endgroup\$ – Peter Taylor Sep 11 '19 at 11:25

Browse other questions tagged or ask your own question.