3

I currently have setup a AJAX to PHP set of functions that processes a number of items on a page. Basically the code inserts a series of tasks into the database, then inserts supplies into the database based on those newly created task ID's. However it works 90% of the time. Sometimes it seems as though the Task ID's are not created first which doesn't allow the supplies to use those ID's for inserting into the database. Is there a way to make sure that the task is inserted, then all supplies are inserted for that ID, then move onto the next one. At the end when all is complete I would like to redirect to a new page, again I put this in the last success call on the supplies portion, but it would redirect on the first loop. This process usually generates around 5 tasks, with 12 supplies per each task. I was reading about a $.when loop but could not get it to work. NOTE: after testing the ajax calls are submitting correctly, it was that one field on some of them was null, and the DB was having an issue. So the counter method below works.

$(document).on("click", "#submitTasks", function(e) {
    e.preventDefault();
    var tasks = $('#tasks').find('.box');
    var project_id = $('#project_id').val();
    tasks.each(function() {
        var trs = $(this).find('.reqTables').find('.table').find('tbody').find('tr');
        var task_definition_id = $(this).find('.task_definition_id').val();
        var labor_type_id = $(this).find('.laborAmount').children('option:selected').val();
        var task_status_id = 1;
        var qty_labor = $(this).find('.laborQty').val();
        var amount_labor = $(this).find('.laborTotal').val();
        var amount_materials = $(this).find('.matTotal').val();
        var amount_gst = $(this).find('.gstTotal').val();
        amount_materials = +amount_materials + +amount_gst;
        amount_materials = amount_materials.toFixed(2);
        var active = 1;
        //console.log(div)
        var task = {
            project_id : project_id,
            task_definition_id : task_definition_id,
            labor_type_id : labor_type_id,
            task_status_id : task_status_id,
            qty_labor : qty_labor,
            amount_labor : amount_labor,
            amount_materials : amount_materials,
            active : active
        };
        saveTasks(task, trs, project_id);
    });
});
function saveTasks(task, trs, project_id) {
    $.ajax({
        type : "POST",
        url : "<?php echo base_url(); ?>" + "mgmt/project/saveTasks",
        data : task,
        dataType : "json",
        cache : "false",
        success : function(data) {
            trs.each(function() {
                var total = $(this).find('input[name="calculatedCost"]').val();
                if (total != 'n/a') {
                    var task_id = data;
                    var supply_id = $(this).find('.suppliesPicker').children('option:selected').val();
                    var task_requirement_id = $(this).find('td:first-child').data('id');
                    var qty = $(this).find('input[name="calculatedQty"]').val();
                    var cost_per = $(this).find('.costPicker').val();
                    var delivery_cost = $(this).find('input[name="transport"]').val();

                    var notes = '';
                    var qty_actual = '';
                    var active = 1;
                    var taskSupply = {
                        task_id : task_id,
                        supply_id : supply_id,
                        task_requirement_id : task_requirement_id,
                        qty : qty,
                        cost_per : cost_per,
                        delivery_cost : delivery_cost,
                        total : total,
                        notes : notes,
                        qty_actual : qty_actual,
                        active : active
                    };
                    saveTaskSupplies(taskSupply);
                    console.log(taskSupply);
                }
            });
        }
    });
}

