How to solve invalid json data using jquery and php on shopping cart

Hi Team

I am trying to add to cart using jquery calls ajax call to update-cart.php, the server side receives invalid json data with 200 status. While js on console.log receives failed to fetch data(view if cart is added once on the shopping cart badge) and failed to update. How can i solve this issue?

jquery code

$(document).ready(function() {
 // Fetch and display the cart
 fetchCart();

 // Add event listener to the "Add To Cart" button
 $('.add-to-cart-btn').click(function(e) {
	 e.preventDefault();
	 var productId = $(this).attr('id').split('-')[1];
	 addToCart(productId);
 });

 // Update the cart on quantity change
 $('.cart-quantity').change(function() {
	 var productId = $(this).attr('data-productId');
	 var quantity = $(this).val();
	 updateCart(productId, quantity);
 });

 // Fetch the cart from the server
 function fetchCart() {
	 $.ajax({
		 url: 'fetch-cart.php',
		 type: 'GET',
		 dataType: 'json',
		 success: function(response) {
			 if (response && response.success) {
				 renderCart(response.cart);
			 } else {
				 console.error('Failed to fetch cart');
			 }
		 },
		 error: function() {
			 console.error('Failed to fetch cart');
		 }
	 });
 }

 // Render the cart on the page
 function renderCart(cart) {
	 var cartItems = '';
	 var totalQuantity = 0;
	 var totalPrice = 0;

	 $.each(cart, function(index, item) {
		 var rowTotal = item.price * item.quantity;
		 cartItems += `
			 <tr>
				 <td>${item.product_name}</td>
				 <td>${item.price}</td>
				 <td>
					 <input type="number" class="cart-quantity" data-productId="${item.id}" value="${item.quantity}" min="1">
				 </td>
				 <td>${rowTotal}</td>
			 </tr>
		 `;
		 totalQuantity += parseInt(item.quantity);
		 totalPrice += rowTotal;
	 });

	 // Update the cart badge
	 $('.badge123').text(totalQuantity);

	 // Update the cart table body
	 $('#cart-table tbody').html(cartItems);

	 // Update the total quantity and price
	 $('#total-quantity').text(totalQuantity);
	 $('#total-price').text(totalPrice);
 }

 // Add a product to the cart
 function addToCart(productId) {
	 $.ajax({
		 url: 'update-cart.php',
		 type: 'POST',
		 dataType: 'json',
		 data: { id: productId, quantity: 1 },
		 success: function(response) {
			 if (response && response.success) {
				 fetchCart();
				 console.log('Cart updated');
			 } else {
				 console.error('Failed to update cart');
			 }
		 },
		 error: function() {
			 console.error('Failed to update cart');
		 }
	 });
 }

 // Update the quantity of a product in the cart
 function updateCart(productId, quantity) {
	 $.ajax({
		 url: 'update-cart.php',
		 type: 'POST',
		 dataType: 'json',
		 data: { id: productId, quantity: quantity },
		 success: function(response) {
			 if (response && response.success) {
				 fetchCart();
				 console.log('Cart updated');
			 } else {
				 console.error('Failed to update cart');
			 }
		 },
		 error: function() {
			 console.error('Failed to update cart');
		 }
	 });
 }
});

html code

<div class="col-lg-4 col-md-6 col-sm-12 pb-1">
  <div class="card product-item border-0 mb-4" id="productId">
    <div class="card-header product-img position-relative overflow-hidden bg-transparent border p-0">
      <img class="img-fluid w-100" src="img/product-1.jpg" alt="" id="product_img">
    </div>
    <div class="card-body border-left border-right text-center p-0 pt-4 pb-3">
      <h6 class="text-truncate mb-3" id="product_name">Colorful Stylish Shirt 0</h6>
      <div class="d-flex justify-content-center">
        <h6>R120.00</h6>
        <h6 class="text-muted ml-2" id="price"><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-0"><i class="fas fa-eye text-primary mr-1"></i>View Detail</a>
      <a href="#" class="btn btn-sm text-dark p-0 add-to-cart-btn" id="add_to_cart-1">
      <i class="fas fa-shopping-cart text-primary mr-1"></i>Add To Cart</a>
    </div>
  </div>
</div>
<div class="col-lg-3 col-6 text-right">
  <a href="#" class="btn border">
  <i class="fas fa-shopping-cart text-primary"></i>
  <span class="badge badge123">0</span>
  </a>
</div>

php code

<?php

// Include your dbconn.php file that contains the PDO connection
$dsn = 'mysql:host=localhost;dbname=ecommerce_store';
$username = 'root';
$password = '';

