jQuery When Done on dynamically pulled function call - javascript

I have the following code:
In site-code.js
....
var ajaxContentFunc = $(origin).data("modal-content-handler");
$.when(window[ajaxContentFunc]()).done(function (resp) {
kModal.showContent(resp);
});
In another file I have the following tag and function
Click Me
....
function ajaxContentGeneration() {
var aProm = $.ajax({
url: "tests/ajax/AjaxTest.aspx",
data: { exampleType: "modal-ajax" },
dataType: "html"
});
aProm.done(function (data) {
console.log("Ajax Loaded!");
var content = $(data).find("#ajax-content");
return aProm;
});
}
I need to populate the result of the ajaxContentGeneration (whatever method that might be) into the variable to send to showContent or in other words:
1) Pull the ajaxContentFunction Name from the tag's modal-content-handler data attribute
2) Call function (in this case ajaxContentGeneration)
3) Wait for the function's ajax to complete and return the data generated (in this case html)
4) When completed pass that value to kModal.showContent(----Here----);
However currently I am getting:
1) Pulls ajaxContentFunctionName correctly
2) Calls Function (ajaxContentGeneration() function)
3) Calls kModal.showContent(undefined). This is called prematurely because the deferred isn't correctly waiting for the function call to complete (after the ajax is done).
4) Ajax Completes
Where am I messing up here ?

As far as I can tell, you are 95% there.
Use .then() instead of .done() and return the promise returned by $.ajax().then() :
function ajaxContentGeneration() {
return $.ajax({
url: "tests/ajax/AjaxTest.aspx",
data: { exampleType: "modal-ajax" },
dataType: "html"
}).then(function (data) {
return $(data).find("#ajax-content"); // this will return jQuery
// return $(data).find("#ajax-content").html(); // this will return html
});
}
You can probably also purge $.when() from the top-level call :
var ajaxContentFunc = $(origin).data("modal-content-handler");
window[ajaxContentFunc]().then(function (resp) {
// `resp` is whatever was returned by the `return $(data).find()...` statement above
kModal.showContent(resp);
});
The reason I say "probably" is that $.when() would be necessary if value-returning (not promise-returning) functions could be called instead of ajaxContentGeneration().

