1

First let me thank @Jasen, he spent 9 days helping me with an issue and it means a lot to me that he took him time to help me. It was this that he was helping me with, but at the last second they decided they wanted to go with AJAX since the contact page uses it and removing items from the cart utilizes it.

Let me get to my issue, I have view (this is MVC 5) that in loop loads all the products of a selected category. I want to use jQuery nd AJAX to add items to the cart. This works great for the first item in the list the first time it is added to the cart.

I imagine my problem is all the buttons have an id of AddToCart and jQuery, the way I have it written can't decide which button is being clicked.

Here is the code for the view

@model IEnumerable<AccessorizeForLess.ViewModels.DisplayProductsViewModel>

@{
    ViewBag.Title = "Products > Necklaces";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<link href="~/Content/Site.css" rel="stylesheet" />
<link href="~/Content/jquery.fancybox.css?v=2.1.5" rel="stylesheet" />
<link href="~/Content/jquery.fancybox-buttons.css?v=1.0.5" rel="stylesheet" />
<link href="~/Content/jquery.fancybox-thumbs.css?v=1.0.7" rel="stylesheet" />
<h2>Products > Necklaces</h2>
<div id="update-message"></div>
<p class="button">
    @Html.ActionLink("Create New", "Create")
</p>
@*@using (Html.BeginForm("AddToCart", "Orders", FormMethod.Post))*@
{
    <div id="container">
        <div id="sending" style="display:none;"><img src="~/Content/ajax-loader.gif" /></div>
        <div style="color:red" id="ItemAdded"></div>
        <div class="scroll">

            @foreach (var item in Model)
            {
                <div class="scroll2">
                    <div class="itemcontainer">
                        <table>
                            <tr>
                                <td id="@item.Id" class="divId">
                                    <div class="DetailsLink" id="@item.Id"> &nbsp;&nbsp;&nbsp;@Html.ActionLink(@item.Name, "Details", new { id = item.Id })</div>
                                    <br />
                                    <div id="@item.Id"></div>
                                    <div class="divPrice" id="@item.Price">@Html.DisplayFor(modelItem => item.Price)</div>
                                    <div class="divImg"><a class="fancybox-thumbs" href="@item.Image.ImagePath" title="@item.Image.AltText" data-fancybox-group="thumb"><img src="@item.Image.ImagePath" alt="@item.Image.AltText" title="@item.Image.AltText" /></a></div>
                                    <div>&nbsp;</div>
                                    <div class="divQuantity">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Quantity: @Html.TextBoxFor(modelItem => item.Quantity, new { @id = "quantity", @style = "width:50px;", @class = "formTextBox" })</div>
                                    <div class="form-group">
                                        <div class="col-md-offset-2 col-md-10">
                                            <input type="button" value="AddToCart" class="btn btn-default" id="AddToCart" />
                                        </div>
                                    </div>
                                    <div style="height:15px;"></div>
                                </td>
                            </tr>
                        </table>
                    </div>
                </div>
                    }
                    <div class="button">@Html.ActionLink("Back To Categories","Categories")</div>
                    <br />
       </div>        
    </div>
@*}*@

And here is my jQuery code:

@section scripts {
    <script src="~/Scripts/jQuery-jScroll.js"></script>
    <script src="~/Scripts/jquery.fancybox.js?v=2.1.5"></script>
    <script src="~/Scripts/jquery.fancybox-thumbs.js?v=1.0.7"></script>
    <script src="~/Scripts/jquery.fancybox-buttons.js?v=1.0.5"></script>
    <script type="text/javascript">
            //$(function () {
            //    $('.scroll').jscroll({
            //        autoTrigger: true
            //    });
                $('.fancybox-thumbs').fancybox({
                    prevEffect: 'none',
                    nextEffect: 'none',

                    closeBtn: true,
                    arrows: false,
                    nextClick: false
                });

                // Document.ready -> link up remove event handler
                $("#AddToCart").click(function () {
                    //first disable the button to prevent double clicks
                    $("#AddToCart").attr("disbled", true);
                    $("#AddToCart").prop("value", "Adding...");
                    $("#ItemAdded").text("");
                    //now show the loading gif
                    $("#sending").css("display", "block");
                    // Get our values
                    var price = parseFloat($(".divPrice").attr("id"));
                    var quantity = parseInt($("#quantity").val());
                    var id = parseInt($(".divId").attr("id"));

                    $.ajax({
                        url: "@Url.Action("AddToCartAJAX", "Orders")",
                        type: "POST",
                        data: { "id": id, "quantity": quantity, "price": price },

                        //if successful
                        success: function (data) {
                            successfulCall()
                        },
                        error: function (data) {
                            alert(data.Message);
                        }
                    });

                    function successfulCall() {
                        //enable the send button
                        $("#AddToCart").attr("disbled", false);

                        //hide the sending gif
                        $("#sending").css("display", "none");

                        //change the text on the button back to Send
                        $("#AddToCart").prop("value", "Add to Cart");

                        //display the successful message
                        $("#ItemAdded").text("Your item has been added to your order.");

                        //clear out all the values
                        $("input#quantity").val("0");
                    }

                    function errorCall() {
                        $("#AddToCart").attr("disbled", false);
                        $("#sending").css("display", "none");
                        $("#AddtoCart").prop("value", "Add to Cart");
                        $("#ItemAdded").text(data.message);
                    }
                    //alert('Clicked!');
                });
            //s});
    </script>
}

Can someone show me what I am doing wrong here so I can get this working?

EDIT #1

Here is the updated jQuery code:

$(".AddToCart").click(function () {
//first disable the button to prevent double clicks
$(this).prop("disbled", true).prop("value", "Adding...");
$("#sending").css("display", "block");
var td = $(this).closest('td')

//traverse DOM and find relevant element 
var price = parseFloat(td.find(".divPrice").prop("id")),
    quantity = parseInt(td.find("#quantity").val()),
    id = parseInt(td.find(".divId").prop("id"));

$.ajax({
    url: "@Url.Action("AddToCartAJAX", "Orders")",
    type: "POST",
    data: { "id": id, "quantity": quantity, "price": price },
    //if successful
    success: function (data) {
        successfulCall()
    },
    error: function (data) {
        errorCall(data);
    }
});

It worked before making the client side changes (granted only once and only for the first item in the list), since I havent changed the server side code what could have gone wrong? EDIT #2

Here is the whole thing in it's entirity

@model IEnumerable<AccessorizeForLess.ViewModels.DisplayProductsViewModel>

@{
    ViewBag.Title = "Products > Necklaces";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<link href="~/Content/Site.css" rel="stylesheet" />
<link href="~/Content/jquery.fancybox.css?v=2.1.5" rel="stylesheet" />
<link href="~/Content/jquery.fancybox-buttons.css?v=1.0.5" rel="stylesheet" />
<link href="~/Content/jquery.fancybox-thumbs.css?v=1.0.7" rel="stylesheet" />
<h2>Products > Necklaces</h2>
<div id="update-message"></div>
<p class="button">
    @Html.ActionLink("Create New", "Create")
</p>
@*@using (Html.BeginForm("AddToCart", "Orders", FormMethod.Post))*@
{
    <div id="container">
        <div id="sending" style="display:none;"><img src="~/Content/ajax-loader.gif" /></div>
        <div style="color:red" id="ItemAdded"></div>
        <div class="scroll">

            @foreach (var item in Model)
            {
                <div class="scroll2">
                    <div class="itemcontainer">
                        <table>
                            <tr>
                                <td id="@item.Id" class="divId">
                                    <div class="DetailsLink" id="@item.Id"> &nbsp;&nbsp;&nbsp;@Html.ActionLink(@item.Name, "Details", new { id = item.Id })</div>
                                    <br />
                                    <div id="@item.Id"></div>
                                    <div class="divPrice" id="@item.Price">@Html.DisplayFor(modelItem => item.Price)</div>
                                    <div class="divImg"><a class="fancybox-thumbs" href="@item.Image.ImagePath" title="@item.Image.AltText" data-fancybox-group="thumb"><img src="@item.Image.ImagePath" alt="@item.Image.AltText" title="@item.Image.AltText" /></a></div>
                                    <div>&nbsp;</div>
                                    <div class="divQuantity">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Quantity: @Html.TextBoxFor(modelItem => item.Quantity, new { @style = "width:50px;", @class = "formTextBox quantity" })</div>
                                    <div class="form-group">
                                        <div class="col-md-offset-2 col-md-10">
                                            <input type="button" value="AddToCart" class="btn btn-default AddToCart" />
                                        </div>
                                    </div>
                                    <div style="height:15px;"></div>
                                </td>
                            </tr>
                        </table>
                    </div>
                </div>
                    }
                    <div class="button">@Html.ActionLink("Back To Categories","Categories")</div>
                    <br />
       </div>        
    </div>
@*}*@
@section scripts {
    <script src="~/Scripts/jQuery-jScroll.js"></script>
    <script src="~/Scripts/jquery.fancybox.js?v=2.1.5"></script>
    <script src="~/Scripts/jquery.fancybox-thumbs.js?v=1.0.7"></script>
    <script src="~/Scripts/jquery.fancybox-buttons.js?v=1.0.5"></script>
    <script type="text/javascript">
            //$(function () {
            //    $('.scroll').jscroll({
            //        autoTrigger: true
            //    });
                $('.fancybox-thumbs').fancybox({
                    prevEffect: 'none',
                    nextEffect: 'none',

                    closeBtn: true,
                    arrows: false,
                    nextClick: false
                });

                // Document.ready -> link up remove event handler
                $(".AddToCart").click(function () {
                    //first disable the button to prevent double clicks
                    $(this).prop("disbled", true).prop("value", "Adding...");
                    $("#sending").css("display", "block");
                    var td = $(this).closest('td')

                    //traverse DOM and find relevant element 
                    var price = parseFloat(td.find(".divPrice").prop("id")),
                        quantity = parseInt(td.find(".quantity").val()),
                        id = parseInt(td.find(".divId").prop("id"));

                    $.ajax({
                        url: "@Url.Action("AddToCartAJAX", "Orders")",
                        type: "POST",
                        data: { "id": id, "quantity": quantity, "price": price },
                        //if successful
                        success: function (data) {
                            successfulCall()
                        },
                        error: function (data) {
                            errorCall(data);
                        }
                    });

                    function successfulCall() {
                        //enable the send button
                        $(this).prop("disbled", false).prop("value", "Add To Cart");

                        //hide the sending gif
                        $("#sending").css("display", "none");

                        //display the successful message
                        $("#ItemAdded").text("Your item has been added to your order.");

                        //clear out all the values
                        $("input#quantity").val("0");
                    }

                    function errorCall(data) {
                        $(this).prop("disbled", false).prop("value", "Add To Cart");
                        $("#sending").css("display", "none");
                        $("#ItemAdded").text(data.message);
                    }
                    //alert('Clicked!');
                });
            //s});
    </script>
}

EDIT #2

Here is the code for AddToCartAJAX in OrdersController:

public ActionResult AddToCartAJAX(int id, int quantity, decimal price)
{
    var cart = ShoppingCart.GetCart(this.HttpContext);

    cart.AddToCart(id, quantity, price);

    return RedirectToAction("Index");
}

And AddToCart in ShoppingCrt.cs:

public void AddToCart(int id, int quantity, decimal price)
{
    // Get the matching cart and product instances
    var order = entities.Orders.FirstOrDefault(
        c => c.OrderGUID == ShoppingCartId
        && c.OrderItems.Where(p => p.ProductId == id).FirstOrDefault().ProductId == id);

    if (order == null)
    {
        // Create a new order since one doesn't already exist
        order = new Order
        {
            InvoiceNumber = Guid.NewGuid().ToString(),
            OrderDate = DateTime.Now,
            OrderGUID = ShoppingCartId,
            IsShipped = false
        };
        entities.Orders.Add(order);

        // Save changes
        entities.SaveChanges();

        //add the OrderItem for the new order
        OrderItem oi = new OrderItem()
        {
            OrderId = order.OrderId,
            OrderGUID = ShoppingCartId,
            ProductId = id,
            ProductQuantity = quantity,
            ProductPrice = price
        };

        entities.OrderItems.Add(oi);
        entities.SaveChanges();
    }
    else
    {
        // If the item does exist in the cart, 
        // then add one to the quantity
        order.OrderItems.Where(p => p.ProductId == id).FirstOrDefault().ProductQuantity += quantity;
    }
}

Hope that helps

PsychoCoder
  • 10,570
  • 12
  • 44
  • 60

1 Answers1

3

As you are creating elements in a loop, duplicate identifiers are created. Which makes your HTML invalid.

Identifiers in HTML must be unique. and this is expected behaviour.

You can assign a common class then can use class selector. Here in example I have added AddToCart CSS class to the button.

HTML

@Html.TextBoxFor(modelItem => item.Quantity, new {@style = "width:50px;", @class = "formTextBox quantity" })

<input type="button" value="AddToCart" class="btn btn-default AddToCart" />

Script

$(".AddToCart").click(function() {
    $(this).prop("disabled", true)
           .prop("value", "Adding...");

    var td = $(this).closest('td.divId')

    //traverse DOM and find relevant element 
    var price = parseFloat(td.find(".divPrice").prop("id")),
        quantity = parseInt(td.find(".quantity").val()),
        id = parseInt(td.find(".divId").prop("id"));

    //Your ajax call
});

I would also recommend you to use data-* prefixed custom attributes to store arbitrary data.

 <div class="divPrice" data-price="@item.Price"></div>

Which can be fetched using .data(), it also converts value to appropriate type.

 var price = $('.divPrice').data("price");

Note: Change the rest of selectors accordingly.

Satpal
  • 132,252
  • 13
  • 159
  • 168
  • I implemented your changes into my existing code, but now when it hits my ajax call I get a 500 (Internal Server Error), I've checked my permissions and they seem to be correct. – PsychoCoder Aug 04 '15 at 06:49
  • @PsychoCoder, Its nothing to do with permission and as per suggestion you have to do lot of changes to achieve the result and 500 means something is not right at server side. – Satpal Aug 04 '15 at 06:58
  • can you check my edit please. I know it's got to be something simple I'm missing. – PsychoCoder Aug 04 '15 at 07:11
  • @PsychoCoder, As Satpal noted, a 500 error is a result of an exception on the serer. You need to show your controller code. –  Aug 04 '15 at 08:16
  • @StephenMuecke I edited to include the control from OrdersController and ShoppingCart.cs – PsychoCoder Aug 04 '15 at 14:13
  • 1
    @PsychoCoder, `return RedirectToAction("Index")` will not work with `$.ajax()` this will help you => http://stackoverflow.com/a/17745088/ – Satpal Aug 04 '15 at 14:37
  • It works now with one caveat, when I go to add a 2nd item it's always the same product id no matter what I select. I can show whatever code you guys need to see, just let me know. – PsychoCoder Aug 04 '15 at 16:14