2

I have a save button that calls a function to open a modal dialog with two buttons; "Save Timelines" and "Cancel". The "Save Timeline" button calls two functions AFTER WHICH the page needs to reload. I've tried a few different ways to get this done...

1 Just calling the functions pragmatically:

    function genSaveTimelinesModal() {
    $("#saveTimelineDialog").dialog({
        resizable: false,
        height: 250,
        modal: true,
        buttons: {
            "Save timelines": function() {
                editSavedTimelines(); 
                saveTimelines();
                $(this).dialog("close");
                location.reload();
            },
            Cancel: function() {
                $(this).dialog("close");
            }
        }
    });
}

2 Setting up a callback:

function genSaveTimelinesModal() {
    $("#saveTimelineDialog").dialog({
        resizable: false,
        height: 250,
        modal: true,
        buttons: {
            "Save timelines": function() {
                editSavedTimelines(saveTimelines()); 

                location.reload();
            },
            Cancel: function() {
                $(this).dialog("close");
            }
        }
    });
}

3 Using JQuery $.when().do():

    function genSaveTimelinesModal() {
    $("#saveTimelineDialog").dialog({
        resizable: false,
        height: 250,
        modal: true,
        buttons: {
            "Save timelines": function() {
                $.when(editSavedTimelines(), saveTimelines()).do(location.reload());
            },
            Cancel: function() {
                $(this).dialog("close");
            }
        }
    });
}

My issue, on all three attempts, comes about when the "Save Timelines" button is clicked... the page reloads and none of the functions are run. When I pull the location.reload() call out of each example, the functions run as I want them.

Is there a way to reload the page ONLY AFTER the functions are completed?

For reference, here are the functions I'm calling:

function saveTimelines() { 
    console.log("start save");
    for (i=1; i < timelineIndex + 1; i++) {
        var dow = startdow;
        var clientValue = $("#clientNameSelect" + i).val();
        var projectValue = $("#projectSelect" + i).val();
        var taskValue = $("#taskSelect" + i).val();
        var billingValue = $("#billingSelect" + i).val();
        var activityValue = $("#activitySelect" + i).val();
        var stateValue = $("#states" + i).val();
        var sundayValue = $("#sun" + i).val();
        var mondayValue = $("#mon" + i).val();
        var tuesdayValue = $("#tue" + i).val();
        var wednesdayValue = $("#wed" + i).val();
        var thursdayValue = $("#thu" + i).val();
        var fridayValue = $("#fri" + i).val();
        var saturdayValue = $("#sat" + i).val();



        $.ajax({
            type: "GET",
            url:"biqqyzqyr?act=API_DoQuery&query={'6'.EX.'" + projectValue + "'}AND{'16'.TV.'" + currUserEmail + "'}&clist=3&includeRids=1&fmt=structured",
            dataType: "xml",
            success: function (xml) {
                $(xml).find("record").each(function () {
                    var resourceMap = new Array();
                    $(this).children().each(function () {
                        var name = $(this).attr("id");
                        var value = $(this).text();
                        resourceMap[name] = value;
                    });
                    resourceRecords.push(resourceMap);
                });
                console.log("hi");
                var resourceRId = '3';
                for (var j = 0; j < resourceRecords.length; j++) {
                    resourceOptions = resourceRecords[j][resourceRId];
                    console.log(resourceOptions);
                }

                $.ajax({
                    type: "GET",
                    url: "biha4iayz?act=API_AddRecord&_fid_12=" + dow + "&_fid_36=" + clientValue + "&_fid_9=" + projectValue + "&_fid_7=" + taskValue + "&_fid_10=" + billingValue + "&_fid_15=" + activityValue + "&_fid_11=" + stateValue + "&_fid_13=" + sundayValue + "&_fid_57=" + mondayValue + "&_fid_58=" + tuesdayValue + "&_fid_59=" + wednesdayValue + "&_fid_60=" + thursdayValue + "&_fid_61=" + fridayValue + "&_fid_62=" + saturdayValue + "&_fid_17=" + resourceOptions,
                    dataType: "xml",
                    success: function () {
                        console.log(i+ "new")
                    },
                    fail: loadFail
                });

            },
            fail: loadFail
        });
    }

    alert(timelineIndex+savedTimelineIndex+" timelines have been saved to the system...");

}



function editSavedTimelines(callback) {
    console.log("start edit");
    for (j=1; j < savedTimelineIndex + 1; j++) {
        var dow = startdow;
        var savedRId = $("#recordsaved" + j).val();
        var sundayValue = $("#sunsaved" + j).val();
        var mondayValue = $("#monsaved" + j).val();
        var tuesdayValue = $("#tuesaved" + j).val();
        var wednesdayValue = $("#wedsaved" + j).val();
        var thursdayValue = $("#thusaved" + j).val();
        var fridayValue = $("#frisaved" + j).val();
        var saturdayValue = $("#satsaved" + j).val();

        console.log(savedRId);



     $.ajax({
        type: "GET",
        url: "biha4iayz?act=API_EditRecord&rid=" + savedRId + "&_fid_13=" + sundayValue + "&_fid_57=" + mondayValue + "&_fid_58=" + tuesdayValue + "&_fid_59=" + wednesdayValue + "&_fid_60=" + thursdayValue + "&_fid_61=" + fridayValue + "&_fid_62=" + saturdayValue,
        dataType: "xml",
        success: function () {

        },
        fail: loadFail
    });



}

}
Tabaker78
  • 49
  • 1
  • 1
  • 9
  • The nested `ajax` calls in `saveTimelines` make it particularly tricky. A combination of solution 2 and 3 could work. But one important point on using `when` is that you would need your functions to return the xhr that is returned by `$.ajax` – James Montagne Feb 26 '14 at 20:14
  • Can `editSavedTimelines()` and `saveTimelines()` run in parallel, or does one need to complete before the second starts? – Jason P Feb 26 '14 at 20:16
  • They can be completed in any order. – Tabaker78 Feb 26 '14 at 20:20
  • are you sure you do a synchron ajax call, because saveTimelines and editSaveTimelines are called for sure but the problem is if ajax request is asynchron the location.refresh will overload it –  Feb 26 '14 at 20:23
  • I have similar functions that run with only one call and the location.reload doesn't overload it. – Tabaker78 Feb 26 '14 at 20:30

