Well this must be a school assignment as we are seeing almost the same code from different people…
We’re looking at something like this where you have items listed on the page, you can add items to the cart and they are displayed in a cart table.
So you need to take things one line at a time and go through each item understanding that any error ANYWHERE in the Javascript or related to references or calls made will kill the whole thing.
Do yourself a favor and create a products
table with the fields id
, name
, price
and image
then create a few records for testing. You can then make a query and turn your product display into a real life example with IDs and fields using result values.
<?php
$sql = "SELECT
id
, name
, price
, image
FROM `products`
ORDER BY id ASC";
$query = $conn->prepare($sql);
$query->execute();
while($row = $query->fetch(PDO::FETCH_ASSOC)){
echo '<div class="col-lg-4 col-md-6 col-sm-12 pb-1">
<div class="card product-item border-0 mb-4" id="'.$row['id'].'">
<div class="card-header product-img position-relative overflow-hidden bg-transparent border p-0">
<img class="img-fluid w-100" src="img/'.$row['image'].'" alt="" id="product_img'.$row['id'].'">
</div>
<div class="card-body border-left border-right text-center p-0 pt-4 pb-3 body'.$row['id'].'">
<h6 class="text-truncate mb-3" id="name'.$row['id'].'">'.$row['name'].'</h6>
<div class="d-flex justify-content-center">
<h6>R<span id="price'.$row['id'].'">'.$row['price'].'</span></h6><h6 class="text-muted ml-2"><del>R120.00</del></h6>
</div>
</div>
<div class="card-footer d-flex justify-content-between bg-light border">
<a href="#" class="btn btn-sm text-dark p-0 view-details-btn" id="cart-'.$row['id'].'"><i class="fas fa-eye text-primary mr-1"></i>View Detail</a>
<a href="#" class="btn btn-sm text-dark p-0 remove-item-cart-btn" id="cart-'.$row['id'].'"><i class="fas fa-shopping-cart text-primary mr-1"></i>Remove Item</a>
<a href="#" class="btn btn-sm text-dark p-0 add-to-cart-btn" id="add_to_cart-'.$row['id'].'">
<i class="fas fa-shopping-cart text-primary mr-1"></i>Add To Cart</a>
<i class="fas fa-shopping-cart text-primary mr-1"></i><span class="badge badge'.$row['id'].'"></span>
</div>
</div>
</div>'."\r";
}
?>
You will notice some changes to the product display as IDs need to be unique so make them both unique and identifiable I added the product ID to the id like
id="name'.$row['id'].'"
and you will notice on the price I didn’t want the currency sign and used <span>
to enclose the price so only the price can be picked up.
<h6>R<span id="price'.$row['id'].'">'.$row['price'].'</span></h6>
Now lets look at the javascript.
The $(document).ready(function() {
I am bring this up to point out that this is only run when the page is loaded and pertains to the items that are on the page When the page is loaded… This will be important.
The next line is fetchCart();
and as there are no items (yet) in your cart
table we will comment this line out for now. There are many things both in php and html that need to be right before this fumction call can be used. So what we are doing is commenting out each section so only ONE part can be tested one at a time.
$('.add-to-cart-btn').click(function(e) {
What you have is fine but you need more than the product ID to add an item to the cart table. But now that product ID was added to those ids in the display section, we can now grab the product name and price defining those id attributes the same way, then using getElementById
. Now at this point you do not plunge forward and call your next function addToCart()
, but instead comment that function and use alert()
to check that values are as expected. When successful go to the next step.
$('.add-to-cart-btn').click(function(e) {
e.preventDefault();
var productId = $(this).attr('id').split('-')[1];
var name = document.getElementById("name" + productId).innerHTML;
var price = document.getElementById("price" + productId).innerHTML;
alert(productId);
alert(name);
alert(price);
//addToCart(productId,name,price);
});
You will notice that I added the name
and price
values to the addToCart(productId,name,price);
function.
// Add a product to the cart
function addToCart(productId,name,price) {
$.ajax({
url: 'update-cart.php',
type: 'POST',
dataType: 'json',
data: { id: productId, name: name, price: price, quantity: 1 , mode: "add_item"},
success: function(response) {
if (response && response.success) {
/*
fetchCart();
// Update the cart badge
var qty = response.qty;
$('.badge'+productId).text(qty);
*/
} else {
console.error('Failed to update cart');
}
},
error: function() {
console.error('Failed to update cart');
}
});
}
The name
and price
can then be picked up in the function and added them to the data:
section. There are also several calls to update-cart.php
and each processing section needs to be handled differently so I define a field called mode:
and the action we are going to do as add_item
. You’ll notice that I again have some sections commented out in the SUCCESS section as we test each part one at a time.
Now on update-cart.php
we are not dealing with JSON data as it was sent with type: 'POST'
so you pick it up like you would any POST data. I start by looking for request method POST and that $_POST['id']
is not empty as we need that in all processing sections. I keep your try catch and connection code and the code where the response is echoed, then exit;
It is within this section where we place all the processing code.
As a controlling factor I want to query the cart table for the item_id
s that are in the cart and place these in an array called $cartitems
. More on this in a moment.
I then write the first processing condition looking for quantity and mode add_item
and to make sure $_POST['id']
is not in the $cartitems
array.
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['id'])) {
try {
// Connect to the database
$conn = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//cartitems
$cartitems = array();
$querycartitems = $conn->prepare("SELECT `item_id` FROM `cart`");
$querycartitems->execute();
while($row = $querycartitems->fetch(PDO::FETCH_ASSOC)){
$cartitems[] = $row['item_id'];
}
// Add New Item
if(isset($_POST['mode']) && !empty($_POST['quantity']) && !in_array($_POST['id'], $cartitems) && $_POST['mode'] == "add_item"):
endif;
// Return a success response
echo json_encode($response);
exit; // Stop executing further code
} catch (PDOException $e) {
// Return an error response
$response = ['success' => false, 'error' => $e->getMessage()];
echo json_encode($response);
exit; // Stop executing further code
}
}
We need to take a moment to talk about the cart table. I won’t even get into the fact that there should be an order ID (the ID from the order
table) that separates this order from the next, but it must be noted that id
in the cart table is the auto increment id
for this cart
record. It would NOT be the ID
from the products
table. It will be important to keep the item and cart id’s straight moving forward so I defined my cart fields as
`id`, `item_id`, `name`, `price`, `quantity`
SO, the INSERT query will use that item_id
field instead of the id
field.
INSERT INTO `cart`(`item_id`, `name`, `price`, `quantity`) VALUES (:itemId,:name,:price,:quantity)
I prepare and bind the values much like you did only adding the name and price values as well and execute the query. In the response code I want to also pass back the quantity so I can be used for updating the badges. I define 'qty'
as the KEY and $_POST['quantity']
as the value. This section looks like this.
// Add New Item
if(isset($_POST['mode']) && !empty($_POST['quantity']) && $_POST['mode'] == "add_item"):
$stmt = $conn->prepare("INSERT INTO `cart`(`item_id`, `name`, `price`, `quantity`) VALUES (:itemId,:name,:price,:quantity)");
$stmt->bindParam(':itemId', $_POST['id']);
$stmt->bindParam(':name', $_POST['name']);
$stmt->bindParam(':price', $_POST['price']);
$stmt->bindParam(':quantity', $_POST['quantity']);
$stmt->execute();
$response = ['success' => true, 'qty' => $_POST['quantity']];
endif;
At this stage you should be able add items into your cart, checking phpmyadmin for new records. It is relatively simple at this point to get that Remove Item
button working as it is a simplified version of the add to cart code in that you define the itemId
and call the function to send the request.
//Remove Item from Cart
$('.remove-item-cart-btn').click(function(e) {
e.preventDefault();
var itemId = $(this).attr('id').split('-')[1];
removeCartItem(itemId);
//$('.badge'+itemId).text(null);
});
In the function we define the mode:
as remove_item
.
//remove Cart Item
function removeCartItem(itemId) {
$.ajax({
url: 'update-cart.php',
type: 'POST',
dataType: 'json',
data: { id: itemId, mode: "remove_item" },
success: function(response) {
if (response && response.success) {
//fetchCart();
} else {
console.error('Failed to update cart');
}
},
error: function() {
console.error('Failed to update cart');
}
});
}
On update-cart.php
we can then write an IF condition looking for mode remove_item
and delete the record.
// Remove Item
if(isset($_POST['mode']) && $_POST['mode'] == "remove_item"):
$stmt = $conn->prepare("DELETE FROM cart WHERE item_id = :itemId");
$stmt->bindParam(':itemId', $_POST['id']);
$stmt->execute();
$response = ['success' => true];
endif;
Now it’s time to deal with fetch cart. THIS is the section where all the related JS functions, php and html need to be correctly defined or the whole thing stops working, which is why we started by commenting the fetchCart(); out. Now the fetchCart()
function is straight forward.
// Fetch the cart from the server
function fetchCart() {
$.ajax({
url: 'fetch-cart.php',
dataType: 'json',
success: function(response) {
if(response && response.success) {
//updateBadges(response.badges);
//renderCart(response.cart);
} else {
console.error('Failed to fetch cart');
}
}
});
}
Note the response
is looking for success
, cart
and I also added badges
so these response KEYS need to be defined on the fetch-cart.php
page. As we are grabbing cart data I opted to name the array $cart and set it as an array. I then defined both the cart
and badge
keys as arrays so these keys would be available even if there were no records in the database. I then query the cart
table and build the $cart['cart']
array using the same fields that were defined in the renderCart
function. Remember, any small part the doesn’t match will cause an error. Now I could have just changed the $cart['cart']
building and the renderCart
function code to use item.name
instead of item.product_name
but I wanted to point out that these are the kinds of things that can make your code fail.
As the badges are part of the product section and not the cart, the badges correspond to the item_id
that was saved in the cart
record, so defining $cart['badges'] groups the
item_id` with the quantity.
$cart = array();
$cart['cart'] = array();
$cart['badges'] = array();
$stmt = $conn->prepare("SELECT * FROM `cart` ORDER BY item_id ASC");
$stmt->execute();
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
$cart['cart'][] = array(
'id' => $row['id'],
'product_name' => $row['name'],
'price' => $row['price'],
'quantity' => $row['quantity']
);
$cart['badges'][] = array(
'item_id' => $row['item_id'],
'quantity' => $row['quantity']
);
}
$cart['success'] = true;
echo json_encode($cart);
exit;
Looking back at the fetchCart
function, the response.success
section calls 2 functions. The updateBadges
function is straight forward as we check the length to get the number of records returned and then use each()
loop through those records defining the item_id
and quantity
and applying it to update the text
inside the badge element with the matching “class.id”.
// Update Badges
function updateBadges(badges) {
if(badges.length>0){
$.each(badges, function(index, badge) {
var badgeID = badge.item_id;
var badgeQty = badge.quantity;
$('.badge'+badgeID).text(badgeQty);
});
}
}
It took me awhile to get renderCart
function to work. It was pretty clear that looking at
// Update the cart table body
$('#cart-table tbody').html(cartItems);
that cartItems
was going to be rendered in the <tbody>
tag of a table with id="cart-table"
. The values of total quantity and price are also assigned to specific ids so this table needs to be made as specified for the javascript to work. Further styling can be done but the basics is like this.
<table cellpadding="0" cellspacing="0" border="0" id="cart-table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td colspan="2"> </td>
<td id="total-quantity"></td>
<td id="total-price" style="font-weight:bold;"></td>
</tr>
</tfoot>
</table>
Even after this, I was not able to get the renderCart
function to work. It was only after removing all line returns, tabbing and spacing from cartItems
building was I able to get things to work.
// Render the cart on the page
function renderCart(cart) {
var cartItems = '';
var totalQuantity = 0;
var totalPrice = 0;
var rows = cart.length;
if(rows > 0){
$.each(cart, function(index, item) {
var price = item.price;
var rowTotal = item.price * item.quantity;
cartItems += '<tr><td>'+item.product_name+'</td><td>R'+item.price+'</td><td><input type="number" class="cart-quantity" data-productId="'+item.id+'" value="'+item.quantity+'" min="1"></td><td>R'+rowTotal+'</td></tr>';
totalQuantity += parseInt(item.quantity);
totalPrice += rowTotal;
});
}
// Update the cart table body
$('#cart-table tbody').html(cartItems);
// Update the total quantity and price
$('#total-quantity').text(totalQuantity);
$('#total-price').text('R' + totalPrice);
}
Now that the cart table is working we now come to quantity change.
// Update the cart on quantity change
$('.cart-quantity').change(function()
This doesn’t work. Why? because the $(document).ready(function()
only recognizes things that were on the page when it was first rendered, and all those cart rows with the quantity inputs were added later. But a “Document On Change identified by class” will be triggered when the input with this class name is changed. So at the very bottom of my javascript I added this to call the updateCart
function.
//on quantity change
$(document).on("change", ".cart-quantity", function(){
var productId = $(this).attr('data-productId');
var quantity = $(this).val();
// Update the cart on quantity change
updateCart(productId, quantity);
});
And in the updateCart
function we again set data:
with the id, quantity and mode, this time as update_qty
.
// Update the quantity of a product in the cart
function updateCart(cartId, quantity) {
$.ajax({
url: 'update-cart.php',
type: 'POST',
dataType: 'json',
data: { id: cartId, quantity: quantity , mode: "update_qty" },
success: function(response) {
if (response && response.success) {
fetchCart();
} else {
console.error('Failed to update cart');
}
},
error: function() {
console.error('Failed to update cart');
}
});
}
As you know, in the cart display table the ID being used to identify these records is the id
from the cart
table, where most of the other functions the id
was from the products display of products
records. So on update-cart.php
page we again use the mode to isolate this section of processing to update the quantity with the id
.
// Update Quantity
if(isset($_POST['mode']) && !empty($_POST['quantity']) && $_POST['mode'] == "update_qty"):
$stmt = $conn->prepare("UPDATE cart SET quantity = :quantity WHERE id = :cartId");
$stmt->bindParam(':quantity', $_POST['quantity']);
$stmt->bindParam(':cartId', $_POST['id']);
$stmt->execute();
$response = ['success' => true, 'qty' => $_POST['quantity']];
endif;
With everything defined you should be able to un-comment all fetchCart(); and
updateBadges` lines. I think I might get a decent grade on my homework project.