function saveTaskSupplies(taskSupply) {
    $.ajax({
        type : "POST",
        url : "<?php echo base_url(); ?>" + "mgmt/project/saveTaskSupplies",
        data : taskSupply,
        dataType : "json",
        cache : "false",
        success : function(data) {
            ***** I WANT TO REDIRECT TO A NEW PAGE WHEN THE LAST ONE OF THESE COMPLETES ******
        }
    });
}    
user1881482
  • 746
  • 2
  • 16
  • 34
  • Possible duplicate of [Wait until all jQuery Ajax requests are done?](http://stackoverflow.com/questions/3709597/wait-until-all-jquery-ajax-requests-are-done) – Seth May 25 '16 at 02:30
  • I've been trying to make that work, however, not all the requests are happening at the same time. There is one function in the first loop that calls a second function 12 times, then the first loop goes thru a second time and calls that second function 10 times. This `.when` looks to be what i need but where would I insert it. I have tried putting the when loop on `saveTasks(task, trs, project_id)` and on `saveTaskSupplies(taskSupply);` but that does not seem to work – user1881482 May 25 '16 at 02:40
  • Basically what you need to do is add counters to keep track of how many items you have processed, and once the processed count == total count, continue. I'm working a complete answer. – cyberbit May 25 '16 at 03:00

3 Answers3

3

This code will wait for nested loop ajax function calls to finish their promises, then proceed..

var allPromises;

$(document).on("click", "#submitTasks", function(e) {
    //...
    var tasks = $('#tasks').find('.box');

    allPromises = [];

    tasks.each(function() {
        //.. somehow getTask
        var req = saveTasks(task, trs, project_id);
        allPromises.push(req);
    });

    $.when.apply(null, allPromises).done(function(){
        // Do your things here,
        // All save functions have done.
    });
});

function saveTasks(task, trs, project_id) {
    return $.ajax({
        // ,,, your codes
        success : function(data) {
            // ...
            trs.each(function() {
                // ... Somehow get taskSupply
                var req = saveTaskSupplies(taskSupply);
                allPromises.push(req);
            }
        }
    });
}

function saveTaskSupplies(taskSupply) {
    return $.ajax({
        // ... bla bla bla
        success : function(data) {
            // Whatever..
        }
    });
}
choz
  • 17,242
  • 4
  • 53
  • 73
  • 1
    I am going to try and implement this as well. I like the above counter idea, and it is working, however this approach teaches me new things about JS – user1881482 May 25 '16 at 03:51
2

Here is a direct solution using the code you provided. The basic concept is to increment a counter as supplies are processed. Once the counter reaches the total number of supplies, a procedure is run. See comments throughout.

var totalTaskSupplies = 0;
var processedTaskSupplies = 0;

$(document).on("click", "#submitTasks", function(e) {
    e.preventDefault();
    var tasks = $('#tasks').find('.box');
    var project_id = $('#project_id').val();

    tasks.each(function() {
        var trs = $(this).find('.reqTables').find('.table').find('tbody').find('tr');
        var task_definition_id = $(this).find('.task_definition_id').val();
        var labor_type_id = $(this).find('.laborAmount').children('option:selected').val();
        var task_status_id = 1;
        var qty_labor = $(this).find('.laborQty').val();
        var amount_labor = $(this).find('.laborTotal').val();
        var amount_materials = $(this).find('.matTotal').val();
        var amount_gst = $(this).find('.gstTotal').val();

        // Add number of supplies for current task to total task supplies
        totalTaskSupplies += trs.length;

        amount_materials = +amount_materials + +amount_gst;
        amount_materials = amount_materials.toFixed(2);
        var active = 1;
        //console.log(div)
        var task = {
            project_id : project_id,
            task_definition_id : task_definition_id,
            labor_type_id : labor_type_id,
            task_status_id : task_status_id,
            qty_labor : qty_labor,
            amount_labor : amount_labor,
            amount_materials : amount_materials,
            active : active
        };
        saveTasks(task, trs, project_id);
    });
});
function saveTasks(task, trs, project_id) {
    $.ajax({
        type : "POST",
        url : "<?php echo base_url(); ?>" + "mgmt/project/saveTasks",
        data : task,
        dataType : "json",
        cache : "false",
        success : function(data) {
            trs.each(function() {
                var total = $(this).find('input[name="calculatedCost"]').val();
                if (total != 'n/a') {
                    var task_id = data;
                    var supply_id = $(this).find('.suppliesPicker').children('option:selected').val();
                    var task_requirement_id = $(this).find('td:first-child').data('id');
                    var qty = $(this).find('input[name="calculatedQty"]').val();
                    var cost_per = $(this).find('.costPicker').val();
                    var delivery_cost = $(this).find('input[name="transport"]').val();

                    var notes = '';
                    var qty_actual = '';
                    var active = 1;
                    var taskSupply = {
                        task_id : task_id,
                        supply_id : supply_id,
                        task_requirement_id : task_requirement_id,
                        qty : qty,
                        cost_per : cost_per,
                        delivery_cost : delivery_cost,
                        total : total,
                        notes : notes,
                        qty_actual : qty_actual,
                        active : active
                    };
                    saveTaskSupplies(taskSupply);
                    console.log(taskSupply);
                }
            });
        }
    });
}

function saveTaskSupplies(taskSupply) {
    $.ajax({
        type : "POST",
        url : "<?php echo base_url(); ?>" + "mgmt/project/saveTaskSupplies",
        data : taskSupply,
        dataType : "json",
        cache : "false",
        success : function(data) {
            ++processedTaskSupplies;

            // All supplies have been processed
            if (processedTaskSupplies == totalTaskSupplies) {
                // Do something
            }
        }
    });
}
cyberbit
  • 1,345
  • 15
  • 22
  • This works except I had to get the length of all tr's that have a value in the total column in an input called `calculatedCost`. So I ran the increment in another each loop. Is there something better here than what I changed. `trs.each(function() { var checkCount=$(this).find('input[name="calculatedCost"]').val(); if (checkCount != 'n/a') { // Add number of supplies for current task to total task supplies ++totalTaskSupplies; } }); ` – user1881482 May 25 '16 at 03:48
  • Theoretically speaking, since you are summing up the total in every `each` iteration and you immediately fire the corresponding Ajax request, there will be a chance that `if (processedTaskSupplies == totalTaskSupplies) ` is taking a not completely-calculated `totalTaskSupplies` and result in earlier redirect when there are still Ajax request not yet fired. – Calvin Lau May 25 '16 at 04:21
  • Ok.. that makes sense. So I actually moved the trs.length count outside the loop, by looking at the whole document for tr with an input with a class of calculatedCost and found the total count out there that were not equal to n/a. At the top of the click function `var trsFilter = $('input.calculatedCost').filter(function(){return this.value!='n/a'}); totalTaskSupplies += trsFilter.length;` – user1881482 May 25 '16 at 05:20
0

Regarding the first question, by studying your code I couldn't see the reason of it. You only execute the saveTaskSupplies() when saveTasks() has executed successfully, so the task_id should already be created.

However, I would think of another possible problem from your backend, in your Ajax success function in saveTasks(), You assume the PHP script always execute successfully and return the task_id. Would it be possible that your PHP script has some problem and the task_id is not created in some instance?

For the second question, there are a few approaches, as @Seth suggest you can use jQuery.when, or you can create a global counter to keep track of whether the saveTaskSupplies() is the last one. Note that you should calculate the total length of trs before firing the Ajax request, otherwise, you may have a chance of having a not well-calculated total and redirecting before all tasks are done. If it is the last one it will redirect after successful Ajax call.

// create a global counter
var counter = 0, 
    trl = 0;
$(document).on("click", "#submitTasks", function(e) {
    ...
    var trList = [];
    tasks.each(function() {
        // calculate the length of total task before actually firing the Ajax Request
        var trs = $(this).find('.reqTables').find('.table').find('tbody').find('tr');
        // keep a copy of the trs so the next each loop does not have to find it again
        trList.push(trs);
        trl += trs.length;
    });
    tasks.each(function() {
        // get the trs of current iteration we have found in last loop
        var trs = trList.shift();
        ...
        saveTasks(task, trs, project_id);
    });
});

function saveTasks(task, trs, project_id) {
    $.ajax({
        ...
        success : function(data) {
            trs.each(function() {
                ...
                saveTaskSupplies(taskSupply);
            }
        }
    });
}

function saveTaskSupplies(taskSupply) {
    $.ajax({
        ...
        success : function(data) {
            // check if the counter exceed the length of trs
            if (++counter == trl) { 
                location.href = 'place you want to go'; 
            }
        }
    });
}

On the other hand, for your task I would also suggest shifting the responsibility of data insertion to PHP backend, so all you need to do is to pass the task information and the task supplies at once to a single PHP script. This approach allows the use of Transaction to make sure all data insertion is success or otherwise all should fail.

Community
  • 1
  • 1
Calvin Lau
  • 547
  • 1
  • 4
  • 11
  • I will give this a shot... I was thinking that I may need to pass all the data in one object and sort it in PHP. I will try and add the counter method and see if that illuminates things for me. I believe you may be right, and possibly the javascript is not grabbing all of the data it needs every time. Is sending one object faster than multiple ajax requests would you know? – user1881482 May 25 '16 at 02:57
  • It is important to note that both methods, sending multiple insertion request and single insertion request are both work-able approaches. But shifting the insertion to PHP gives you advantage of `Transaction` support. And regarding the speed, I believe sending one object will be faster because from a network perspective, it will create less overhead but that performance improvement is quite neglectable in most situations. – Calvin Lau May 25 '16 at 03:04
  • Wouldn't this redirect after the total supplies in **one** task have been processed? There's no check to make sure all tasks have been run as well. – cyberbit May 25 '16 at 03:19
  • @cyberbit Yes, you are correct. I have missed that. Answer updated. – Calvin Lau May 25 '16 at 04:13