I've been looking for an answer for this a couple of days now without luck.
I'm trying to build a chart from data collected via ajax calls to JSON REST Api. I use morris.js for building my charts. The data for chart is collected via 3 nested ajax calls. The first ajax call retrieves a list with tasks, on success a loop iterates over the result and makes an ajax call for every result. And on success on these calls data is retrieved to build the chart.
Inside the last success function an for loop
runs to build an array containing the data for the chart.
These data get pushed into the parameter array for the chart (morris.js).
So 3 nested ajax call, with an $.each
loop after the first call.
My problem is that the charts gets build for each ajax iteration. I have been trying to move the chart function (Morris.Donut(params);
) outside of loop without luck.
My code:
var arr = [];
var counts = [];
var test = [];
$.ajax({
url: "http://helpdesk.site.com/IT/_vti_bin/ListData.svc/ITHelpdeskRequests?$filter=TaskStatusValue%20eq%20%27Not%20Started%27",
headers: {
'accept': 'application/json;odata=verbose',
'content-type': 'application/json;odata=verbose'
},
success: function(data) {
$.each(data.d.results, function(a, data) {
$.ajax({
url: "http://helpdesk.site.com/IT/_vti_bin/ListData.svc/ITHelpdeskRequests(" + data.RequesterId + ")/CreatedBy",
headers: {
'accept': 'application/json;odata=verbose',
'content-type': 'application/json;odata=verbose'
},
success: function(data2) {
$.ajax({
url: "http://helpdesk.site.com/IT/_vti_bin/ListData.svc/ITHelpdeskRequests(" + data.AssignedToId + ")/AssignedTo",
headers: {
'accept': 'application/json;odata=verbose',
'content-type': 'application/json;odata=verbose'
},
success: function(data3) {
var params = {
element: 'taskchart',
data: [],
colors: ['#b85f28', '#d46125', '#CE4E00']
};
$(".inner").prepend('<p>'+data.Request +' <br>Submitted by: '+data2.d.Name+'<br>Assigned to: '+data3.d.Name+' | Due in: '+data.DueInDays+' day(s)</p>');
var indexOfName = arr.indexOf(data3.d.Name);
if (indexOfName == -1) {
arr.push(data3.d.Name);
counts.push(1);
} else {
counts[indexOfName] ++;
}
for (var i in arr, counts) {
test = {
'label': '' + arr[i] + '',
'value': counts[i]
}
params.data.push(test);
}
Morris.Donut(params);
}
})
}
})
})
}
})
I know this code can look a bit messy, but I can't seem to build it better. Any suggestions on how I can build the chart after the ajax calls have finished loading the data?
I'm really banging my head against the wall here.
Quick Rundown of The Problem
After looking through your codes more closely, the reason why your chart is re-built for each iteration in your
$.each
function is because yourMorris.Donut(params);
is called within the deepest nestedsuccess
callback in your loop (duh?). And movingMorris.Donut(params);
outside of your$.each
function won't work simply because of the asynchronous nature of your nested ajax calls, i.e. the call to$.each
probably exits andMorris.Donut(params);
is invoked, before the ajax calls are completed.An Easier Alternate (Server-Side) Solution
Before showing you some javascript codes, I recommend that if you have control over the server-side API codes, you should consider moving all these complexity there; i.e. create a new web service that accepts an
TaskStatusValue
input, and returns IT requests that satisfy this condition along with whatever other information (requester's name, assignee's name etc.) is needed to construct your charts. I think essentially, that's what your JS codes are trying to do.As I mentioned in my previous comment, your nested AJAX calls rely on results of the parent AJAX call, which essentially, boils down to making synchronous requests, defeating the purpose of asynchronous calls.
The JQuery Solution (TL;DR)
With that being said, if you absolutely have to do everything on the client side, we will need to pass an array of
jQuery.deferred
objects to thejQuery.when
function such that your chart will be drawn only after all your asynchronous calls are completed.There can be other easier, better way to do this. But here's my solution....
Code Refactoring
Extract your two nested AJAX calls into one separate function like the following. This function will return the
Deferred
object returned by the$.ajax
function:After getting all the IT requests with
TaskStatusValue=NotStarted
in your firstsuccess
callback, add all the AJAX (Deferred objects) calls you will be making later to an array like this:Then finally, you can pass this array of
Deferred
objects to the$.when
function.The pseudo-array
arguments
within the$.each
function should contain all the results gathered from all your ajax calls. You can possibly draw your chart within thethen
callback.I experimented with this using this fiddle, using some fake data.