Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running jQuery functions with effects in a specific order

I have some jQuery inside javascript functions that changes text on a page and fades it in and out at specific time intervals. I want the functions to run in order one after the other, after each function finishes doing its effects.

dialogueExchange1();
dialogueExchange2();
dialogueExchange3();

function dialogueExchange1() {
  $('.text-area1').text("hey");
  $('.text-area1').delay(1000).showDialogue(800, 3000).prepareDialogue(800, "hey, are you awake?");
}

function dialogueExchange2() {
  $('.text-area1').delay(900).showDialogue(800, 4000).prepareDialogue(800, "wake up").delay(900);

  $('.text-area2').text("...");
  $('.text-area2').delay(1200).showDialogue(800, 1500).fadeOut(800);
}

function dialogueExchange3() {
  $('.text-area1').delay(900).showDialogue(800, 4000).prepareDialogue(800, "let's go").delay(900);

  $('.text-area2').text("not yet");
  $('.text-area2').delay(1200).showDialogue(800, 1500).fadeOut(800);
}

The showDialogue and prepareDialogue are methods I created that delay and fade text in and out. That is working fine. Basically, I'm just trying to get the text to change in the text area selectors after a specific time. What is currently happening is that all the functions are being run at the same time, thus firing the text changing effects all at the same time. I want dialogueExchange1 to do its effects then when it is done, for dialogueExchange2 to do its effects and then when its done, etc etc.

I have tried messing around with queues, timeouts and callbacks via the solutions below, but I haven't gotten it to do exactly what I want:

how to avoid callback chains?

How do I chain or queue custom functions using JQuery?

I had this working in the past and doing what I wanted by just having all the text changing methods chained together in one line of code but it just looks bad. Having it broken up in functions and running them in order would make it more organized and helpful to keep track of the text changes and delay times. Thanks!

EDIT: showDialogue and prepareDialogue functions as requested

$.fn.showDialogue = function(fadeInTime, showTextTime) {
    this.fadeIn(fadeInTime).delay(showTextTime);
    return this;
};

$.fn.prepareDialogue = function(fadeOutTime, dialogue) {
    this.fadeOut(fadeOutTime, function() {
        $(this).html(dialogue);
    });
    return this;
};

SOLUTION EDIT2: Thanks for the responses everyone and to whoughton for first suggesting the use of promise(). This is my solution at the moment, but I'm sure I'm going to refactor it down the road and change it now that I have seen Shaunak's answer.

dialogueExchange1();

function dialogueExchange1() {
    $('.text-area1').text("hey");
    $('.text-area1').delay(1000).showDialogue(800, 3000).prepareDialogue(800, "hey, are you awake?");

    $('.text-area1, .text-area2, .text-area3').promise().done(function() {
        dialogueExchange2();     
    });
}

function dialogueExchange2() {
    $('.text-area1').delay(900).showDialogue(800, 4000).prepareDialogue(800, "wake up");

    $('.text-area3').text("...");
    $('.text-area3').delay(1800).showDialogue(800, 1500).fadeOut(800);

    $('.text-area1, .text-area2, .text-area3').promise().done(function() {
        dialogueExchange3();     
    });
}

function dialogueExchange3() {
    $('.text-area1').delay(900).showDialogue(800, 4000).prepareDialogue(800, "come on let's go");

    $('.text-area2').text("hold on");
    $('.text-area2').delay(1200).showDialogue(800, 1500).fadeOut(800);
}

This way it gives me a lot of flexibility in refining delay times to reflect and mimic a conversation. The next function only runs when the effects within a function are finished, as made possible by promise(). Here is a jsFiddle link if you want to see it in action.

like image 916
Alex E Avatar asked Feb 15 '23 14:02

Alex E


2 Answers

Here is a a way to do this with promises as @whoughton pointed out:

Solution using .promise() ( jsfiddle )

Html:

<div class="hasEffects effect1"> Div 1</div>
<div class="hasEffects effect2"> Div 2</div>
<div class="hasEffects effect3"> Div 3</div>

<button id="animate">animate</button>

Jquery:

effects1 = function(){
    $(".effect1").effect("shake", {}, 1000);
    return $(".effect1");
// or you can return $(".hasEffects"); if you are running effects on multiple elements
};

effects2 = function(){
    $(".effect2").effect("shake", {}, 1000);
    return $(".effect2");
};

effects3 = function(){
    $(".effect3").effect("shake", {}, 1000);
    return $(".effect3");
};

$("#animate").click(function(){
    runAnimations([effects1,effects2,effects3]);
});

 runAnimations = function(functionArray) {
    //extract the first function        
    var func = functionArray.splice(0, 1);

    //run it. and wait till its finished 
    func[0]().promise().done(function() {

        //then call run animations again on remaining array
        if (functionArray.length > 0) runAnimations(functionArray);
    });

}

Here is the jsfiddle

How this works

You pass an array of all the functions you need to chain in the runAnimation function which is run recursively until all functions are completed. They way it halts the execution of next function before animation in previous one is completed is using .promise() feature of jquery.

Everytime the runAnimation() runs, it extracts the first function in the array and runs it, and after .proimise().done() is executed for that function it call runAnimation() again with the remaining array.

The real trick is in understanding how .promise() works. It waits for all animations running on all the selectors passed to it. So in the individual effects function you can run effects on as many elements as you want. As long as you return correct selector you should be good.

For example, suppose in all thee effects you wanted to run 3 different effects on all 3 divs. Thats fine, just return $("hasEffects") from the function and it will work. :)

In your case

In your particular case here is how you can use this example. Add a grouping class on all your elements for example class="hasEffects" . And return a jquery selector for them from your animation functions. Then chain them using runAnimation. Here is how it should look:

function dialogueExchange1() {
   $('.text-area1').text("hey");
   $('.text-area1').delay(1000).showDialogue(800, 3000).prepareDialogue(800, "hey, are you awake?");
   return $(".hasEffects");
}

function dialogueExchange2() {
   $('.text-area1').delay(900).showDialogue(800, 4000).prepareDialogue(800, "wake up").delay(900);

   $('.text-area2').text("...");
   $('.text-area2').delay(1200).showDialogue(800, 1500).fadeOut(800);
   return $(".hasEffects");
}

function dialogueExchange3() {
   $('.text-area1').delay(900).showDialogue(800, 4000).prepareDialogue(800, "let's go").delay(900);

   $('.text-area2').text("not yet");
   $('.text-area2').delay(1200).showDialogue(800, 1500).fadeOut(800);
   return $(".hasEffects");
}

runAnimations([dialogueExchange1,dialogueExchange2,dialogueExchange3]);

runAnimations = function(functionArray){
        var func = functionArray.splice(0,1);
          func[0]().promise().done(function(){
            if(functionArray.length > 0 ) runAnimations(functionArray);
          });

    }
like image 171
Shaunak Avatar answered Feb 18 '23 11:02

Shaunak


I would recommend using a system of promises, jQuery has its own implementation:

http://api.jquery.com/promise/

like image 44
whoughton Avatar answered Feb 18 '23 10:02

whoughton