skip to Main Content

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.

Default pic before Change

This is the amount changed by one but total remains the same.

Pic after adding product by one

2

Answers


  1. 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:

    <div class="col">
        <h6 class="text-end">Total: [email protected]</h6>
    </div>
    

    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:

    <input type="number" data-id="@items.ProductId" class="product-amount" value="@items.Count" onchange="amountChange(this)"/></td> *@
                    <td>[email protected] ea.</td>
    

    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:

    <div class="col">
        <h6 id="cartTotal" class="text-end" >Total: [email protected]</h6>
    </div>
    

    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.

    <script type="text/javascript">
    
        function amountChange(e) {
            let $input = $(e.target);
            let $total = $('#cartTotal);
            let productId = $input.data("id");
    
            if(productId == null) return;
    
            $.post("/ShoppingCart/UpdateCart", {"id": productId, "quantity": $input.val()},
               function(data){
                   if (data.isSuccessful) {
                       console.log("Add to Cart Successful")
                       $total.html(`Total: ${data.total}`);
                   } else {
                       console.log("Add to cart failed: " + data.message);
                   }
               });
        }
    
    </script>
    

    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 or JsonResult. When your code goes to updates the cart you want a simple object for the result like:

    public class UpdateCartResult
    {
        public bool IsSuccessful { get; set; }
        public decimal Total { get; set; }
        public string? Message { get; set; }
    }
    

    After you re-calculate your total, return this result back to the browser:

    return Json(new UpdateCartResult { IsSuccessful = true, Total = cart.Total });
    

    … 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:

    return Json(new UpdateCartResult { IsSuccessful = false, Message = "Cart could not be updated, product not found" }); // or Quantity should be between 1-10, etc.
    

    … 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.

    Login or Signup to reply.
  2. 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:

    <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" id="Count" /></td>
                        <td>
                            [email protected] ea.
                            <input hidden value="@items.Product.Price" id="Price" />
                        </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" id="total-amount"></h6>
        </div>
    </div>
    <script src="~/lib/jquery/dist/jquery.js" type="text/javascript"></script>
    <script>
        function calculateTotal() {
            var total = 0;
            // Loop through each product row to calculate the total
            $('.product-amount').each(function () {
                var quantity = $(this).val(); // Get the quantity
                var price = $(this).closest('tr').find('#Price').val(); // Get the price
    
                if (quantity && price) {
                    total += (quantity * price); // Add to the total
                }
            });
            // Update the total amount displayed
            $('#total-amount').text(total.toFixed(2));
        }
        $(document).ready(function () {
            // Calculate the total on page load
            calculateTotal();
            // Listen for changes on all quantity inputs and update total
            $(document).on('input', '.product-amount', function () {
                calculateTotal();
            });
        });
    </script>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search