Fire a callback only once on animating two objects - javascript

I was starting to make a quite complex animation with jQuery and I was looking for a certain way to animate elements together. I wrote the code somewhat as follows, which I could use to animate multiple elements together:
Here's the fiddle: http://jsfiddle.net/wkhrU/
$(document).ready(function(){
$('#firstElement, #secondElement').animate(
{left : '+=400px'},
500,
"linear",
function(){
alert("completed");
});
});
However, the callback function fires twice i.e. each time for the element inside. What I was looking for is to be something like animating multiple elements simaltaneously for the same time duration and after the completion, fire a call inside which I wanted to swap the id attrib of the elements. I can't do the same here because on completion callback, it swaps the ids once and on the next function callback, swaps them back again. What's the appropriate way to achieve this?

SOLUTION
You could maybe use a promise() to call the function to be executed instead of the inbuilt callback of animate. This ensures that the function in done is called only after animating both the elements. It's basically like you're asking #firstElement & #secondElement to promise JS that it'll inform when both of 'em are done so that you could attach a done handler to it.
$(document).ready(function () {
$('#firstElement, #secondElement').animate({
left: '+=400px'
},
500, "linear").promise().done(function () {
alert("completed");
});
});
DEMO
http://jsfiddle.net/hungerpain/wkhrU/1/
MORE INFO ON USED METHODS :
promise
Docs : http://api.jquery.com/promise/
What it does : Makes sure that all actions of a certain type bound to the collection, queued or not, have finished.
done
Docs : http://api.jquery.com/deferred.done/
What it does : Add handlers to be called when the promise is resolved.

Related

Does dojo/_base/fx support promises?

I am need to execute a method after an animation on a dom element ends.
At the moment I am using the following code with no success.
I would like to know if dojo, support promise for animation, if no, I would like to know in which way I could work it out.
fx.fadeOut({ node: 'target'}).play().then(function () {
// do smt here
}.bind(this));
Yes can you use the registered Callback of the FadeOut function.
the fadeOut (dojo/_base/fx function which returns an annimation instance)
provides 5 registred callback functions :
beforeBegin
onBegin
onEnd
onPlay
onAnimate (with argument)
In your case you need to make some actions when the animation is done so passe the onEnd function in the FadOut arguments, it should look like :
fx.fadeOut({
node:"target",
onEnd: function(){
// Some stuff at the end of the animation.
}
}).play();
you can also use the above callback as args .
I was able to solve this issue using 'dojo/on' and subscribing to the End event on fx.fadeOut(). Still I am very interesting to understand if there are better way for the same result.
var anim = fx.fadeOut({ node: 'target'}).play();
on(anim, "End", function () {
// dom smt here
}.bind(this));

understanding how $.promise works similar to transitionEnd

I was just messing around on the documentation page of jQuery.promise() and came across the following peice of code :
$("button").on("click", function () {
$("p").append("Started...");
$("div").each(function (i) {
$(this).fadeIn().fadeOut(1000 * (i + 1));
});
$("div").promise().done(function () {
$("p").append(" Finished! ");
});
});
FIDDLE HERE
Now I do understand that $.defer in jQuery assists in Asynchronous programming, also I understand that $.done and $.fail are part of the $promise object .
I have read an interesting article HERE. There are a few good examples of how $.defer can be used to monitor css-3 transitions.
However in the fiddle example I provide, I fail to understand how $.promise picks up the fact that the transition is complete. How does promise pick up that that fadeout() is complete?
How does the below piece of code really work?
$("div").promise().done(function () {
$("p").append(" Finished! ");
});
How is promise really working here? Can anyone explain?
To put it simply, jQuery creates a queue of Deferred objects on each object returned by the $("div") selector (these are visible using the .data() function).
When you add some CSS animations to the divs with jQuery functions such as fadeIn() or fadeOut(), it creates Deferred objects that are appended to each individual div queues.
Using$("div").promise().done() on the parent collection allows to check if all of the children Deferred object queues are empty (jQuery will iterate on the children elements).
I haven't delved into the jQuery source, but here's my understanding.
$.promise returns a Promise which completes once all actions of a certain type have ended. By default, the 'type' is fx (source).
When the fx queue is empty, the promise will resolve.
In your fiddle, you call fadeIn(), which adds the animation to the fx queue. ($.fadeIn() has queue: true by default.) $.fadeOut does the same.
When the queue is empty, the promise will resolve. This fiddle would support that. (Queue is 'inprogress' whilst the animations are running, but empty 100ms later.)
A slightly more convoluted fiddle - notice how the promise completes when we clear the fx queue using $(el).queue('fx',[]);?

