Ok, I have a div:
<div class="moreStuff"> <p>stuff in here matters</p></div>
and some Javascript:
weiredFunction(takes,some,values);
this "weirdFunction()" takes some values, does some non-important stuff and adds/appends it to the div.moreStuff.
Directly after "WeirdFunction()" I have my own function:
stuffIWantToDo(...);
the "stuffIWantToDo()" checks the div.moreStuff and reacts according to the data in it.
the problem is that it takes about 300ms for the "weirdFunction()" to do it's buisness and add it's data to the div .moreStuff but my function "stuffIWantToDo()" exicutes before 300ms; meaning there is nothing in the div .moreStuff when "stuffIWantToDo()" runs.
I want to prevent "stuffIWantToDo()" from running untill "weirdFunction()" appends the data to the div.moreStuff.
try make stuffIWantToDo() as a callback function in weiredFunction()
function weiredFunction(callback) {
// finish the weird thing
// Call the callback
callback('some','stuff','there');
}
function stuffIWantToDo(a, b, c) {
// I'm the callback
alert(a + " " + b + " " + c);
}
weiredFunction(foo);
Check this SO post: Create a custom callback in JavaScript
If you are using some jQuery calls, then we need to see it, as #tymeJV already said, look's like you have some async code involve in weiredFunction function
If not, your code must work synchronously and the only thing you need, is call your stuffIWantToDo functionas a callback, or at the end of the first one.
If none of this make sense to you, then use the old and well know setTimeout javascript function
like
function weiredFunction(takes,some,values){
/* some weired stuff here */
setTimeout(function() {
stuffIWantToDo(...);
}, 300);
}
If i understand you right, you have no possibility to change the weiredFunction. Based on that assumption, you have to write a small interval-based observer that fires your function once the content has changed.
var stuffContent = '';
window.setInterval(function() {
var currentStuffContent = $('.moreStuff').html();
if(currentStuffContent == stuffContent) return;
stuffContent = currentStuffContent;
stuffIWantToDo();
}, 50);
However, it would be the best if you could simply fire stuffIWantToDo within weiredFunction as a callback.
Related
I am creating tests for a page and HAVE to use jQuery to change elements on the control version of the page for each different experience.
I'm using jquery to load an element from an external page and replace a div. However, on the external page, it uses an ajax call to an api to populate the div, so I copied over the function with the ajax call.
I think it is attempting to make the ajax call before the new div is actually loaded on the page.
I've tried moving the function, wrapping the load function within the ajax call, but it still doesnt work.
I could be missing something obvious, but here's that part of my code:
$('.replace-div').load('external.html #replace-div');
function waitForLoad() {
if ($('#replace-div')) {
var object;
$.ajax({
url: "https://api.com",
async: false,
success: function(result) {
object = result;
var variable1 = object["blah"][0].value,
var variable2 = object["blah"][0].value,
var variable3 = object["blah"][0].value,
var variable4 = object["blah"][0].value,
$('newElement').attr('href', variable1);
$('newElement2').attr('src', variable2);
$('newElement3').attr('href', variable3);
$('newElement4').text("text" + variable4 + "more text");
}
});
} else {
setTimeout(waitForLoad, 15);
}
}
waitForLoad();
I don't get any errors in the console, and when I paste the above waitForLoad function into the console, it populates totally fine. obviously this is after the page loads the new div, so i just need to know how to make the ajax call wait for the load.
I've tried .ajaxComplete(), but it doesnt help.
$(function() {}); also does not work
.load() has a callback argument where you supply a function to run after the data is loaded.
$('replace-div').load('external.html #replace-div', function() {
$.ajax(...);
});
So, what happens is that, once waitForLoad is called for the first time, it doesn't see the div loaded, the code goes to the else block and executes again with a 15ms timeout. 15ms is not enough for the div to load, most likely.
You can try three things (ordered from worse to better):
Try increasing the timeout to a bigger number. Start with 1000 (1000ms - 1 second) and see if it works or you need to increase it. It's more likely you'll have to decrease it
Try using setInterval instead of setTimeout, which will repeat itself. Of course, once it loads, you'll need to clear the interval so it stops. Also, better use bigger timeouts/intervals, like 50 or 100ms b/c the fast firing timers can slow down a page a lot
E.g.
$('.replace-div').load('external.html #replace-div');
function waitForLoad() {
if ($('#replace-div')) {
clearInterval(window.waiter);
...
} else {
window.timer = setInterval(waitForLoad, 50);
}
}
waitForLoad();
Same as 2, but using more idiomatic callback function after load call.
// the window.waiter is the interval handle, and it will run every 100ms. It calls .load every time, and if the #replace-div is found, unsets the interval handle.
window.waiter = setInterval(function() {
$(".replace-div").load("external.html #replace-div", function() {
if ($(".replace-div #replace-div").length) {
// found the DIV
// code...
clearInterval(window.waiter); // bye interval handle, thanks
}
});
}, 100);
Ok, so I'm using Q.js to help with keeping users notified of a potentially long calculation going on asynchronously (using setTimeout with zero delay to repeatedly call a function a set number of times).
However, this does not work:
var status = d3.select('#status');
var t = transfer({
N: 100
});
t.then(function (values) {
status.text('');
column_chart(values);
}, function (error) {
status.text('There was an error calculating the transfer probabilities')
}, function (progress) {
status.text('Calculating ' + Math.round(progress) + '%');
});
For some reason the status element is undefined inside the resolve, reject and progress functions. However if I replace each status.text with d3.select('#status').text then it all works fine. I'm a bit confused as to why I can't used a cached element?
OK, there was a problem with the name of my cached element!
var status = d3.select('#status');
Changing its name fixed the problem
var statusEl = d3.select('#status');
It turns out inside my promise functions status was the raw HTML element which of course had no text method. This is that weird thing where all DOM elements with ID's are added to the global window object...
I have a question regarding the order of execution of JavaScript (listener) methods. While I appreciate the below code is probably not best practise, is there any way to guarantee the order the below functions will fire when btn1 is changed?
$(function() {
$('#btn1').change(function(){
doStuff();
});
});
$(function() {
$(#btn1, btn2).change(function(){
doMoreStuff();
});
});
E.g. is it possible to assert that based on the order the JS code "appears in" (i.e listed in the actual js / html file), that (when #btn1 changes):
1. that doStuff() will execute first
2. that doStuff() will complete fully before doMoreStuff() is invoked – assuming all doStuff is doing is updating the DOM
I have a real example, where doStuff updates the DOM and doMoreStuff invokes an Ajax endpoint, using the updated DOM values - and want to be sure doStuff will always be invoked first (again based on the flaky design that it is "listed" first).
Thanks,
Damien
As far as I'm aware, jQuery ensures event handlers fire in the order in which they were created (first in, first out). Currently I can't find any documentation on this, but I'm sure I have read that somewhere in the past.
As long as your first change function isn't asynchronous, it should be the case that the first function will finish execution before the second starts. We can test this by adding a loop within our first change function:
$(function() {
$('#btn1').change(function(){
console.log("First change event triggered at " + +(new Date()));
for (var i = 0; i < 100000000; i++)
continue;
console.log("First change event finished at " + +(new Date()));
});
});
$(function() {
$('#btn1, #btn2').change(function(){
console.log("Second change event triggered at " + +(new Date()));
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<select id="btn1"><option>1</option><option>2</option></select>
<select id="btn2"><option>1</option><option>2</option></select>
As you can see, the first finishes before the second starts.
I'm working on a JavaScript driven site where I will have a lot of stuff that need's to be executed in a certain order. A lot of the stuff involves animations and AJAX-loading. Some pseudo code could look like this:
Load JSON formated data
Generate HTML-elements using the loaded JSON data and render them inside a div
Make the elements inside the div scrollable using a jQuery UI slider
Randomize a number between 1 and the total number of loaded elements
Make the jQuery UI slider scroll (animate) to the element that represents the randomized number for a duration of 500 milliseconds
Load more JSON formated data
Replace other elements on the page
And so on...
Each step in this is wrapped in a function - one function loads the JSON data, another generates the HTML-elements, a third initializes the jQuery UI slider and so on. Encapsulating the code in functions makes the code easier to read for me, but above all I want to be able to call the functions in different orders depending on what happens on the page and I want to be sure that one function has finished running before the next one is executed.
If there was just regular functions that didn't involve AJAX or jQuery animations I'd just execute the functions I want to execute, one after the other. The problem is that I need to wait for the animations and data retrieving functions to finish before moving on. To aid me both the animation and AJAX methods in jQuery allow me to send along a callback. But here's where I get lost.
What I want it to do is the following:
Load JSON data. If the loading is successful, go on and...
Generate HTML-elements
Make the elements scrollble
Randomize a number between 1 and the total number of loaded elements and pass it to...
A function that makes the jQuery slider slide (animated) to the element. When the animation is finished...
Load more JSON formated data. If the loading is successful, go on and...
Replace other elements on the page
The ideal thing would be if I could set up this sequence/chain of events in one single place, for example inside an event handler. If I want to call the functions in a different order or not call all of them I would just set up a different sequence/chain. An example could be:
Randomize a number between 1 and the total number of loaded elements and pass it to...
A function that makes the jQuery slider slide (animated) to the element. When the animation is finished...
This means that I'd have to be in control over the callbacks in each step.
I hope you understand what I'm looking for. I want to control the entire execution sequence from a single function. This function would be "the conductor of the orchestra" and all the other functions would be the different instrument sections of the orchestra. This conductor functions need's ears so it can hear when the violinist is finished with her solo and can tell the horns to start playing. Excuse me for the corny allegory, but I hopes it makes it easier to understand what I want to do.
Thanks in advance!
/Thomas
Would the jQuery .queue() function help you?
Could you store a sequencer variable that is an array (which you would be able to change) and then call a sequencer at the end of each function?
You could then pass a step code through each function and cross-reference that with the sequencer variable as to what the next step should be.
Pseudo Code:
var func['1'] = function(arg1,arg2,seq) {
//code
sequencer(seq);
}
var func['2'] = function(arg1,arg2,seq) {
//code
sequencer(seq);
}
var func['3'] = function(arg1,arg2,seq) {
//code
sequencer(seq);
}
var func['4'] = function(arg1,arg2,seq) {
//code
sequencer(seq);
}
function sequencer(seq) {
seq = seq + 1;
window.func[seq]
}
I tried executing this code:
var seq = 0;
var func = [];
func[1] = function(seq) {
setTimeout(function() {
console.log("Executing function 1");
sequencer(seq);
}, 2000);
}
func[2] = function(seq) {
console.log("Executing function 2");
sequencer(seq);
}
func[3] = function(seq) {
console.log("Executing function 3");
}
function sequencer(seq) {
seq = seq + 1;
func[seq].call();
}
sequencer(seq);
But the result (in Firebug) is:
Executing function 1
func[seq] is undefined
[Break on this error] func[seq].call();
I think that the problem is caused by context, but I'm not sure. JavaScript is sensitive to the context in which a function is called.
/Thomas
I found that what I was trying to achieve was slightly overkill for my purposes. So I decided to go with a different approach. I can send one or more boolean variables as a parameters to a function and use them to decide whether to execute a second function or not. Here's an example:
$("#justOneStep").click(function() {
loadImage(false);
});
$("#twoStepsPlease").click(function() {
loadImage(true);
});
function loadImage(boolDoMore) {
// Do the stuff that loads an image
...
if(boolDoMore) {
nextFunction();
}
}
function nextFunction() {
// Do some more stuff
...
}
Not very fancy but easy to understand and control and sufficient for my needs at the moment.
/Thomas
I am trying to call showUpload(); from within two setTimeouts. Neither works. It seems to be out of scope and I'm not sure why. I tried this.showUpload() which didn't work either.
$(document).ready(function(){
var progress_key = $('#progress_key').val();
// this sets up the progress bar
$('#uploadform').submit(function() {
setTimeout("showUpload()",1500);
$("#progressbar").progressbar({ value:0}).fadeIn();
});
// uses ajax to poll the uploadprogress.php page with the id
// deserializes the json string, and computes the percentage (integer)
// update the jQuery progress bar
// sets a timer for the next poll in 750ms
function showUpload() {
$.get("/myid/videos/uploadprogress/" + progress_key, function(data) {
if (!data)
return;
var response;
eval ("response = " + data);
if (!response)
return;
var percentage = Math.floor(100 * parseInt(response['bytes_uploaded']) / parseInt(response['bytes_total']));
$("#progressbar").progressbar({ value:percentage})
});
setTimeout("showUpload()", 750);
}
});
Thank you for your time.
As #Daniel said, this should work:
setTimeout(showUpload, 750);
Please note that the quotes should be removed (this is why it isn't being executed until the timeout runs out). Right now, you are passing a string, which is evaled when the timeout runs out. This eval will happen in a different scope, which is why you are seeing the problem you are seeing.
Instead, passing a reference to the showUpload function to setTimeout will allow your function to be executed later. Keep in mind that when it runs, it will be in a different scope, so you may have other scope issues, like with progress_key. You will need to create a closure around showUpload to capture that parameter.
It looks like you need to remove the parenthesis from showUpload in both your setTimeout calls. Otherwise you will be invoking the showUpload method instead of passing it as a parameter:
setTimeout(showUpload, 750);