clearInterval() and setInterval() when all animations are complete - javascript

I have a function that is repeatedly being called with setInterval creating animations. If there are still animations running I need it to stop calling the function until all the animations are complete. The code I am using is as follows:
EDIT: Added coded where I am removing the animated elements from the DOM, is that the problem?
var serviceLoop = setInterval(serviceQueue, LOOP_POLL_MS); //10 ms
function serviceQueue()
{
//do some animations..
function moveMan(from, to, plane)
{
(function() {
var tmp_from = from;
var tmp_to = to;
var tmp_plane = plane;
var pos = tmp_from.offset();
var temp = tmp_from.clone(true);
temp.css({ "visibility":"visible",
"position":"absolute",
"top":pos.top + "px",
"left":pos.left + "px"});
temp.appendTo("body");
tmp_from.css("visibility", "hidden");
//if (!tmp_plane) tmp_to.css("visibility", "hidden");
temp.animate(to.offset(), moveMan.ANIMATION_TIME, function() {
tmp_to.css("visibility", "visible");
temp.remove();
});
})();
}
if ($(":animated").length > 0)
{
clearInterval(serviceLoop);
$(":animated").promise().done(function() {
serviceLoop = setInterval(serviceQueue, LOOP_POLL_MS);
});
}
}
The problem I am having is after a couple of animations the function passed to done() is never called, and the script stops.

It seems likely that you end up waiting on a promise() that is waiting on some animations, but then you remove some of those objects from the DOM and then their animation never finishes so the promise never resolves.
See this quote from the jQuery doc for .promise():
Note: The returned Promise is linked to a Deferred object stored on
the .data() for an element. Since the.remove() method removes the
element's data as well as the element itself, it will prevent any of
the element's unresolved Promises from resolving. If it is necessary
to remove an element from the DOM before its Promise is resolved, use
.detach() instead and follow with .removeData() after resolution.
One quick hack might be top call .stop(true) on any item that you are removing from the DOM.
In general, this code needs to be rewritten to be safe from that possibility and hopefully to rethink how you approach whatever it is you're trying to do every 10ms because that's generally a bad design. You should use events to trigger changes, not a 10ms polling operation. You have not explained the purpose of this code so it's not clear to me what alternative to suggest.

Related

jQuery run function after everything has run

I want to add colspan with jQuery to an element. If I insert this function into console in Chrome then it works fine, but if I put it in document ready and run it in the script then nothing happens. This function should run after all scripts have run if possible. Any tips will help, no access to HTML.
$( document ).ready(function() {
$("#fieldset_Q8aGrid td:eq(4)").attr('colSpan',3)
});
You probably have some asynchronous scripts running which add the content after your code runs. If you do not have access to that other code, then the most pragmatic solution is to try from time to time until it succeeds:
$( document ).ready(function() {
var timer = setInterval(function () {
if ($("#fieldset_Q8aGrid td:eq(4)").attr('colSpan',3).length) {
clearInterval(timer);
}
}, 500); // check every half-second.
});
I am assuming you have some async code in your program that, for example, create dynamic HTML. This is the case if you don't know when all the functions will completely execute.
We can split this problem into two parts.
If you call a function X after a sync code Y, X will happen after Y.
But if you have call a function X after an async code Y, you don't know what will execute first.
To solve 1) you just have to put your code at the end of your script.
To solve 2) you have to use jQuery's deferred and when to explicitly tell your program: execute this when X, Y, Z is done.
Quick explanation (and quite not precise) of what a promise is:
A promise is an object with a then method (thenable). The then method
can receive a function that will be called when the promise is
resolved
From an animation you can use the .promise() method to create a promise that will be resolved when the animation is done.
var animationPromise = $('h1')
.css({'left': '0', 'position' : 'relative'})
.animate({'left': '20px'}, 700, 'linear')
.promise()
If you are making a call to a service, the jQuery's post, get and ajax, return a promise
// save the promise for later use
var asyncPromise = $.get('https://jsonplaceholder.typicode.com/users');
// execute some the function that uses this data
asyncPromise.then(function(users) { /* do something with the data */ })
Maybe, you have some functionality that is neither an AJAX or an animation, in this case you can create your own promise. For example, this promise is resolved from a setTimeout.
var backgroundChange = $.Deferred()
setTimeout(function() {
$('body').css('background', 'lightblue')
backgroundChange.resolve(true)
}, 3000) /* Please note that backgroundChange
doesn't know that it will be resolved after 3 seconds */
Later on, you can use .when(...) to create a promise that is resolved when a set of promises are resolved.
$.when(asyncPromise, backgroundChange, animationPromise)
.then(function() {
$('.container').css('color', 'blue')
//alert('Everything is done!');
})
Here is a working example that solves the two problems https://codepen.io/anon/pen/JWBXLd?editors=1010
You can either rearrange your scripts to change their order, or go with window.load:
$(window).load(function() {
// code
});
But this will fire after literally all your page is loaded. Even images. So it'll be better to go with the first approach.