2 Answers2

2

This should work for you. It uses an array of Deferred objects for the for ajax calls in for loops, and has one final deferred object for when all of those are complete. The outer function listens for completion of those deferred objects. Note that I have snipped a lot of code that isn't relevant, and changed from success callbacks to .done() for your ajax calls.

function genSaveTimelinesModal() {
    $("#saveTimelineDialog").dialog({
        resizable: false,
        height: 250,
        modal: true,
        buttons: {
            "Save timelines": function() {
                $.when(editSavedTimelines(), saveTimelines()).done(function() {
                    location.reload();
                });
            },
            Cancel: function() {
                $(this).dialog("close");
            }
        }
    });
}

function saveTimelines() {

    var finalDef = $.Deferred();

    var defs = [];
    for (i=1; i < timelineIndex + 1; i++) {

        var def = $.Deferred();
        defs.push(def);

        $.ajax({...}).done(function(xml) {

            $.ajax({...}).done(function() {

                def.resolve(true);

            });

        });        
    }

    $.when.apply(null, defs).done(function() {
        finalDef.resolve();
    });

    return finalDef.promise();

}

function editSavedTimelines() {

    var finalDef = $.Deferred();

    var defs = [];
    for (j=1; j < savedTimelineIndex + 1; j++) {    

        var def = $.Deferred();
        defs.push(def);

        $.ajax({...}).done(function() {
            def.resolve(true); 
        });
    }

    $.when.apply(null, defs).done(function() {
       finalDef.resolve(true); 
    });

    return finalDef.promise();
}
Jason P
  • 26,984
  • 3
  • 31
  • 45
  • Got pulled away while writing my answer and didn't realize your very similar answer had been posted while I was gone. +1 - Out of curiosity, is there a reason to pass `true` to `def.resolve`? – James Montagne Feb 26 '14 at 21:03
  • @JamesMontagne Just personal preference... I like having some truthy value there. – Jason P Feb 26 '14 at 21:12
  • Thank you for te quick response, and as @JamesMontagne suggested, I should probably just redesign the calls altogether. Time is a hindrance though, so I have to deal with what I have for the moment. I tried implementing this answer and the page is reloading, but only editSaveTimelines() is running. [link](http://pastebin.com/0gJ1BpyE) here is my implementation in case I missed something. Thank you again for the response. – Tabaker78 Feb 26 '14 at 21:28
  • @Tabaker78 Unrelated to the actual question asked, but you also seem to have a closure in a loop issue. Good read on that here: http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example By the time your inner ajax call is made your variables will be reassigned so only the last set of values will be sent for each request. – James Montagne Feb 26 '14 at 21:34
2

The problem with your usage of when is that both of your functions don't return anything. Your call essentially amounts to this:

$.when(undefined, undefined)

Your saveTimelines function is the more complicated function because you make a 2nd ajax call in the callback of the first. And to make matters even worse, these ajax calls are in a loop. So your function isn't "complete" until the inner ajax calls for each iteration of the loop are complete.

I would strongly suggest trying to completely redesign this to simplify things. If you can eliminate the loop as well as the nested ajax calls, this would be much easier.

That being said, lets look at how we could overcome this issue. First to deal with the issue of the inner ajax call, you can solve this by creating your own deferred object. Something like this (ignoring the loop for the moment):

function saveOneTimeline(/* any params here, such as i */) {

    // create a deferred object which will be returned by this function and resolved once all calls are complete
    var def = $.Deferred();

    /* ... */

    $.ajax({
        /* ... */    
        success: function (xml) {
            /* ... */                
            $.ajax({
                /* ... */    
                success: function () {
                    // we are done, resolve the deferred object
                    def.resolve();
                }
            });
        }
    });

    // return the deferred object so that the calling code can attach callbacks/use when
    return def;
}

Then finally, our previous method can be called in a loop, placing the returned deferred objects into an array and then using when to return a promise that will only resolve once all of the deferreds resolve. It would look something like this:

function saveTimelines() {
    // an array to store all of the deferreds
    var defs = [];

    for (i=1; i < timelineIndex + 1; i++) {
        defs.push(saveOneTimeline(i));
    }

    // call when on the array of deferred objects and return the resulting promise object
    return $.when.apply($, defs);
}

Your editSavedTimelines is slightly less complex due to the fact that you don't have nested ajax calls. However, you still have a loop. A very similar approach can be used, except the helper function can simply return the object returned by the ajax call directly.

As you can see, this all very complex. It would probably be a much better idea to instead try to eliminate some of your complexity to avoid having to go to these lengths. Perhaps if you can make one bulk ajax call instead of many in a loop then allow the backend code to handle the separation.

James Montagne
  • 77,516
  • 14
  • 110
  • 130
  • +1 Explanation of why the existing code doesn't work is a nice touch, even if this is relatively the same solution as the other answer. – Rich Feb 26 '14 at 21:08
  • +1 Probably a better answer than mine because of the explanation. – Jason P Feb 26 '14 at 21:13