How to stop a form from submitting until an animation is complete

1.7k views Asked by At

I am trying to run an animation on a form after the user has clicked the submit button and before it submits. I did find solutions here on stackoverflow and on other sites but they either resulted in the form submitting before the animation could run, or the animation would run but the form would not submit. It's just for my sister's wedding website so my job isn't in jeopardy but it would be nice to make her site special.

note that the developer console returns no errors.

This is the code we started with: Executing a form after animation is done JQuery. It is an accepted answer so i'm assuming it worked for somebody, but I don't understand how the preventDefault function is supposed to work when nothing is passing the event to it. I also tried this one: Run an animation before I submit a form (and make sure it completes)

Basically the animation is a letter that slides into an envelope which is folded then slides off screen.

Here's the form.

<form method="post" action="index.php">
    <label>names</label>
    <input type="text" name="names" id="nameInput" placeholder="type here" />
    <label>food allergies</label>
    <input type="text" name="allergies" placeholder="type here" />
    <input type="submit" name="send" value="Send Email">
</form>

and here's the jQuery. updated with @Roamer-1888's code

var windowWidth = $(window).width();

var imgPosition = ($('.mobile_background').width() - windowWidth) / 2;
var centreTriangle = (windowWidth / 2) - 35;

function sendMail(e) {
    e.preventDefault();
    var form = e.target;

    var setTop = function () {
        return $('.letter').animate({top: 2}, 1000).promise();
    }
    var openEnvelope = function () {
        return $('.envelope_fold.top_up').slideUp().promise();
    }
    var insertContents = function () {
        return $('input').css('display', 'none');
        // you probably need also to do something here to prevent the inputs from showing again when the envelope is animated off screen. eg append the inputs to the envelope element. 
    }
    var closeEnvelope = function () {
        return        $('.envelope_fold.shaddow').add('.envelope_fold.top_down').slideDown(300).promise();
    }
    var sealEnvelope = function () {
        return $('#seal').css('display', 'block');
    }
    var exitStageRight = function () {
        return $('#envelope').animate({left: 3000}).promise();
    }
    var send = function () {
        form.submit();
    }

    if ($('#nameInput').val() == "") {
        alert('Please tell us your name');
    } else {
        $.when() //resolved promise to get the chain started
            .then(setTop)
            .then(openEnvelope)
            .then(insertContents)
            .then(closeEnvelope)
            .then(sealEnvelope)
            .then(exitStageRight)
            .then(send);
    }
}


function showMenu() {
    $('.navButton').css({
        right: '111px'
    });
    $('#menu').css({
        left: 0
    }, 300);
}

function hideMenu() {
    $('.navButton').css({
        right: '10px'
    }); 
    $('#menu').css({
        left: '100%'
    }, 300);
}

$(document).ready(function(){
    // styles
    $('#intro').css({
        'height': $(window).height()
    });

    $('.mobile_background').css({
        'right': -imgPosition
    });

    $('.triangle').css({
        'margin-left': centreTriangle
    });

    // countdown clock
    $('#count').countdown('2015/01/17 13:30:00', function(event) {
       $(this).html(event.strftime('<h3>%D<span>d</span> %H<span>h</span> %M<span>m</span</h3>'));
    });

    // form submit verification removal
    $('.success a').click(function(){
        $(this).parent().remove();
    });

    // Navigation
    $('.navButton').on('click', function(){
        showMenu();
    });

    $('body > div, #menu a').on('click', hideMenu);


    if (windowWidth > 1025) {
        $('nav').mouseenter(function(){
            showMenu();
        });
        $('nav').mouseleave(function(){
            hideMenu();
        });     
    }

    if (windowWidth <= 400) {
        $('.navButton').on('click', function(){
            showMenu();
            $('body > div').css({
                right: '101px'
            });
        });

        $('body > div + div, #menu a').on('click', function(){
            hideMenu();
            $('body > div').css({
                right: 0
            });
        });
    }

    // fire form animation
    $("form:eq(0)").on('submit', sendMail);
});

Also I want the first step of the animation to make the inputs go behind the front of the envelope but it prevents all of the functions running after that from working. I have to have them in front of the envelope at first otherwise users wouldn't be able to enter text. So if anyone can figure that out that would also be great.

scroll to the bottom of this site to see the form: http://celinaanddavid.ca

Thanks for the help.

1

There are 1 answers

14
Roamer-1888 On BEST ANSWER

For serial animations like this, you can avoid that horrible "pyramid of doom" (deep nested callbacks) by exploiting jQuery's promises and writing a nice friendly .then() chain.

And to make the code even more readable, it is also a good idea to write each animation step as a named function.

You will end up with something like this :

function sendMail(e) {
    e.preventDefault();
    var form = e.target;

    var setTop = function () {
        return $('.letter').animate({top: 2}, 1000).promise();
    }
    var openEnvelope = function () {
        return $('.envelope_fold.top_up').slideUp().promise();
    }
    var insertContents = function () {
        return $(form).find('input').css('z-index', 0);
        // you probably need also to do something here to prevent the inputs from showing again when the envelope is animated off screen. eg append the inputs to the envelope element. 
    }
    var closeEnvelope = function () {
        return $('.envelope_fold.shaddow').add('.envelope_fold.top_down').slideDown(300).promise();
    }
    var sealEnvelope = function () {
        return $('#seal').css('display', 'block');
    }
    var exitStageRight = function () {
        return $('#envelope').animate({left: 3000}).promise();
    }
    var send = function () {
        form.submit();
    }

    if ($('#nameInput').val() == "") {
        alert('Please tell us your name');
    } else {
        $.when() //resolved promise to get the chain started
            .then(setTop)
            .then(openEnvelope)
            .then(insertContents)
            .then(closeEnvelope)
            .then(sealEnvelope)
            .then(exitStageRight)
            .then(send);
    }
}

Now all you need to do is attach sendMail as your form's sumbit handler, eg :

$("form:eq(0)").on('submit', sendMail);