Issue with waiting for an event to be done then execute another

I'm trying to use jQuery Promises to solve a problem.
I have a UI in which I make an API call and waiting for a response (Which takes a while). I want to wait for the response to see if everything is loaded then do an action. How can I achieve this?
This is my code:
var d1 = new $.Deferred();
if(($('.fa-spin').length + $('.chart-loading').length )==0){
d1.resolve();
} //Logic behind here is, I'm checking for loading icon,
loading spinner and once they are destroyed,
I want to execute something!
$.when(d1).then(function() {
console.log("fetched");
//Run something
});
I want to check if my loading spinner is done, and then fire an event. How can I do this?
When I execute this code, the deferred never gets resolved and fetch is never printed
you'll need to continually check in order to resolve:
var d1 = new $.Deferred();
var testInterval = setInterval(function() {
if(($('.fa-spin').length + $('.chart-loading').length )==0){
clearInterval(testInterval);
d1.resolve();
}
},100);
$.when(d1).then(function() {
console.log("fetched");
//Run something
});
this will check every .1sec if the spinner's gone, and resolve your defer object when the condition is met. although, you could just run your code there instead of as a defer/resolve.
additionally it might be more effective to hook into your actual process rather than the existence of a spinner dom object, consider implementing an existing observer pattern or roll your own: observer pattern

How to check that element length has changed before continuing function - Javascript

Is there a way to pause a function to check the state of an element and only continue when the state has changed?
Here's my function :
JS
download : function() {
var loading = $('#stats-table > div > div.jtable-busy-message').html();
while (loading.length > 0) {
alert("Table still loading, please wait to download report");
loading = $('#stats-table > div > div.jtable-busy-message').html();
}
// continue with the rest of the function here
}
What should I replace the illustrative while loop with in the above function in order to pause the function and check the .length of loading for a change before proceeding with the function.
For information, the loading variable either has a length of 18 and 0 - so simply checking for this change in length.
So until the length changes do not continue with the rest of the method body.
UPDATE
I've found a simple way of doing this, so no longer require any answers - thanks
No, Javascript is (mostly) single threaded and its impossible for the state to change unless some other piece of code changed it. Any such change cannot happen "mid function". Your while loop will just cause the browser to lock up, permanently.
If you're waiting for an asynchronous load operation to complete, then your code needs to continue from within that operation's completion callback.
$.fn.exists = function(callback) {
var args = [].slice.call(arguments, 1);
if (this.length) {
callback.call(this, args);
}
return this;
};
// Usage
$('div.test').exists(function() {
//do your stuff
});
​
This might helps you :)

How to use Deferred objects in a for loop