Another way would be to do:
// should really be renamed...
function ajaxContentGeneration(){
return $.ajax({
url : "tests/ajax/AjaxTest.aspx",
data : { exampleType: "modal-ajax" },
dataType : "html"
})
}
Somewhere else:
var ajaxContentFunc = $(origin).data("modal-content-handler");
window[ajaxContentFunc]()
.done(function(RES){
kModal.showContent( $(RES).find("#ajax-content") );
});
So the functionality of the ajaxContentGeneration function will be to return an AJAX promise, and not have it manipulated inside it, but do the manipulation where needed (getting the #ajax-content element from the response)
Note that this whole thing is bad practice JS design, and you should avoid having functions on top of the window object, but instead on another object.

Related

Stopping Code until AJAX call completes inside $.each loop

I have a function that loads HTML from an external file via an AJAX call using jQuery.
These ajax call runs inside of a $.each loop and I need this ajax call to finish before the loop continues. Here is my code:
$('img').each(function(){
var cotainer_html = $('.cotainer_html').clone();
/* Get Image Content */
$.ajax({
url: '/assets/ajax/get_studio_image.php',
type:'GET',
success:function(data){
cotainer_html.find('.replaceme').replaceWith(data);
}
});
});
I know I can set async:false but I hear that is not a good idea. Any ideas?
To achieve this you can put each request in to an array and apply() that array to $.when. Try this:
var requests = [];
$('img').each(function(){
var cotainer_html = $('.cotainer_html').clone();
/* Get Image Content */
requests.push($.ajax({
url: '/assets/ajax/get_studio_image.php',
type:'GET',
success:function(data){
cotainer_html.find('.replaceme').replaceWith(data);
}
}));
});
$.when.apply($, requests).done(function() {
console.log('all requests complete');
});
Note that you're replacing the same content on each request, so the only one which will have any effect on the UI is the last request. The preceding ones are redundant.
Also note that you should never, ever use async: false. It locks the UI thread of the browser until the request completes, which makes it look like it has crashed to the user. It is terrible practice. Use callbacks.
The OP appears to want the calls to run in series, not in parallel
If this is the case you could use recursion:
function makeRequest($els, index) {
var cotainer_html = $('.cotainer_html').clone();
$.ajax({
url: '/assets/ajax/get_studio_image.php',
type:'GET',
success:function(data){
cotainer_html.find('.replaceme').replaceWith(data);
if ($els.eq(index + 1).length) {
makeRequest($els, ++index);
} else {
console.log('all requests complete');
}
}
});
}
makeRequest($('img'), 0);
You can use a pseudo-recursive loop:
var imgs = $('img').get();
var done = (function loop() {
var img = imgs.shift();
if (img) {
var cotainer_html = $('.cotainer_html').clone();
/* Get Image Content */
return $.get('/assets/ajax/get_studio_image.php')
.then(function(data) {
cotainer_html.find('.replaceme').replaceWith(data);
}).then(loop);
} else {
return $.Deferred().resolve(); // resolved when the loop terminates
}
})();
This will take each element of the list, get the required image, and .then() start over until there's nothing left.
The immediately invoked function expression itself returns a Promise, so you can chain a .then() call to that that'll be invoked once the loop has completed:
done.then(function() {
// continue your execution here
...
});

Javascript: is there a better way to execute a function after x amount of async database/ajax calls

using Backbone.js we have an application, in which on a certain occasion we need to send an ajax post to a clients webservice.
however, the content to be posted, is dynamic, and is decided by a certain array.
for each item in the array we need to go fetch a piece of data.
after assembling the data that aggregated object needs to be sent.
as of now, i have a synchronous approach, though i feel that this is not the best way.
var arrParams = [{id: 1, processed: false},{id: 7, processed: false},{id: 4, processed: false}];
function callback(data) {
$.post()... // jquery ajax to post the data... }
function fetchData(arr, data, callback) {
var currentId = _(arr).find(function(p){ return p.processed === false; }).id; // getting the ID of the first param that has processed on false...
// ajax call fetching the results for that parameter.
$.ajax({
url: 'http://mysuperwebservice.com',
type: 'GET',
dataType: 'json',
data: {id: currentId},
success: function(serviceData) {
data[currentId] = serviceData; // insert it into the data
_(arr).find(function(p){ return p.id === currentId; }).processed = true; // set this param in the array to 'being processed'.
// if more params not processed, call this function again, else continue to callback
if(_(arr).any(function(p){ return p.processed === false }))
{
fetchData(arr, data, callback);
}
else
{
callback(data);
}
},
error: function(){ /* not important fr now, ... */ }
});
}
fetchData(arrParams, {}, callback);
isn't there a way to launch these calls asynchronous and execute the callback only when all results are in?
You have to use JQuery $.Deferred object to sync them. Look at this article Deferred Docs
You can use in this way:
$.when(
$.ajax({ url : 'url1' }),
$.ajax({ url : 'url2' }) // or even more calls
).done(done_callback).fail(fail_callback);
I would do something like this:
make a function that besides the parameters that you pass to fetchData also gets the index within arrParams, then in a loop call that function for every element. In the success function set processed in your element to true, and check if "you're the last" by going through the array and see if all the rest is true as well.
A bit of optimization can be if you make a counter:
var todo = arrParams.length;
and in the success you do:
if (--todo == 0) {
callback(...)
}

Chaining multiple jQuery ajax requests

I have the following code:
$.when(loadProjects())
.then(function() {
$.when.apply($, buildRequests(projects))
.then(function(data) {
$.when.apply($, vcsRequests(buildTypes))
.then(function(data) {
$.when.apply($, vcsDetailRequests(vcsRoots))
.then(function(data) {
alert('done');
});
});
});
});
Each of the functions passed into when.apply() return arrays of requests. I cannot perform the buildRequests calls until the calls from loadProjects() has finished as they rely on information returned from those calls. Each call depends on information returned by the previous call, so they must be in this order. I need to know when all the calls have finished so I can process the data returned.
Is there a cleaner way to approach this?
I came across yepnope.js the other day. I haven't tried it myself yet, but it might be helpful if you're doing a lot of ajax loading.
Actually thinking this over makes me realize that yepnope.js is not really applicable to your case. What I would consider in your case is to have loadProjects() et al return a single promise by applying when() internally in each function. Also putting pipe() to use could lead to something like
loadProjects().pipe(buildRequests).pipe(vcsRequests).pipe(vcsDetailRequests);
Sample buildRequests():
function buildRequests(projects){
// Do something using projects
// ...
var requestsPromise = ...; // Finally get ajax promise for requests
return requestPromise;
}
The result of the requestPromise will then be passed into the next piped function once it is resolved/rejected.
From the docs on pipe():
// Example: Chain tasks:
var request = $.ajax( url, { dataType: "json" } ),
chained = request.pipe(function( data ) {
return $.ajax( url2, { data: { user: data.userId } } );
});
chained.done(function( data ) {
// data retrieved from url2 as provided by the first request
});
.... response according to my comment on original post:
Seems you have lot of requests to chain. I would then consider
combining all request into single one.... much more efficient than
chaining...
Well, something like this:
PHP:
$projects = YourAPI::loadProjects();
$builds = YourAPI::getBuilds($projects);
$vcs = YourAPI::getVCS($builds);
$details = YourAPI::getVCSDetails($vcs);
// for example
return json_encode($details);
// OR, if you need all the data
$results = array(
"projects" => $projects,
"builds" => $builds,
"vsc" => $vcs,
"details" => $details
);
return json_encode($results);
This way, you have inherent synhronization between calls AND less HTTP trafiic ;)
Dependence chain of AJAX requests : You can chain multiple AJAX request — for example, first call retrieves the user details of a user, and we need to pass that value to second script. Remember that $.then() returns a new promise, which can be subsequently passed to the $.done() or even another $.then() method.
var a1 = $.ajax({
url: '/first/request/url',
dataType: 'json'
}),
a2 = a1.then(function(data) {
// .then() returns a new promise
return $.ajax({
url: '/second/request/url',
dataType: 'json',
data: data.userId
});
});
a2.done(function(data) {
console.log(data);
});

Getting undefined via ajax JSON

when I check the log from Console using chrome browser, I keep getting sideType is undefined. It is not returning data to sideType variable.
when I put console.log(sideType); in the sideGroupData() function - it work fine without problem.
Code:
function sideGroupData(GroupID) {
$.getJSON("group_data.php",{GroupID:GroupID}, function(j){
return j;
});
}
function reloadProduct(productID) {
$.post("product.php", { productID:productID }, function(data) {
var sideType = sideGroupData(123);
console.log(sideType);
});
}
reloadProduct(999);
It's because you're running your call in a closure. The ajax call is being made asynchronously, which means that your code continues moving even though you're making an ajax call:
function setVar () {
var retVal = 1;
$.ajax(..., function(data){
retVal = data; //retVal does NOT equal data yet, it's still waiting
});
return retVal; //will return 1 every time because code doesn't wait for ajax
}
var someVar = setVar(); // will be 1
If you want to return that value, include a callback function to run when the data is returned, and run it with the data supplied, like so:
function sideGroupData(GroupID, callback){
$.getJSON('group_data.php', {GroupID: GroupID}, callback);
}
function reloadProduct(productID) {
$.post("product.php", { productID:productID }, function(data) {
sideGroupData(123, function(sideType){
console.log(sideType);
});
});
}
or, just make the call inside the function itself:
function reloadProduct(productID, groupId) {
var prod, sideType;
$.post("product.php", { productID:productID }, function(data) {
prod = data;
$.getJSON('group_data.php', {GroupID: groupId}, function(json){
sideType = json;
// you now have access to both 'prod' and 'sideType', do work here
});
});
}
the sidegroupdata() function call will return immediately - it'll trigger the ajax request and keep on executing. That means sideType is being assigned a null value, because sideGroupData doesn't actually explicitly return anything after the ajax call section.
Is there any reason you're doing an ajax request WITHIN an ajax request? Wouldn't it make more sense to modify the product.php page to return a data structure containing both that product ID AND the 'sidegroupdata' included in a single response?

js variable scope question

how do i make data overwrite results variable ?
var ajax = {
get : {
venues : function(search){
var results = "#";
$.getJSON("http://x.com/some.php?term="+search+"&callback=?",function(data){ results = data; });
return results;
}
}
};
data is overwriting results, just after results has been returned.
You can use the ajax function instead of getJSON, since getJSON is just shorthand for
$.ajax({
url: url,
dataType: 'json',
data: data,
success: callback
});
and then also set async to false, so that the call will block.
However, in your case this won't work, because JSONP requests (with "?callback=?") cannot be synchronous.
The other (better) option is to have whatever code is dependent on the results return value get called by the success callback.
So, instead of something like this:
var results = ajax.get.venues('search');
$('#results').html(translateResults(results));
Maybe something like this:
ajax.get.venues('search', function (results) {
$('#results').html(translateResults(results));
});
venues = function (search, callback) {
$.getJSON("http://x.com/some.php?term="+search+"&callback=?",
function(data){
callback(data);
});
};
Your problem is the asynchronous nature of JavaScript. results does get overwritten, but only later, after the function has already exited, because the callback is executed when the request has finished.
You would have to make the Ajax call synchronous using sync: true (this is usually not a good idea, just mentioning it for completeness's sake) or restructure your code flow so it doesn't depend on the return value any more, but everything you need to do gets done in the callback function.
This isn't a scope problem. It's because $.getJSON is asynchronous; results is returned before $.getJSON finishes. Try making a callback for $.getJSON to call when it's done.
function JSON_handler(data){
// do stuff...
}
$.getJSON("http://x.com/some.php?term="+search+"&callback=?", JSON_handler);
You could put the logic you want to run in a callback.
var ajax = {
get : {
venues : function(search, fnCallback){
var results = "#";
$.getJSON("http://x.com/some.php?term="+search+"&callback=?",
function(data){
// success
results = data;
(typeof fnCallback == 'function') && fnCallback(data);
});
return results;
}
}
};
ajax.get.venues(term, function(result){
// Do stuff with data here :)
})
functional programming can be fun.

Categories

Resources