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();
});
}
Related
While waiting for the back end devs to implement a "cancel all" feature, which cancels all tasks tracked by the back end, I am attempting to makeshift it by cancelling each individual task. The cancel REST service accepts an ID in the form of a data object {transferID: someID}.
I use a FOR loop to iterate over an array of IDs that I have stored elsewhere. Anticipating that people MAY end up with dozens or hundreds of tasks, I wanted to implement a small delay that will theoretically not overflow the number of HTTP requests the browser can handle and will also reduce a blast of load on the back end CPU. Here is some code with comments for the purpose of this discussion:
ta.api.cancel = function (taskArray, successCallback, errorCallback) {
// taskArray is ["task1","task2"]
// this is just the latest attempt. I had an attempt where I didn't bother
// with this and the results were the same. I THOUGHT there was a "back image"
// type issue so I tried to instantiate $.ajax into two different variables.
// It is not a back image issue, though, but one to do with setTimeout.
ta.xhrObjs = ta.xhrObjs || {};
for (var i = 0; i < taskArray.length; i++) {
console.log(taskArray); // confirm that both task1 and task2 are there.
var theID = taskArray[i];
var id = {transferID: theID}; // convert to the format understood by REST
console.log(id); // I see "task1" and then "task2" consecutively... odd,
// because I expect to see the "inside the setTimeout" logging line next
setTimeout(function () {
console.log('inside the setTimeout, my id is: ')
console.log(id.transferID);
// "inside the setTimeout, my id is: task2" twice consecutively! Y NO task1?
ta.xhrObjs[theID] = doCancel(id);
}, 20 * i);
}
function doCancel(id) {
// a $.Ajax call for "task2" twice, instead of "task1" then "task2" 20ms
// later. No point debugging the Ajax (though for the record, cache is
// false!) because the problem is already seen in the 'setTimeout' and
// fixed by not setting a timeout.
}
}
Thing is: I know setTimeout makes the containing function execute asynchronously. If I take out the timeout, and just call doCancel in the iterator, it will call it on task1 and then task2. But although it makes the call async, I don't understand why it just does task2 twice. Can't wrap my head around it.
I am looking for a way to get the iterator to make the Ajax calls with a 20ms delay. But I need it to call on both! Anybody see a glaring error that I can fix, or know of a technique?
You must wrap your function setTimeout and pass the id variable into it, like this:
(function(myId, i) {
setTimeout(function () {
console.log('inside the setTimeout, my id is: ', myId);
}, 20 * i);
}(theId, i));
This pattern does not create a unique variable1 for each instance of the loop as one might expect.
function () {
for (var i = 0; i < length; i++) {
var variable1;
}
}
In javascript variables are "hoisted". To quote Mozilla:
"Because variable declarations (and declarations in general) are
processed before any code is executed, declaring a variable anywhere
in the code is equivalent to declaring it at the top."
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var
So it should be re-written as:
function () {
var variable1;
for (var i = 0; i < length; i++) {
}
}
What this means is that after the loop has finished, any asynchronous callbacks that reference this variable will see the last value of the loop.
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.
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.
I want to make sure I understand callbacks properly, and javascript timing etc. in general.
Say my code looks like this, is it guaranteed to execute in order?
SetList(); // initializes the var _list
Some.Code(_list, function(data) {
// update list
});
DoSomething(_list); // operates on _list
Update
What I am seeing is SetList calls, then DoSomething, then Some.Code.
Some.Code calls another function. so:
Some.Code(_list, function() {
//load _list from ajax request
Other.Code.WithCallback(_list, function(){....});
});
I guess to fix this, I have to add DoSomething to the inner function as another callback?
SetList(), Some.Code() and DoSomething() will execute in that order, one after the other. The anonymous function passed as the second argument to Some.Code() could be called during the execution of Some.Code() (before the function returns and DoSomething() is called) or it could be called at a later time by another function, and event handler or timer, it all depends on when you specified it to be called.
Since you're using ajax, the request to the remote server is made on a separate thread, so the executing javascript thread continues to run and call other functions until a response (or, more specifically, for the onreadystatechange event to fire). When the ready state of the ajax request changes, its readystatechange event handler is queued to be called -- meaning it will execute as soon as all currently executing scripts finish.
If you want DoSomething() to execute after the response is received via ajax, you should run it to the end of your callback function instead.
That code would execute in order:
SetList(), then Some.Code(), then function(data), then DoSomething().
JavaScript is single-threaded, and executes in order. The only way that things would happen out of sync is if you set an interval/timer within Some.Code() or function(data) that called another function.
If you had:
var i=0;
functionCall() //some long process that sets i=1;
if (i==1) { alert("In Order!"); } else { alert("Out of Order!"); }
That would alert "In Order," But if you had:
var i=0;
setTimeout(functionCall, 1000) //some long process that sets i=1;
if (i==1) { alert("In Order!"); } else { alert("Out of Order!"); }
That would execute "Out of Order," because the third line would execute before functionCall() is called.
Updated Answer
Because you are using Ajax, I'm guessing you are making an asynchronous call, which is the reason for the delay. You have a callback function, but it's still waiting to be called back, so Javascript moves on to execute the next line while it waits.
To execute in the order you want, you'll need to do this:
SetList(); // initilizes the var _list
Some.Code(_list, function(data) {
// update list
DoSomething(_list); // operates on _list
});
This way, you can ensure that DoSomething() is called when your callback method is called, and not before.
So, I have a page that loads and through jquery.get makes several requests to populate drop downs with their values.
$(function() {
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
LoadContact();
};
It then calls LoadContact(); Which does another call, and when it returns it populates all the fields on the form. The problem is that often, the dropdowns aren't all populated, and thus, it can't set them to the correct value.
What I need to be able to do, is somehow have LoadContact only execute once the other methods are complete and callbacks done executing.
But, I don't want to have to put a bunch of flags in the end of the drop down population callbacks, that I then check, and have to have a recursive setTimeout call checking, prior to calling LoadContact();
Is there something in jQuery that allows me to say, "Execute this, when all of these are done."?
More Info
I am thinking something along these lines
$().executeAfter(
function () { // When these are done
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
},
LoadContact // Do this
);
...it would need to keep track of the ajax calls that happen during the execution of the methods, and when they are all complete, call LoadContact;
If I knew how to intercept ajax that are being made in that function, I could probably write a jQuery extension to do this.
My Solution
;(function($) {
$.fn.executeAfter = function(methods, callback) {
var stack = [];
var trackAjaxSend = function(event, XMLHttpRequest, ajaxOptions) {
var url = ajaxOptions.url;
stack.push(url);
}
var trackAjaxComplete = function(event, XMLHttpRequest, ajaxOptions) {
var url = ajaxOptions.url;
var index = jQuery.inArray(url, stack);
if (index >= 0) {
stack.splice(index, 1);
}
if (stack.length == 0) {
callback();
$this.unbind("ajaxComplete");
}
}
var $this = $(this);
$this.ajaxSend(trackAjaxSend)
$this.ajaxComplete(trackAjaxComplete)
methods();
$this.unbind("ajaxSend");
};
})(jQuery);
This binds to the ajaxSend event while the methods are being called and keeps a list of urls (need a better unique id though) that are called. It then unbinds from ajaxSend so only the requests we care about are tracked. It also binds to ajaxComplete and removes items from the stack as they return. When the stack reaches zero, it executes our callback, and unbinds the ajaxComplete event.
You can use .ajaxStop() like this:
$(function() {
$(document).ajaxStop(function() {
$(this).unbind("ajaxStop"); //prevent running again when other calls finish
LoadContact();
});
LoadCategories($('#Category'));
LoadPositions($('#Position'));
LoadDepartments($('#Department'));
});
This will run when all current requests are finished then unbind itself so it doesn't run if future ajax calls in the page execute. Also, make sure to put it before your ajax calls, so it gets bound early enough, it's more important with .ajaxStart(), but best practice to do it with both.
Expanding on Tom Lianza's answer, $.when() is now a much better way to accomplish this than using .ajaxStop().
The only caveat is that you need to be sure the asynchronous methods you need to wait on return a Deferred object. Luckily jQuery ajax calls already do this by default. So to implement the scenario from the question, the methods that need to be waited on would look something like this:
function LoadCategories(argument){
var deferred = $.ajax({
// ajax setup
}).then(function(response){
// optional callback to handle this response
});
return deferred;
}
Then to call LoadContact() after all three ajax calls have returned and optionally executed their own individual callbacks:
// setting variables to emphasize that the functions must return deferred objects
var deferred1 = LoadCategories($('#Category'));
var deferred2 = LoadPositions($('#Position'));
var deferred3 = LoadDepartments($('#Department'));
$.when(deferred1, deferred2, deferred3).then(LoadContact);
If you're on Jquery 1.5 or later, I suspect the Deferred object is your best bet:
http://api.jquery.com/category/deferred-object/
The helper method, when, is also quite nice:
http://api.jquery.com/jQuery.when/
But, I don't want to have to put a bunch of flags in the end of the drop down population callbacks, that I then check, and have to have a recursive setTimeout call checking, prior to calling LoadContact();
No need for setTimeout. You just check in each callback that all three lists are populated (or better setup a counter, increase it in each callback and wait till it's equal to 3) and then call LoadContact from callback. Seems pretty easy to me.
ajaxStop approach might work to, I'm just not very familiar with it.