How can I get some javascript to run before a document ready function?
I have the following snippet finished first...
var applicantlist = [];
$.getJSON("apps.json", function(appsdata) {
for (var i = 0; i<appsdata.applications.length; i++){
var tempapp = [appsdata.applications[i].name, appsdata.applications[i].server];
applicantlist.push(tempapp);
}
});
I've tested this, and the data gets pushed into the array just fine. The problem is that I need this array to make some ajax calls that are found in my page ready function as follows...
$(document).ready(function(){
window.jsonpCallbacks = {};
alert(applicantlist.length);
for (var i = 0; i < applicantlist.length; i++){
(function(index){
window.jsonpCallbacks["myCallback" + index] = function(data){
myCallback(data,index);
};
})(i);
//Jquery/Ajax call to the WoW API for the data.
$.ajax({
"url":"http://us.battle.net/api/wow/character/" + applicantlist[i][1] + "/" + applicantlist[i][0] + "?jsonp=jsonpCallbacks.myCallback" + i,
"type":"GET",
"data": { fields: "items, talents, progression, professions, audit, guild, stats"},
"dataType":"jsonp",
"contentType":"application/json",
"jsonpCallback":"jsonpCallbacks.myCallback"+i,
"success":function(data1){
}
})
}
All of this fires off before the first snipet, no matter where I seem to put it. So, the array is empty (the alert message just shows "0").
As you can see by the URL of my ajax call, I need that array populated beforehand. I've tried putting the first snippet in a seperate .js file and calling it before all other javascript files on the actual HTML page...
What am I missing?
Move the code that sends the first request to the document.ready. You don't usually want anything happening before the document is ready. Then move the code that sends the next request(s) to the callback of the first request after you populate the array and do whatever else you need to happen first
$(document).ready(function () {
$.getJSON("apps.json", function(appsdata) {
...
// add items to your array
sendNextRequest();
}
});
function sendNextRequest() {
//Jquery/Ajax call to the WoW API for the data.
...
}
This gurantees that the calls to the WoW api don't get fired until the first $.getJSON call completes and you populate your array.
FYI this is a common challenge in javascript. You need one operation to run only after another finishes. When you use ajax, you have callbacks like in my example above that help you achieve this. Outside of ajax requests, you can use jQuery Promises to defer tasks until after something else finishes.
Related
For my website I am using an API where I need to load several variables from. Each of the variables are dependent on the return value of the previous call (I use the returned variable from call 1 to make call 2 etc).
Example:
Say that I need to make 5 different API calls to gather all of my data and each is dependent on the return value of the previous call. Then in my case I am doing like this. I am passing a callback function to the first function that loads the data. Then that function will make the first API call. When that call is finished it will pass the callback function to the next function that makes the second API call, and so on. When the last API call is finished the callback function gets called and then I know that all the data has been loaded. In code it would look something like this (I am using the Trello API in my application so I will use it in the example below):
function loadData(cb){
//Make the first API call
Trello.get('/member/me/boards', function(boards){
myBoards = boards;
for(var i = 0; i < boards.length; i++){
//Make the second API call
Trello.get('/boards/' + board[i].id + '/lists', function(lists){
board[i].lists = lists;
//Then make the third and fourth and so on
.....
//When all calls are made call the callback function
cb();
});
});
}
As you can see the callback function will be passed a long way into the callstack. I was wondering if there is a better way to load the data and to store it (as of now I just store everything in a large array). And what is some best practices for loading large amount of data from an API?
P.S. In my original code each of the API calls are in separate functions, but I simplified it here to reduce the amount of code in the example.
I don't know if this is an option for you but using TypeScript makes solving this kind of JavaScript problem much more simple:
async function loadData() {
const boards = await Trello.get('/member/me/boards');
return boards.map(async (board) => {
const lists = await Trello.get('/boards/' + board.id + '/lists');
const something = await Trello.get('/...');
const somethingElse = await Trello.get('/...');
// ...more calls
return {
...board,
lists: lists,
something: something,
somethingElse: somethingElse
// ... more attributes
};
});
}
loadData().then((data) => console.log(data));
Without fully understanding your problem this may not be a valid solution, but taking a quick glance at the trello api docs shows a batch call you could make to avoid looping at each level. Batching these would allow for many fewer API calls at each level and would be considered a best practice:
function loadData(cb){
//Make the first API call
Trello.get('/member/me/boards', function(boards){
myBoards = boards;
var boardAPIs = [];
var boardResponses = [];
for(var i = 0; i < boards.length; i++){
boardAPIs.push('/boards/' + board[i].id + '/lists');
//max of 10 at a time per documentation
if (boardAPIs.length == 10 || i >= (boards.length - 1)) {
//Make the second level API call
Trello.get('/batch/?urls=' + boardAPIs.join(','), function(boards){
// collect response information on all boards, then continue with third request
boardResponses.push(...);
if (i >= (boards.length - 1)) {
// all board requests have been made, continue execution at third level
// if this were the last level of calls, you could call cb() here
for(var j = 0; i < boardResponses.length; i++){
// loop inside responses to get individual board responses, build up next set of batch requests
}
}
});
boardAPIs= [];
}
});
});
}
One thing to note here: the docs mentioned that you can only batch 10 requests at a time, so I added some code in there to check for that.
This post provides more information on how to consume the batch service:
this means you get only a single response back, and it looks a little
different from a normal response. The response is an array of objects
– but not of the normal response objects you might expect. Instead,
it’s an object with a single property, with a name set to the HTTP
response code of the request.
You may focus on a deep first approach, so that the first data arrives fast at the client:
function loadData(showChunk){
//Make the first API call
Trello.get('/member/me/boards', function(boards){
myBoards = boards;
(function getboard(i){
//Make the second API call
Trello.get('/boards/' + board[i].id + '/lists', function(lists){
board[i].lists = lists;
//Then make the third and fourth and so on
.....
//When all calls are made for the first board call the callback function, and also continue with the next board
showChunk();
if(i+1<boards.length) setTimeout(getboard, 1, i+1);
});
})(0);
});
}
I have a page that should load after the initial page load a bit of data trough AJAX that is then used in a few functions.
So far I can only get it to work with loading the AJAX requests separately (which means the same request is called like 30 times)
What I need is the possibility to have a function that can be called multiple times, but only activates the AJAX call once and the other times gives the data back without having again the same AJAX call that already gave the data back running (cause that's redundant and not needed, the data doesn't change).
Now I could do that by simply making a call and store it in a global variable and just check if something is in this variable or not...
BUT! The "but" is the problem, that these around 20 calls that need the information the AJAX delivers happen right after the DOM is loaded, right together with the AJAX call.
And so I cannot do that, because the 20 requests happen before the first AJAX call even finished showing all data.
I tried to do some stuff with JQueries "deferred", but could only manage to do it with one call and not with multiple calls at almost the same time without that it triggers the AJAX call everytime.
But I'm sure that must be possible somehow! Nicely, without some sort of loops and timeout. I really like the idea of loading pages and parts of pages partially. Input field isn't loaded right from the start, but gets delivered as soon as it is ready, etc...
Is it? I really can't wrap my head around this one...
$(function(){
loadme1();
loadme2(); /* loaded from complete different parts in the code, so not possible to start loadme2 only after loadme1 has everything finished */
});
function getData(){
return $.get("/pathtogetthedata", {}, function(data){
});
}
function loadme1(){
getData().done(function(data){
var obj = $.parseJSON(data);
/* do something with obj */
}
}
function loadme2(){
getData().done(function(data){ //please just wait till the first call to the same method finished and give me that data or wait till it's in a global variable and I take it from there. Only make a call if there is no jquery "promise" waiting
var obj = $.parseJSON(data);
/* do something with obj */
}
}
You have to keep all the "callback" and then when the data ready, to call the callback you just saved for example:
var funcs = []
function exampleOfAjaxGetData(callback) {
funcs.push(callback)
if (funcs.length == 1) {
setTimeout(function() {
alert('This is need to be called once1')
while (funcs.length > 0)
funcs.pop()('The data return from ajax')
}, 2000)
}
}
exampleOfAjaxGetData(function(x) {
alert('I got the data:' + x)
})
exampleOfAjaxGetData(function(x) {
alert('I got the data:' + x)
})
jsFiddle: http://jsfiddle.net/yn5ayw30/
In the example I show you a function that takes 2 seconds to complete.
I called the function twice. But the "setTimeout" run only once. When setTimeout complete, it will run all the function that wait for answer.
var getDataCalled = false;
var deferred = $.Deferred();
function getData(){
if(!getDataCalled) {
getDataCalled = true;
return $.get("/", {} , function(data) {
deferred.resolve(data);
});
} else {
console.log("returning deferred");
return deferred;
}
}
How about you save when you first call your "getData" function. When it has already been called you return your own "deferred" object back and resolve it when your first ajax request is finished.
I hope this short code snippet speaks for itself and is easy to understand.
Calling getData() will now first make the ajax request and after that always return a deferred object you created yourself.
getData().done(function(data) {
console.log(data);
});
getData().done(function(data) {
console.log(data);
});
getData().done(function(data) {
console.log(data);
});
You will see there will only be one ajax request.
I can think of one solution here it is :
var adata = -1; // global variable data holder
function getdata()
{
//if ajaxx call is already done and completed then return data
if(adata != -1 && adata != -2)return adata;
if(adata == -1)
{
//function getting called first time
adata = -2; // now we change value of adata to -2
// we will use this -2 to check if ajaxx call is stil running
//do ajaxx $.get call
$.get( "url_goes_here", function( data ) {
adata = data;// assingh received data to adata, so -2 is changed now
});
//now code will move to while loop part even after first call as while loop part doesn't have condition
//thus waiting for ajaxx call to be completed even if its first call
}
while(adata == -2){
//just a loop to delay output until call finishes
}
return adata;
}
Now you can use getdata() function to achieve what you want
My problem is a bit hard to explain, but I'll try my best. I have 3 tabs, one of which is active by default. Every time a tab is activated, an event fires. However, the tab headings are set in a function set earlier. My code is given below:
<script>
var queues = {};
$(document).ready(function(){
getQueueData();
$('.nav a[href="#queue1"]').tab('show');
});
function getQueueData(){
$.post('interface/getqueues.php', function(data){
var str = "#q";
for (var i=0; i<data.data.length; i++){
str += i;
$(str).text(data.data[i]["name"]);
queues[str] = data.data[i];
console.log(queues[str]);
str = "#q";
}
});
}
$('a[data-toggle="tab"]').on("shown.bs.tab", function(event){
console.log("HELLO WORLD!");
var elem = $(this).attr("id");
var id = "#"+elem;
console.log(queues);
});
</script>
Now, the getQueueData() function should execute BEFORE the shown.bs.tab event is fired. This is necessary for the latter event to be able to access the values in the queues object. In fact, however, when the page is loading, the shown.bs.tab function is executing first, followed by getQueueData(). This means that in the shown.bs.tab method, queues is undefined the first time. I know it because the HELLO WORLD and an empty object is printing in the log before the console.log(queues[str]) output. Does anyone know how I can make the getQueueData() execute completely before going to the 'shown.bs.tab' event?
Haha!
Pretty common question for beginners in javascript's asynchronous realm. :)
Welcome to the jungle mate. ;)
Here is the magic trick. Move your $('.nav a[href="#queue1"]').tab('show'); inside $.post function. Put it after your for loop. :)
function getQueueData(){
$.post('interface/getqueues.php', function(data){
var str = "#q";
for (var i=0; i<data.data.length; i++){
str += i;
$(str).text(data.data[i]["name"]);
queues[str] = data.data[i];
console.log(queues[str]);
str = "#q";
}
$('.nav a[href="#queue1"]').tab('show'); // << Triggre your show event here.
});
}
And everything will work great.
Will be updating this answer with explanation, soon. :)
==
EDIT:
Explanation.
Your concern, that getQueueData() should execute BEFORE shown.bs.tab is already fulfilled. However, it does appear that getQueueData() gets executed after your tabs show event.
Why is that?
Because you have an asynchronous call inside your getQueueData(). The script will make an API call to interface/getqueues.php and because it is asynchronous, it's heroic nature takes over and it doesn't wait until the server responds. It continues it's execution and returns from function getQueueData() and goes ahead and triggers the show event on your tabs (because the next statement after a call to getQueueData is your tabs's show statement.
When the server responds, it will trigger the callback you have specified in your $.post function call.
Therefore when this callback executes (which is after response is received from the server, the show event on your tabs has already taken place.
And this is why it seems like getQueueData is getting executed after tabs show event.
But in reality, it is executed before the tabs show event and the call back of $.post happens whenever it's necessary.
For more information, please read this awesome guide.
post is an asynchronous method. It means that its result is not necessarily get just after it is called. Javascript is such language that it won't wait for the result of an async method to continue to execute the rest of code.
Thus, you have to synchronizde your code by your own.
One thing that you can use for this purpose is deferred object. When you create it, it has a pending state. Whenever you resolve or reject over that object, you will get notified.
Below, notice how you could use it.
You can wait for a function to be finished with a deferred object.
.
<script>
var queues = {};
var deferred = $.Deferred();
$(document).ready(function(){
getQueueData();
deferred.then(function() {
$('.nav a[href="#queue1"]').tab('show');
});
});
function getQueueData(){
$.post('interface/getqueues.php', function(data){
var str = "#q";
for (var i=0; i<data.data.length; i++){
str += i;
$(str).text(data.data[i]["name"]);
queues[str] = data.data[i];
console.log(queues[str]);
str = "#q";
}
deferred.resolve();
});
}
I would like to send multiple request by using ajax and return to data in a for loop (one by one, one finished, one start...).
However, it seems sometimes it sends a new data without waiting for the previous one completed. as a result, the data returned is not correct. Although it happens not frequently, anyone could help me to solve the problem?
for (var i=0; i<myarray.length;i++){
ajaxfunction(myarray[i]);
}
My assumption is to:
setTimeout in the loop, but the result seems wrong
I have to use RAW javascript, not JQuery or other library. I found a interesting things called "complete:" and ".done()" in JQuery, I am not sure if that is what I want. May anyone tell me how to solve the problem by using raw javascript?
I usually use recursive to solve this problem but don't know if it's having bad side or not. The code something like this:
function doRequest(index, collection){
$.ajax({
url: collection[index],
....
complete: function(){
//do something
if (index + 1 < collection.length) doRequest(index + 1, collection);
}
});
}
doRequest(0, myArray);
Recursive call did magic to solve the problem of finish one ajax call and wait for another request until first request finished.
var queue_element = ["a","b","c","d","e","f","g"];
var execute_queue = function(i){
$.ajax( {
url: queue_element[i],
success: function(
{
i++; // going to next queue entry
// check if it exists
if (queue_element[i] != undefined)
{
execute_queue(i);
}
}
}); // end of $.ajax( {...
}; // end of execute_queue() {...
var index = 0;
execute_queue(index); // go!
I'm accessing a json file which has 50 entries per page over x amount of pages.
I have the total number of entries, say 500 - which amounts to 10 pages.
I get the data from json file for page 1, pass the data to an array and then repeat the function but this time for page 2.
I have created the function and it loops perfectly incrementing and fetching each page, but it doesn't wait for the json data to be parsed and passed to the array before looping again.
Basically I want to wait until the data has been processed and then continue on.
My code so far is roughly this:
function getJsonData(metroID){
currentPageNo = 0;
totalPages = 'x';
count = 0;
function jsonLoop(){
meroAreaSearchString = 'http://jsonurl'+currentPageNo;
$.getJSON(meroAreaSearchString,{},function( data ){
if(totalPages == 'x'){
var totalEntries = data.resultsPage.totalEntries;
var perPage = data.resultsPage.perPage;
totalPages = (totalEntries/perPage);
log(totalEntries+', '+perPage+', '+totalPages);
log(Math.round(totalPages));
}
$.each(data.resultsPage.results.event, function(i,item){
var name = item.displayName;
var type = item.type;
var valueToPush = new Array();
valueToPush[0] = name;
valueToPush[1] = type;
valueToPush[3] = count;
locations.push(valueToPush);
count++;
});
});
if(currentPageNo == totalPages){
log(locations);
alert('finished processing all results');
}else{
currentPageNo++;
jsonLoop();
}
currentPageNo++;
jsonLoop();
}
}
Have you tried making the request syncronous?
Just put this piece of code at the top of your function getJsonData
$.ajaxSetup({async:false});
You can specify the async option to be false to get a synchronous Ajax request. This will stop your function until the callback set some data.
The $.getJSON() function fires off an AJAX request, and calls it's callback function when the AJAX call resolves successfully, if that makes any sense.
Basically, that just means that given a call $.getJSON(url,data,callback);, jQuery will fire an AJAX request to url passing data along with it, and call callback when that call resolves. Clear cut straightforward.
The thing you're missing here is that an AJAX call is just that -- as its name implies, its asynchronous. This means that throughout the whole lifetime of the AJAX call, it lets the other logic in your application run instead of waiting for it to finish.
So something like this:
$.getJSON(url, data, callback);
alert('foo');
... will most probably result in an alert() call happening before your AJAX call completes. I hope that made sense.
To make sure that something happens after your AJAX call completes, you put that logic inside the callback. That's really what the callback is for.
$.getJSON(url, data, function (d) {
something_you_want_done_after_ajax_call();
});
In the context of your problem, you just have to put all that conditional recalling of jsonLoop() into your callback. It's not very obvious right now because of your indenting, but it's currently outside your callback.