PsychoCoder
PsychoCoder

Reputation: 10765

jQuery AJAX event only firing once

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

Upvotes: 1

Views: 502

Answers (1)

Satpal
Satpal

Reputation: 133453

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.

Upvotes: 3

Related Questions