try {
    // Create a new PDO instance
    $pdo = new PDO($dsn, $username, $password);

    // Set the error mode to exception
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // Retrieve the JSON data from the request
    $data = json_decode(file_get_contents('php://input'), true);

    // Perform the necessary operations to update the cart in the database
    if (is_array($data)) {
        foreach ($data as $item) {
            $id = $item['id'];
            $quantity = $item['quantity'];

            // Update the cart item quantity in the database
            $stmt = $pdo->prepare("UPDATE cart SET quantity = :quantity WHERE id = :id");
            $stmt->bindParam(':quantity', $quantity);
            $stmt->bindParam(':id', $id);
            $stmt->execute();
        }

        // Return a success response
        $response = ['success' => true];
        echo json_encode($response);
    } else {
        // Return an error response if the JSON data is invalid
        $response = ['error' => 'Invalid JSON data'];
        echo json_encode($response);
    }
} catch (PDOException $e) {
    // Return an error response if there's a database connection issue
    $response = ['error' => 'Database connection error: ' . $e->getMessage()];
    echo json_encode($response);
}
?>

First of all @gcobani, to format your code on the forum select your code in the editor and click on the </> button in the text menu to format it. This will wrap the code in opening and closing ticks ```. I have done it this time :slight_smile:

Very much guess work here. If you read the Jquery docs for dataType, the first line says ‘The type of data that you’re expecting back from the server.’

This indicates to me that you will still need to stringify the data you are sending.

Does this make a difference?

function updateCart(productId, quantity) {
     // Stringify data first
     const cartProps = JSON.stringify({ id: productId, quantity: quantity });

	 $.ajax({
		 url: 'update-cart.php',
		 type: 'POST',
		 dataType: 'json',
		 data: cartProps, // stringified data to be sent
		 success: function(response) {
			 if (response && response.success) {
				 fetchCart();
				 console.log('Cart updated');
			 } else {
				 console.error('Failed to update cart');
			 }
		 },
		 error: function() {
			 console.error('Failed to update cart');
		 }
	 });
 }

If this is the fix, then the same would apply to addToCart.

Hi

I have added the logic but i am still experience the same problem both javascript failed to update cart and invalid json data

hmm,

I ran the following test on a local server.

HTML jquery-ajax-test.php

<!DOCTYPE html>
<html lang='en'>
<head>
    <meta charset='UTF-8'>
    <meta http-equiv='X-UA-Compatible' content='IE=edge'>
    <meta name='viewport' content='width=device-width, initial-scale=1.0'>
    <title>Document</title>
</head>
<body>
    <output id='output'></output>
    <script
      src="https://code.jquery.com/jquery-3.7.0.min.js"
      integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g="
      crossorigin="anonymous">
    </script>
    <script src='./js/jquery-ajax-test.js'></script>
</body>
</html>

Javascript jquery-ajax-test.js

function updateCart (productId, quantity) {
    // Stringify data first
    const cartProps = JSON.stringify({ id: productId, quantity })

    $.ajax({
        url: './update-cart.php',
        type: 'POST',
        dataType: 'json',
        data: cartProps, // stringified data to be sent
        success: function (response) {
            if (response?.success) {
                // Output to page
                const output = document.querySelector('#output')

                output.insertAdjacentHTML(
                    'afterbegin',
                    `<pre>${JSON.stringify(response.data, null, 2)}</pre>`
                )
            } else {
                console.error('Failed to update cart')
            }
        },
        error: function () {
            console.error('Failed to update cart')
        }
    })
}

updateCart(2, 5) // test with id of 2 and quantity of 5

PHP update-cart.php (excluded DB connection code)

<?php
// On my localserver works with or without this (maybe worth a try though?)
header('Content-Type: application/json');

// Retrieve the JSON data from the request
$data = json_decode(file_get_contents('php://input'), true);

// Perform the necessary operations to update the cart in the database
if (is_array($data)) {
    // Return a success response with the data
    $response = ['success' => true, 'data' => $data];
    echo json_encode($response);
} else {
    // Return an error response if the JSON data is invalid
    $response = ['error' => 'Invalid JSON data'];
    echo json_encode($response);
}

Output in browser

{
  "id": 2,
  "quantity": 5
}

As mentioned in comments I did add header('Content-Type: application/json'); to my php file, however the code worked with or without that set on my localserver.

Basically you are going to have to reduce the amount of moving parts in your test. Maybe try and do an isolated test with just updateCart and explicitly pass in values, like I have done e.g. updateCart(2, 5)

what… exactly… did the server receive? It may help us isolate the problem if we have all of the information.

Is there a particular reason we’re pushing JSON’s all over the place and trying to read php://input instead of just…yaknow… sending post variables? You’re trying to send 2 bits of information: ID and Quantity. It’s not exactly a massive multi-level object.

its sends invalid json data but when i checked on the tab on payload as mentioned by @rpg_digital i saw response of two ID and quantity as well.

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_ids 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">&nbsp;</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.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.