0

I have been having a bit of a tough time trying to sequence some animation events in JQuery with a number of other procedures that I am trying to have happen simultaneously or in a specific sequence. Here is basically what I want to accomplish:

  1. User clicks on a button, which causes a custom div popup box to come up on the screen.
  2. The popup box is animated in along with its backdrop.
  3. The popup box shows a loading message while it also sends (after a 1.5 second delay) an Ajax request to the server to pull down the specified contents that will be then loaded into the popup box.
  4. Then, the popup box loading text should be replaced by the retrieved contents via a sidedown.
  5. When the user reads the message, he/she can then click on the close icon of the popup box or hit the escape key to close the popup.
  6. The popup closes via a fadeout animation.

With my current code (link below to JSFiddle), this seems to work okay and the popup animation and sequencing will show up correctly, provided that the user closes the popup first before clicking on the button to show it again. HOWEVER (and this is my issue)... if the popup is left open and the user clicks on the button again, the sequencing of the animation gets messed up... it seems that the chain of events falls apart - the procedure that shows the popup does not first wait for the popup to close, and you get a horrible effect.

Any ideas on how this may be solved?

Ideally, as the logic in the code suggests, we'd want to wait for the popup's close animation to be done completely before starting the popup's show animation again. I am still a bit of a rookie when it comes to JQuery, so forgive me. I tried to do some research and believe that the solution may lie in using Deferreds in some clever way. However, I thought you guys may provide a great, effective, and up to date solution and prevent me from pulling all my hair out. =)

Here is my code (somewhat simplified in places, but still mimics the problem well):

http://jsfiddle.net/Nickel3ack/kdcwT/21/

Nickel3ack
  • 11
  • 2
  • _"the popup is left open and the user clicks on the button again"_ - Why don't you make a _modal_ popup, i.e., don't allow interaction with the rest of the page until the popup closes. – nnnnnn Mar 05 '12 at 03:16
  • Or, when you go to show the popup again, check to see if it's already showing and follow a different course of action (bring it to the front, don't animate, etc...). – jfriend00 Mar 05 '12 at 03:25
  • Thanks for the suggestions, and I appreciate them. I am more curious however about how to make it work, since, conceptually it is quite doable. – Nickel3ack Mar 05 '12 at 04:34

1 Answers1

0

I managed to solve my problem and get all my events to sequence properly. It took some clever use of Deferreds and lots of trial and error before I got the hand of what I was actually doing. Anyway, in case anyone needs this solution for anything they are working on...

The Problem:
I wanted a message box pop-up on my page to essentially replace the browser's alert window for my purposes. I wanted to chain some fade and slidedown animations to show the message box and then its contents. I also wanted a loading image to appear in the message box when it first appeared, while an asynchronous request was sent to the server. The the message box would show the response. Anyway, this is how I set it up...

In the body of my html, I included the message box container:

<div id="msg-box">
<div id="msg-box-container">
    <div id="msg-box-headbar">
        <div id="msg-box-title"></div>
        <div id="msg-box-clButton"></div>
    </div>
    <div id="msg-box-content"></div>
    <div id="msg-box-footbar"></div>
</div>
</div>
<div id="msg-box-backdrop"></div>

As you see, my message box also has a backdrop (which I set up as a semi-transparent div that resides outside of the message box's container). The backdrop will be shown and animated simultaneously but independently of the message box itself. This is another complication, since the sequence and timing of events has to be executed correctly, otherwise we'd get strange effects... like the backdrop doing one thing while the message box is doing another.

The Script:
To make my code a bit cleaner that in my first attempt, I created an object literal for the message box with a set of functions to manipulate the message box. It took a lot of research, but the solution was ultimately to use a combination of chaining and Deferreds. Utimately, my code looks like this:

    var msgBox = {
    status: 0,
    self: $('#msg-box'),
    title: $('#msg-box-title'),
    cl_button: $('#msg-box-clButton'),
    content: $('#msg-box-content'),
    backdrop: $('#msg-box-backdrop'),

    hideBox: function() {
        var b = this;
        if (b.status == 1) {
            var dfd = $.Deferred();
            b.self.fadeOut(500);
            b.backdrop.fadeOut(500, dfd.resolve);
            b.status = 0;
            return dfd.promise();
        }
    },

    showBox: function(ttl, msg) {
        var b = this;
        if (b.status == 1) {
            $.when(b.hideBox()).then(function() {
                b.doShow();
                b.loadCont(ttl, msg);
            });
        }
        else {
            if(b.backdrop.queue().length > 0) {
                $.when(b.backdrop.queue()).done( function() {
                    b.doShow();
                    b.loadCont(ttl, msg);
                });
            }
            else {
                b.doShow();
                b.loadCont(ttl, msg);
            }
        }
    },

    doShow: function() {
        var b = this;
        b.title.text('Loading...');
        b.content.html('<div id="msg-box-loader"><img id="loading-img" src="/graphics/loader-16.gif" /></div>');
        b.backdrop.height(b.self.height());
        b.self.centerBox($(window).width());
        b.backdrop.centerBox($(window).width());
        b.self.fadeIn('fast');
        b.backdrop.corner('round 6px').fadeTo('fast', 0.6);
    },

    loadCont: function(ttl, msg) {
        var b = this;
        b.content.delay(1500).queue(function(next) {
            b.title.text(ttl);
            b.content.text(msg).slideDown('fast');
            b.backdrop.height(b.self.height()).slideDown('fast');
            b.status = 1;
            next();
        });
    }
};

As you can see, at several points in the hide / show sequence, I use Deferreds to allow certain events to complete before launching new ones. For reference, these are my bindings:

    // events related to message box
$(document).keydown( function(e) {
    if (e.keyCode == 27) {
        e.preventDefault();
        if(msgBox.status == 1)
            msgBox.hideBox();
    }
});
msgBox.cl_button.hover( function() { $(this).toggleClass('no-hover').toggleClass('hover'); });
msgBox.self.on('click', msgBox.cl_button.selector, function() { msgBox.hideBox();  });
$(window).resize(function() {
    if (msgBox.status == 1) {
        msgBox.self.centerBox($(window).width());
        msgBox.backdrop.centerBox($(window).width());
    }
});

// login form submitted
$('#login-form').submit( function(e) { 
    e.preventDefault();
    var postData = 'async=login&user='+$('#login-user').val()+'&pass='+hex_md5($('#login-pass').val());
    $.ajax({ type: 'POST', url: "index.php", data: postData, 
        success: function(response) { 
            if(response==0) msgBox.showBox('Login Failed', 'Invalid Credentials'); 
            else if(response==1) msgBox.showBox('Success', response); 
            else msgBox.showBox('Internal Error', 'There was an internal error. Please contact the administrator.<br><br>'+response); 
        },
        error: function() { 
            msgBox.showBox('Internal Error', 'There was an internal error. Please contact the administrator.'); 
        } 
    });
});
Nickel3ack
  • 11
  • 2