How to remove an attribute after animation in jquery

I'd like to remove an id from an image after its animation is completed. I have this in my code:
if(index == 1 && direction =='down'){
$('#slidetext1 #rock').animate({right:"0"});
$('#slidetext1 #deer').animate({left: "0"}).addClass('open').removeAttr('id');
}
It's not working because it removes the id before even starting the animation, but what I want to do is to remove the id #deer from the image and add ('open') after the .animate() has been executed.
so here i made a jsfiddle: http://jsfiddle.net/67oe1jvn/45/ . pay attection to the left image as you scroll down under the HELLO h1. the thing i want to achive is: when i get to the second section, i'd like to see both of the images slide in the view with the directive "transition:all 1.2s ease-out;" AND whenever the section gets changed make them slide out of the view with a faster transiction, so it won't been noticed that much.
You need to supply the complete callback function which is fired once the animation is complete. You can achieve this by updating the code to following
if(index == 1 && direction =='down'){
$('#slidetext1 #rock').animate({right:"0"});
$('#slidetext1 #deer').animate({left: "0"}, function(){
$(this).addClass('open').removeAttr('id')
});
}
For reference - http://api.jquery.com/animate/
Please try this
$.when($('#slidetext1 #deer').animate({left: "0"})).then(function(){
$('#slidetext1 #deer').addClass('open').removeAttr('id')
});
If a single Deferred is passed to jQuery.when(), its Promise object (a subset of the Deferred methods) is returned by the method. Additional methods of the Promise object can be called to attach callbacks, such as deferred.then. When the Deferred is resolved or rejected, usually by the code that created the Deferred originally, the appropriate callbacks will be called.
OR
$('#slidetext1 #deer').animate({left: "0"}).promise().done(function(){
$('#slidetext1 #deer').addClass('open').removeAttr('id')
});
The .promise() method returns a dynamically generated Promise that is resolved once all actions of a certain type bound to the collection, queued or not, have ended.
DEMO with .promise()
DEMO with .when()

How should I make complex, sequential events in javascript

