I’m having trouble getting the products quantity to reflect the total amount in the razor page. I want the client have the ability to add/remove products when they’re in the cart to dynamically change the quantity of it instead of repeatedly removing said product one by one. The closest I got it to work is to have the on click for the input to detect but obviously as it is written I don’t have the ability to decrease the product by one or how many they want to change. I have tried onchange but nothing seems to work on that onchange event. Also the total does not reflect the current amount as it should. To my understanding the way it is written should reflect the new total with this ajax query call (See photos below). Here is the code that follows:
Cart.cshtml
@using Project.ViewModel
@model CartsViewModel
<div class="row">
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Product Name</th>
<th scope="col">Quantity</th>
<th scope="col">Price</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@foreach(Cart items in @Model.CartItems)
{
<tr>
<td>@items.Product.ProductName</td>
<td><input type="number" data-id="@items.ProductId" class="product-amount" value="@items.Count" /></td>
@* <td><input type="number" data-id="@items.ProductId" class="product-amount" value="@items.Count" onchange="amountChange(this)"/></td> *@
<td>[email protected] ea.</td>
<td>
<a class="btn btn-primary" asp-controller="ShoppingCart" asp-action="RemoveFromCart" asp-route-id="@items.ProductId">Remove</a>
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="row">
<div class="col">
<h6 class="text-end">Total: [email protected]</h6>
</div>
</div>
<script src="~/lib/jquery/dist/jquery.js" type="text/javascript"></script>
<script>
var productAmount = document.getElementsByClassName('product-amount')
for(var prod in productAmount){
productAmount[prod].onclick = function(data){
var productId = $(this).attr("data-id");
if(productId != ''){
$.post("/ShoppingCart/AddToCart", {"id": productId},
function(data){
console.log("Add to Cart Successful")
})
$.get("/ShoppingCart/Cart", function(data, status){
console.log(data)
console.log(status)
})
}
}
}
</script>
As reference for the of the controller
ShoppingCartController.cs
public async Task<IActionResult> Cart()
{
CartsViewModel cart = new CartsViewModel();
ShoppingCartId = GetCartID();
decimal total = GetCartTotal();
cart.CartItems = await _dbContext.Carts.Where(c => c.CartId == ShoppingCartId).Include(p => p.Product).ToListAsync();
cart.Total = Convert.ToDouble(String.Format("{0:0.00}", total)).ToString();
return View(cart);
}
This is the amount before the change.
This is the amount changed by one but total remains the same.
2
Answers
Updating totals and such as you make changes client-side means wiring up JavaScript events client-side and responding to events, getting the necessary details to make the calculations client side. This means ensuring elements you want to update have meaningful Ids so that they can quickly be identified and updated, and controls that you want to use to trigger calculations have easy access to important details for those calculations.
For a start understanding model binding: Code line this:
This is a server-side binding. When we route to a path and populate a model to associate with a view template (cshtml) the model class and template are processed by the view engine on the web server to compose the actual view (html) that the browser renders. This creates all of the input and display elements, so on the browser there is no concept of a
@Model
object to update. Only the HTML.The two general approaches are to update UI via postbacks to to the server, or perform the update client-side. With a postback, every operation where you need to update the model posts changes back to the server to update a model, pass to the view engine, and re-render the html. This makes a page "chatty" with the server and incurs a significant cost to relay state back and forth continuously. (slow, and can get resource expensive)
For a client-side approach we need to expand what you have a bit:
Here you have an attempt to trigger a JS method "amountChange" on the number input. You can simplify this to just
onchange="amountChange"
We will also need to identify the element we plan to update for the total. Since this a singular total for the page, all we really need is to ensure it has an ID so we can find it easily:
Now we need the amountChange function: Inside your script block you are declaring and executing a chunk of code arbitrarily which is a bit of a no-no. Ideally any code that "does stuff" should be declared within a function and if you want to execute that code on page load when using JQuery you should have that function called on document ready. You also need to be careful when setting up events because as pages increase in complexity you can end up attaching multiple events onto an element leading to extra, or even incorrect calls. Since your code is POSTing to the server then the post can return a response that includes the revised total. Alternatively we could re-compute it client-side but that risks becoming out of sync with server state.
Your "AddToCart" method needs to be an "UpdateCart", accepting the productId as well as the quantity; Rather than incrementing a Product, we send the updated quantity (the val() of the input) for how many items you have selected. This will find the product server-side for the cart and adjust the quantity. You want to ensure this passes back an updated
total if it is successful, or a failure if there is an issue. (invalid qty, product not found in cart, etc.) This means returning
IActionResult
orJsonResult
. When your code goes to updates the cart you want a simple object for the result like:After you re-calculate your total, return this result back to the browser:
… where "cart" is the current cart state which you calculate the updated total after updating the relevant product quantity. Otherwise if there is a problem:
… etc based on why the cart update failed. The message should be something you could display to the user. Any exception or such can be logged server-side with an "Unexpected error when updating cart." or the like passed back as the message.
The
$.post()
call has a callback method declared to handle the successful response from a server. Not to be confused with our "IsSuccessful", it just gets called as long as the Server responded with what amounts to an OK response.Depending on how your server is set up you may or may not have the Json serialization automatically converting between PascalCase and camelCase so you might need to preserve the PascalCase (i.e. "data.IsSuccessful" / "data.Total") in your JavaScript code. It can take a bit of debugging inspection client-side to get the wiring and data coming across/interpreted correctly.
That should hopefully give you a start on things to try and work through your project.
Editing the input with quantity is a client side behavior but
@Model.Total
is a server side behavior.A simple way is to use Jquery to listen for changes in the Quantity field and calculate the total value. For example: