0

I have noticed that I'm having problems when I'm using AJAX in jQuery inside a .each() loop. Only the first record in my database are being updated when the script executes.

Here's my script:

function save(){
            var _userTypeId;
            var _userTypeName;
            var _isDeleted;
            var request;

            $("tr.recUserType").each(function(){
                $this = $(this);
                _userTypeId = $this.find("#userTypeId").html();
                _userTypeName = $this.find("#userTypeName").val();
                _isDeleted = $this.find("#isDeleted").val();

                request = $.ajax({
                    url: "save.php",
                    type: "POST",
                    data: {userTypeId: _userTypeId, userTypeName: _userTypeName, isDeleted: _isDeleted}
                });
            });

            request.done(function(){
                document.location.reload();
            });

            request.fail(function(){
                alert("Request Failed!");
            });
        }

And the contents of save.php:

<?php
include_once "globals.php";

dbConnect();

$isExisting = mysql_query("SELECT COUNT(userTypeId) AS userCount FROM userType WHERE userTypeId='".$_POST['userTypeId']."';");

$result = mysql_fetch_array($isExisting);

//original: if(!$result['userCount'] = 0) <-- This was a logical error
if($result['userCount'] != 0)
    mysql_query("UPDATE userType SET userTypeName='".$_POST['userTypeName']."', isDeleted='".$_POST['isDeleted']."' WHERE userTypeId='".$_POST['userTypeId']."';");
else
    mysql_query("INSERT INTO userType VALUES('', '".$_POST['userTypeName']."', '".$_POST['isDeleted']."');");

echo mysql_error();

dbClose();
?>

I have read that I have the option to use synchronous instead of asynchronous, but I have also read it is not a good practice.

So how do I actually get this done asynchronously and fix the problem?

6
  • You are using the same userTypeId - _userTypeId = $this.find("#userTypeId").html(); for each loop so you are only going to update 1 row. Commented Jul 28, 2013 at 3:43
  • 1
    Allow me to just POST userTypeName=Lolinjection&isDeleted=1&userTypeId=' OR 1 = 1; -- and explain to you how SQL injection is bad. Use a library that actually helps protect against this, or at the very least sanitize your inputs. Commented Jul 28, 2013 at 3:47
  • @Sean, I'm not sure how the userTypeId value would be the same if I'm iterating through each of the <tr> tags, even if they have the same id. Commented Jul 28, 2013 at 8:24
  • @Hiroto, I do know that my code is still prone to SQLi, and I still have to do some validations for that. Thanks for the reminder though. Commented Jul 28, 2013 at 8:26
  • Although technically it might work to select the child of the <tr> with the id of userTypeId (stackoverflow.com/a/7017308/689579), I would not recommend it. Using the same id multiple times in a document, even if in different <tr> tags, is invalid html. w3.org/TR/html401/struct/global.html#h-7.5.2 / w3.org/TR/html5/dom.html#the-id-attribute The id attribute assigns a **unique** identifier to an element / The id attribute specifies its element's unique identifier (ID). These would be better as classes. Commented Jul 28, 2013 at 15:14

3 Answers 3

4

jQuery's $.ajax() returns a jQuery XMLHttpRequest Object (a "jqXHR"). You are storing this object into your "response" variable.

Your problem is scope. You are storing all N of your jqXHRs into the same "request" variable. By the end of your loop, "request" is only pointing to the last jqXHR, and thus .done() will only be called when your LAST request completes.

As Karl Anderson pointed out, you should store ALL of your jqXHRs into an array, and then execute a single callback once ALL of them have [asynchronously] completed.

var XHRs = [];

// ...

$("tr.recUserType").each(function() {
    $this = $(this);
    _userTypeId = $this.find("#userTypeId").html();
    _userTypeName = $this.find("#userTypeName").val();
    _isDeleted = $this.find("#isDeleted").val();

    XHRs.push($.ajax({
        url: "save.php",
        type: "POST",
        data: {userTypeId: _userTypeId, userTypeName: _userTypeName, isDeleted: _isDeleted}
    }));
});

$.when(XHRs).then(function() {
    document.location.reload();
});

Also, avoid the delicious temptation to use $.ajax()'s "async: false". The browser will be forced to hang until a request completes, which is bad. You can pretty much always accomplish a $.ajax() call asynchronously; it may require some craftiness, but will definitely be for the better.

Sign up to request clarification or add additional context in comments.

2 Comments

For some reason, it still does not work even after I store the jqXHR objects inside an array. I got the concept of what you're talking about, just not sure why it does not work.
Maybe try: var defer = $.when.apply($, jqXHRs); defer.then(function() { ... }); (stackoverflow.com/questions/9865586/… see Andy's answer)
0

Your issue is in this block of code:

request.done(function(){
    document.location.reload();
});

Since the actions are asynchronous, this line is being executed before the save is completing, thus after the first save does complete, it executes the done logic.

You need to create an array of deferred objects that you can then wait until they are all executed before proceeding forward with your logic using the jQuery .when()/.then() functions.

Here is a previous StackOverflow question that details how to setup this situation. Please read the accepted answer of jQuery when each is completede trigger function.

Comments

0

There are a couple things happening here.

  1. By re-using the request variable in the loop (scoped outside the loop), every iteration of the loop assigns a new object to request, overwriting what was there. This means you are only setting the response handlers for the last iteration.

  2. Your request.done method reloads the page, which in practice will halt the other requests and...

  3. You are looking up the same userTypeId for each iteration of the loop, as mentioned by @Sean

2 Comments

It gets more clear now, however, regarding #3, would it matter if each <tr> tag has the same id which is userTypeId? It won't be the same userTypeId for each iteration because I would be iterating through each <tr>. Is it right or I'm missing something?
since you are using #userTypeId it is looking for elements on the page with the id="userTypeId" of which there should only be one (in terms of best practice). But since you are scoping the lookup to the parent tr it may work out in this case, but the code could be made cleaner using classes or something different.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.