I'm not sure about how to ask that.
I want to know the size of three images from which I have only the URL (needless to say, that's just an example).
Something tells me that Deferred is the way to do it... I've used it in other situations but I don't know how to chain various deferred objects together, one for each iteration of the for loop, and to have one done function for all of them (does it makes sense? I don't know if I'm making myself clear).
That's basically what I'm trying to do with this code, but naturally here the deferred will call done only once, and not for each image. I would like Deferred to be resolved only after I have the dimensions of all images.
update
I understand now, thanks to jgreenberg and Bergi, that I need a Deferred object for each image, as well as an array of Promises. New code:
var imagesURLArray = [
'http://placekitten.com/200/300',
'http://placekitten.com/100/100',
'http://placekitten.com/400/200'];
var promises = [];
function getImageDimensions(url){
var deferred = new $.Deferred();
var img = $('<img src="'+ imagesURLArray[i] +'"/>').load(function(){
deferred.resolve( {width: this.width, height: this.height} );
});
return deferred.promise();
}
for (var i = 0; i < imagesURLArray.length; i++) {
promises.push(getImageDimensions(imagesURLArray[i]));
}
$.when.apply($, promises).then(function(dimensions){
console.log('dimensions: ' + dimensions);
});
Sample here
However I still can't figure out how to retrieve data from all Deferred objects in then(). The dimensions argument returns only data from the first Deferred object.
Here's a little piece of code that extends the built-in jQuery promises to work for the .load() events for images. This allows you to do some very simple coding to wait for your group of images to load. Here's the add-on code for jQuery to support promises for image loading:
(function() {
// hook up a dummy animation that completes when the image finishes loading
// If you call this before doing an animation, then animations will
// wait for the image to load before starting
// This does nothing for elements in the collection that are not images
// It can be called multiple times with no additional side effects
var waitingKey = "_waitingForLoad";
var doneEvents = "load._waitForLoad error._waitForLoad abort._waitForLoad";
jQuery.fn.waitForLoad = function() {
return this.each(function() {
var self = $(this);
if (this.tagName && this.tagName.toUpperCase() === "IMG" &&
!this.complete && !self.data(waitingKey)) {
self.queue(function(next) {
// nothing to do here as this is just a sentinel that
// triggers the start of the fx queue
// it will get called immediately and will put the queue "inprogress"
}).on(doneEvents, function() {
// remove flag that we're waiting,
// remove event handlers
// and finish the pseudo animation
self.removeData(waitingKey).off(doneEvents).dequeue();
}).data(waitingKey, true);
}
});
};
// replace existing promise function with one that
// starts a pseudo-animation for any image that is in the process of loading
// so the .promise() method will work for loading images
var oldPromise = jQuery.fn.promise;
jQuery.fn.promise = function() {
this.waitForLoad();
return oldPromise.apply(this, arguments);
};
})();
This works by overriding the current .promise() method and when the .promise() method is called for each image in the collection that has not yet completed loading, it starts a dummy animation in the jQuery "fx" queue and that dummy animation completes when the image's "load" event fires. Because jQuery already supports promises for animations, after starting the dummy animation for each image that has not yet loaded, it can then just call the original .promise() method and jQuery does all the work of creating the promise and keeping track of when the animation is done and resolving the promise when the animations are all done. I'm actually surprised jQuery doesn't do this themselves because it's such a small amount of additional code and leverages a lot of things they're already doing.
Here's a test jsFiddle for this extension: http://jsfiddle.net/jfriend00/bAD56/
One very nice thing about the built-in .promise() method in jQuery is if you call it on a collection of objects, it will return to you a master promise that is only resolved when all the individual promises have been resolved so you don't have to do all that housekeeping - it will do that dirty work for you.
It is a matter of opinion whether the override of .promise() is a good way to go or not. To me, it seemed nice to just add some additional functionality to the existing .promise() method so that in addition to animations, it also lets you manage image load events. But, if that design choice is not your cup of tea and you'd rather leave the existing .promise() method as it is, then the image load promise behavior could be put on a new method .loadPromise() instead. To use it that way, instead of the block of code that starts by assigning oldPromise = ..., you would substitute this:
jQuery.fn.loadPromise = function() {
return this.waitForLoad().promise();
};
And, to retrieve a promise event that includes the image load events, you would just call obj.loadPromise() instead of obj.promise(). The rest of the text and examples in this post assume you're using the .promise() override. If you switch to .loadPromise(), you will have to substitute that in place in the remaining text/demos.
The concept in this extension could also be used for images in the DOM too as you could do something like this so you could do something as soon as a set of images in the DOM was loaded (without having to wait for all images to be loaded):
$(document).ready(function() {
$(".thumbnail").promise().done(function() {
// all .thumbnail images are now loaded
});
});
Or, unrelated to the original question, but also something useful you can do with this extension is you can wait for each individual images to load and then kick off an animation on each one as soon as it's loaded:
$(document).ready(function() {
$(".thumbnail").waitForLoad().fadeIn(2000);
});
Each image will fade in starting the moment it is done loading (or immediately if already loaded).
And, here's how your code would look using the above extension:
var imagesURLArray = [
'http://placekitten.com/200/300',
'http://placekitten.com/100/100',
'http://placekitten.com/400/200'];
// build a jQuery collection that has all your images in it
var images = $();
$.each(imagesURLArray, function(index, value) {
images = images.add($("<img>").attr("src", value));
});
images.promise().done(function() {
// all images are done loading now
images.each(function() {
console.log(this.height + ", " + this.width);
});
});
Working jsFiddle Demo: http://jsfiddle.net/jfriend00/9Pd32/
Or, in modern browsers (that support .reduce() on arrays), you could use this:
imagesURLArray.reduce(function(collection, item) {
return collection.add($("<img>").attr("src", item));
}, $()).promise().done(function() {
// all images are done loading now
images.each(function() {
console.log(this.height + ", " + this.width);
});
});;
You need to use http://api.jquery.com/jquery.when/
I tried to modify your code but I haven't run it. I believe you'll figure it out from here.
function getImageDimensions(url){
var deferred = new $.Deferred();
var img = $('<img src="'+ imagesURLArray[i] +'"/>').load(function(){
var dimensions = [this.width, this.height];
deferred.resolve(dimensions);
});
return deferred.promise()
}
var promises = []
for (var i = 0; i < imagesURLArray.length; i++) {
promises.push(getImageDimensions(imagesURLArray[i]));
}
$.when.apply($, promises).then(function(dimensions){
console.log('dimensions: ' + dimensions);
});

Loop Through jQuery Function Inifitly

I have a jQuery Animation which I want to loop infinitely, I have the current code but it just returns nothing.
$(document).ready(function() {
var i = 0;
document.write(i);
function runTest(){
$('#page_effect').fadeIn(1500).delay(3500).fadeOut(1500);
$('#page_effect2').delay(7000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#page_effect3').delay(13900).fadeIn(1500).delay(3500).fadeOut(1500);
$('#page_effect4').delay(21000).fadeIn(1500).delay(3500).fadeOut(1500);
i++;
runTest();
}
if(i === 0){
runTest();
}
});
Many Thanks! :)
You could wrap them all in a function and re-call the function after the last animation has finished:
function run(){
$('#page_effect').fadeIn(1500).delay(3500).fadeOut(1500);
$('#page_effect2').delay(7000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#page_effect3').delay(13900).fadeIn(1500).delay(3500).fadeOut(1500);
$('#page_effect4').delay(21000).fadeIn(1500).delay(3500).fadeOut(1500,run);
}
run();
Live example: http://jsfiddle.net/nCs6N/
First off, you shouldn't chain animations like this, use callbacks:
$("#page_effect").fadeIn(1500, function() {
$(this).delay(3500).fadeOut(1500);
});
This will wait for 3500ms to fadeOut, but only after the 1500ms fadeIn is complete.
This way using callbacks, you can call the function again from the last callback:
function runTest(){
...
$('#page_effect4').delay(21000).fadeIn(1500, function() {
$(this).delay(3500).fadeOut(1500, function() {
runTest();
// this will be called only after the fadeout completes
});
});
}
You're queueing the animations, but never "yielding" execution back to the browser, because you just call the function again immediately.
The browser never gets to enter its event loop, and the animations won't start.
To fix this, you'll need to make the browser wait until all of the animations have completed, and then queue them again:
$(function() {
(function animate() {
$('#page_effect').fadeIn(1500).delay(3500).fadeOut(1500);
$('#page_effect2').delay(7000).fadeIn(1500).delay(3500).fadeOut(1500);
$('#page_effect3').delay(13900).fadeIn(1500).delay(3500).fadeOut(1500);
$('#page_effect4').delay(21000).fadeIn(1500).delay(3500).fadeOut(1500);
// using deferred objects, ask for a promise that will be resolved
// when all animations on the specified elements have been completed,
// and when done, call myself to start all over again
$('#page_effect,#page_effect2,#page_effect3,#page_effect4')
.promise().done(animate);
})(); // invoke immediately
});
I note that your four separate effects are probably supposed to run in series, but the .promise() based solution above will also work if they were all running in parallel.
See http://jsfiddle.net/alnitak/ZKevs/
Note that if you are intending to run those effects in series you shouldn't really just queue them up all together - there's no timing guarantee and it's possible that the next element might start animation before the previous one finished.
The traditional solution to this was to add an "animation complete" callback in the last animation, but with four separate animations as you have here that would end up being horribly nested.
jQuery deferred objects can help here, too - note how this eliminates the additional calculated .delay() calls:
$('#page_effect').fadeIn(1500).delay(3500).fadeOut(1500);
$('#page_effect').promise().done(function() {
$('#page_effect2').fadeIn(1500).delay(3500).fadeOut(1500);
});
$('#page_effect2').promise().done(function() {
$('#page_effect3').fadeIn(1500).delay(3500).fadeOut(1500);
});
$('#page_effect4').promise().done(function() {
$('#page_effect4').fadeIn(1500).delay(3500).fadeOut(1500);
});
$('#page_effect4').promise.done(animate);
At which point you can see that every animation chain is identical and can be refactored:
function cycle($els) {
var i = 0, n = $els.length;
(function next() {
var $this = $els.eq(i);
i = (i + 1) % n;
$this.fadeIn(1500).delay(3500).fadeOut(1500).promise().done(next);
})();
});
cycle($('#page_effect,#page_effect2,#page_effect3,#page_effect4'));
Don't call runTest() recursively like that, you'll exhaust the function stack.
Instead use setTimeout(runTest, 0);

Categories