I'm working on an interactive tutorial-tool for JavaScript. The core of the tool is the script of the tutorial. The script will trigger various functions that run animations, speaker-voices load new pages etc. Three sample calls(most tutorials will have 10-100s of calls, so a neat overview of the calls is highly desired:
wrap(); //wrap the page in an iframe
playsound('media/welcome') //playing a sound (duh)
highlight('[name=firstname]'); //animation that highlights an element.
playsound('media/welcome2');
loadpage(page2); //loading a new page
All calls have something in common: they have non-normal-triggers. In this simple script for example, the second call should be triggered once the iframe in the first call is loaded. The third script is triggered once the sound is complete (ie delay). The fourth function should be triggered once the animation is complete. The fifth event should be triggered on an event (for example a click).
A technical solution to this would be to call the function in the callback of the previous function, this has the potential to get pretty messy. What I like with a solution wherer the functions are called lite this is that someone with a little bit of brains, but no coding experience could hammer up a script of their own. How would you solve this? I'm pretty new to javascript so if you could be explicit i'd appreciate it.
I'd use a per-built solution. There is bound be one that fits your needs. Something simple like jTour or if that doesn't cover it something a little more complex like Scriptio. Some of the answers to this question may also be of interest to you.
Edit
If you don't want to use a preexisting solution, I'd do something like this:
var runTutorial = (function () {
// The command object holds all the different commands that can
// be used by someone for the tutorial. Each of these commands
// will recive a callback set as their `this`. This
// callback should be called by your commands when they are done
// running. The person making the tutorial won't need to know
// about the callback, the code will handle that.
var commands = {
wrap: function () {
//wrap the page in an iframe
this();
},
playsound: function (soundPath, soundLength) {
//playing a sound (duh)
setTimeout(this, soundLength);
},
highlight: function (selector) {
//animation that highlights an element.
//I'm using jQuery UI for the animation here,
// but most animation libraries should provide
// a callback for when the animation is done similarly
$(selector).effect('highlight', 'slow', this);
},
loadpage: function (pageUrl) {
//loading a new page
setTimeout(this, 500);
},
waitForClick: function () {
// when we go into the click handler `this` will no
// longer be availble to us since we will be in a
// different context, save `this` into `that` so
// we can call it later.
var that = this;
$(document).one('click', function () {
that();
});
}
},
// This function takes an array of commands
// and runs them in sequence. Each item in the
// array should be an array with the command name
// as the first item and any arguments it should be
// called with following as the rest of the items.
runTutorial = function (commandList) {
var nextCommand = function () {
if (commandList.length > 0) {
var args = commandList.shift();
// remove the command name
// from the argument list
cmd = args.shift(1);
// call the command, setting nextCommand as `this`
commands[cmd].apply(nextCommand, args);
}
}
nextCommand();
};
return runTutorial;
}());
$('#tutorialbutton').click(function() {
runTutorial([
['playsound', 'media/welcome', 1000],
['highlight', '[name=firstname]'],
['playsound', 'media/welcome2', 1500],
['waitForClick'],
['loadpage', page2],
['playsound', 'media/page2', 100]
]);
});
The runTutorial function takes a simple array containing the commands in the order they should be run, along with their parameters. No need to bother the person writing the script with callbacks, runTutorial handles that for them. This has some big advantages over a system that requires the writer to manage callbacks. You don't need an unique name for each line in the script as you do with explicit callbacks, nor endless nesting of anonymous functions. You don't need to rewire anything to change the order that the commands are played in, you just physically rearrange them in the array.
jsfiddle you can play with
Each of your commands will need to wait for its action to be done before it calls its callback (aka this). I simulate this in the fiddle using setTimeout. For instance, if you are using jQuery's .animate for highlight, it provides a complete handler that fires when the animation is done, just stick this (with out the invocation parentheses ()) there. If you are using jQuery UI, it has a built-in 'highlight' effect, so you could implement it like this:
highlight: function (selector) {
//animation that highlights an element.
$(selector).effect('highlight', 'slow', this);
},
Most other libraries that provide animations should provide a similar callback option you can use.
Controlling the callback for the sounds may be harder depending on how you are playing them. If the method you are using doesn't provide a callback or a way of polling it to see if it is done yet you might just have to add another parameter to playsound that takes the length of the sound in ms and then waits that long before proceeding:
playsound: function (soundPath, soundLength) {
//playing a sound (duh)
setTimeout(this, soundLength);
},
Callbacks are your best bet, I think. They don't have to be messy (though it's certainly possible to make them completely incomprehensible). You could create each function to accept a callback, then use a structure like this to call them in sequence in a readable way:
var loadingSequence = {
start : function() { wrap(this.playsound); },
playsound : function() { playsound('media/welcome', this.highlight); },
highlight : function() { highlight('[name=firstname]', this.playsound2); },
playsound2 : function() { playsound('media/welcome2', this.loadpage); },
loadpage : function() { loadpage(page2); }
};
loadingSequence.start();

Chain to pre defined functions with jQuery

I have been trying to chain this last bit of code to work nicely.
It calls two functions that have been preset:
slideToImage(newIndex);
setCaption();
I want the caption to be set once the image has slid.
This has always been a confusing topic for me as I have tried different ways of calling it such as:
callbacks = $.Callbacks();
callbacks.add(slideToImage(newIndex));
callbacks.add(setCaption());
callbacks.fire();
But that does not work.
slideToImage must contain some animation logic that happens over time. So slideToImage would start the animation process and then return before the animation has completed which causes setCaption to be called to early.
You should pass setCaption into slideToImage as a callback and call it once the animation has completed.
function slideToImage(newIndex, callback) {
// I assume you're using jQuery for animation
targetElement.animate({left: 100}, 200, function() {
// once animation has finished we call the callback function
callback();
});
}
slideToImage(newIndex, function() {
setCaption();
});
For "setCaption", it's easy:
callbacks.add(setCaption);
For "slideToImage", since you need to pass a parameter, it's slightly more involved:
callbacks.add(function() { slideToImage(newIndex); });

Categories