I have completed this mockup eCommerce app using Javascript. This version can add products into a 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.
Checkout and login functions are not included.
Appreciate all comments and code review especially on Javascript and coding style and best practices. Thanks a lot in advance.
// Initialization of data and page variables - start.
// For actual system, get data from database via API call either in JSON format.
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 }
];
var cart = [];
cart.length = 0;
$shoppingCartContainer = document.getElementById("shoppingCartContainer");
$clearAll = document.getElementById("clearAll");
$shoppingCart = document.getElementById("shoppingCart");
$totalCartItems = document.getElementById("totalCartItems");
$summary = document.getElementById("summary");
// Initialization of data and page variables - end.
// Functions - start -------------------------------------
const createCartHTMLElements = object => {
// Check type
if (typeof object !== "object") return false;
// Start our HTML
var html = "<table><tbody>";
// Loop through members of the object
debugger;
object.forEach(function(item) {
html += `<tr class=productData><td class="productName">${item.product}</td>\
<td class="productPrice">${item.unitprice.toFixed(2)}</td>\
<td class="quantityProduct">\
<button class="plus-btn" data-id="${item.id}">+</button>\
<label class="quantity">${item.quantity}</label>\
<button class="minus-btn" data-id="${item.id}">-</button>\
</td>\
<td class="total">${item.total.toFixed(2)}</td>\
<td class="deleteProduct"><i class="fa fa-remove del" data-id="${
item.id
}"></i></td>\
</tr>`;
});
// Finish the table:
html += "</tbody></table>";
// Return the table
return html;
};
const updateQuantity = (operation, productId, tr) => {
// Update the quantity in UI
let $quantity = tr.find(".quantity");
let n = $quantity.html();
let i;
switch (operation) {
case "plus":
i = parseInt(n) + 1;
break;
case "minus":
i = parseInt(n) - 1;
if (i < 0) i = 0; // prevent negative quantity
if (i == 0) {
// Duplicate code with delete function
cart = cart.filter(function(el) {
return el.id != productId;
});
if (cart.length === 0) {
$shoppingCart.innerHTML = "";
debugger;
$clearAll.style.display = "none";
$summary.style.display = "none";
}
updateCartCount();
updateOrderSummary();
tr.closest("tr").remove();
}
break;
}
$quantity.html(i);
// Update the total price in UI
let $price = tr.find(".productPrice");
let price = parseFloat($price.html());
let $total = tr.find(".total");
let total = i * price;
$total.html(total.toFixed(2));
// Update the quantity and total in list object
// Find index of specific object using findIndex method.
let objIndex = cart.findIndex(obj => obj.id == productId);
if (objIndex >= 0) {
// Update object's name property.
cart[objIndex].quantity = i;
cart[objIndex].total = total;
updateOrderSummary();
}
};
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 = () => {
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);
};
const updateCartCount = () => {
$totalCartItems.innerHTML = cart.length;
};
// Functions - End -------------------------------------
// Event listener - start -------------------------------------
const CreateAddToCartEventListener = () => {
var addToCart = document.getElementsByClassName("AddToCart");
updateCartCount();
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) {
debugger;
// Update the quantity in list object
// Find index of specific object using findIndex method.
let objIndex = cart.findIndex(
obj => obj.id == parseInt(element.dataset.id)
);
if (objIndex >= 0) {
// Old item found
cart[objIndex].quantity = cart[objIndex].quantity + 1;
cart[objIndex].total = cart[objIndex].quantity * prod.unitprice;
} else {
// For new item
prod.quantity = 1;
prod.total = prod.unitprice;
cart.push(prod);
}
$shoppingCart.innerHTML = createCartHTMLElements(cart);
CreateDeleteEventListener();
$totalCartItems.style.display = "block";
$clearAll.style.display = "block";
$summary.style.display = "block";
updateCartCount();
updateOrderSummary();
return;
}
});
});
});
};
const CreateDeleteEventListener = () => {
var del = document.getElementsByClassName("del");
Array.prototype.forEach.call(del, function(element) {
element.addEventListener("click", function() {
// Duplicate code with minus quantity function
// When quantity is zero, it will delete that item
cart = cart.filter(function(el) {
return el.id != element.dataset.id;
});
if (cart.length === 0) {
$shoppingCart.innerHTML = "";
debugger;
$clearAll.style.display = "none";
$summary.style.display = "none";
}
updateCartCount();
updateOrderSummary();
element.closest("tr").remove();
});
});
};
$(document.body).on("click", ".plus-btn", function() {
let productId = $(this).attr("data-id");
let $tr = $(this).closest("tr");
updateQuantity("plus", productId, $tr);
});
$(document.body).on("click", ".minus-btn", function() {
let productId = $(this).attr("data-id");
let $tr = $(this).closest("tr");
updateQuantity("minus", productId, $tr);
});
$clearAll.addEventListener("click", function() {
$shoppingCart.innerHTML = "";
cart.length = 0;
$clearAll.style.display = "none";
$summary.style.display = "none";
updateOrderSummary();
updateCartCount();
});
document.getElementById("cartIcon").addEventListener("click", function() {
if ($shoppingCartContainer.style.display === "none") {
$shoppingCartContainer.style.display = "block";
} else {
$shoppingCartContainer.style.display = "none";
}
});
window.addEventListener("load", function() {
$shoppingCartContainer.style.display = "none";
window.setTimeout(function() {}, 1000); // prevent flickering
document.getElementById("productRow").innerHTML = populateProducts(
productList
);
CreateAddToCartEventListener();
});
// Event listener - end -------------------------------------
.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;
}
.productContainer {
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;
padding-right: 30px;
}
#right {
text-align: right;
position: relative;
}
#main {
max-width: 1000px;
border: 1px solid black;
margin: 0 auto;
position: relative;
}
#shoppingCartContainer {
width: 400px;
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;
}
#totalCartItems {
margin: 0px 0px -2px -7px;
color: red;
padding: 2px;
border-radius: 25px;
width: 10px;
margin-left: auto;
font-size: 1em;
position: absolute;
}
.plus-btn img {
margin-top: 2px;
}
tr.productData,
td.productName,
td.productPrice,
td.quantityProduct,
td.price,
td.deleteProduct {
padding: 10px;
}
<!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>
<span id="totalCartItems"></span>
</div>
</div>
<hr />
<div class="productContainer">
<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="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="app.js"></script>