198

I want to hold an event until I am ready to fire it e.g

$('.button').live('click', function(e){

   e.preventDefault(); 

   // do lots of stuff

   e.run() //this proceeds with the normal event    

}

Is there an equivalent to the run() function described above?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Mazatec
  • 11,481
  • 23
  • 72
  • 108
  • The default behavior only occurs after your handler returns. It makes little sense to prevent that behavior only to allow it later in your handler. – Frédéric Hamidi Sep 30 '11 at 13:16
  • 9
    @FrédéricHamidi Unfortunately, async stuff ($.ajax, callbacks, etc.) will allow default behavior to occur. – vzwick Sep 30 '11 at 13:21

17 Answers17

201

Nope. Once the event has been canceled, it is canceled.

You can re-fire the event later on though, using a flag to determine whether your custom code has already run or not - such as this (please ignore the blatant namespace pollution):

var lots_of_stuff_already_done = false;

$('.button').on('click', function(e) {
    if (lots_of_stuff_already_done) {
        lots_of_stuff_already_done = false; // reset flag
        return; // let the event bubble away
    }

    e.preventDefault();

    // do lots of stuff

    lots_of_stuff_already_done = true; // set flag
    $(this).trigger('click');
});

A more generalized variant (with the added benefit of avoiding the global namespace pollution) could be:

function onWithPrecondition(callback) {
    var isDone = false;

    return function(e) {
        if (isDone === true)
        {
            isDone = false;
            return;
        }

        e.preventDefault();

        callback.apply(this, arguments);

        isDone = true;
        $(this).trigger(e.type);
    }
}

Usage:

var someThingsThatNeedToBeDoneFirst = function() { /* ... */ } // do whatever you need
$('.button').on('click', onWithPrecondition(someThingsThatNeedToBeDoneFirst));

Bonus super-minimalistic jQuery plugin with Promise support:

(function( $ ) {
    $.fn.onButFirst = function(eventName,         /* the name of the event to bind to, e.g. 'click' */
                               workToBeDoneFirst, /* callback that must complete before the event is re-fired */
                               workDoneCallback   /* optional callback to execute before the event is left to bubble away */) {
        var isDone = false;

        this.on(eventName, function(e) {
            if (isDone === true) {
                isDone = false;
                workDoneCallback && workDoneCallback.apply(this, arguments);
                return;
            }

            e.preventDefault();

            // capture target to re-fire event at
            var $target = $(this);

            // set up callback for when workToBeDoneFirst has completed
            var successfullyCompleted = function() {
                isDone = true;
                $target.trigger(e.type);
            };

            // execute workToBeDoneFirst callback
            var workResult = workToBeDoneFirst.apply(this, arguments);

            // check if workToBeDoneFirst returned a promise
            if (workResult && $.isFunction(workResult.then))
            {
                workResult.then(successfullyCompleted);
            }
            else
            {
                successfullyCompleted();
            }
        });

        return this;
    };
}(jQuery));

Usage:

$('.button').onButFirst('click',
    function(){
        console.log('doing lots of work!');
    },
    function(){
        console.log('done lots of work!');
    });
vzwick
  • 11,008
  • 5
  • 43
  • 63
77

A more recent version of the accepted answer.

Brief version:

$('#form').on('submit', function(e, options) {
    options = options || {};

    if ( !options.lots_of_stuff_done ) {
        e.preventDefault();
        $.ajax({
            /* do lots of stuff */
        }).then(function() {
            // retrigger the submit event with lots_of_stuff_done set to true
            $(e.currentTarget).trigger('submit', { 'lots_of_stuff_done': true });
        });
    } else {
        /* allow default behavior to happen */
    }

});



A good use case for something like this is where you may have some legacy form code that works, but you've been asked to enhance the form by adding something like email address validation before submitting the form. Instead of digging through the back-end form post code, you could write an API and then update your front-end code to hit that API first before allowing the form to do it's traditional POST.

To do that, you can implement code similar to what I've written here:

$('#signup_form').on('submit', function(e, options) {
    options = options || {};

    if ( !options.email_check_complete ) {

        e.preventDefault(); // Prevent form from submitting.
        $.ajax({
            url: '/api/check_email'
            type: 'get',
            contentType: 'application/json',
            data: { 
                'email_address': $('email').val() 
            }
        })
        .then(function() {
            // e.type === 'submit', if you want this to be more dynamic
            $(e.currentTarget).trigger(e.type, { 'email_check_complete': true });
        })
        .fail(function() {
            alert('Email address is not valid. Please fix and try again.');
        })

    } else {

        /**
             Do traditional <form> post.
             This code will be hit on the second pass through this handler because
             the 'email_check_complete' option was passed in with the event.
         */

        $('#notifications').html('Saving your personal settings...').fadeIn();

    }

});
Cory Danielson
  • 14,314
  • 3
  • 44
  • 51
  • 2
    "Instead of digging through the back-end form post code"... In fact you have to do it anyway, you can't rely on client-side validation alone. – Diego V Sep 05 '17 at 11:26
21

You can do something like

$(this).unbind('click').click();
Rafael Oliveira
  • 2,823
  • 4
  • 33
  • 50
19

Override the property isDefaultPrevented like this:

$('a').click(function(evt){
  evt.preventDefault();

  // in async handler (ajax/timer) do these actions:
  setTimeout(function(){
    // override prevented flag to prevent jquery from discarding event
    evt.isDefaultPrevented = function(){ return false; }
    // retrigger with the exactly same event data
    $(this).trigger(evt);
  }, 1000);
}

IMHO, this is most complete way of retriggering the event with the exactly same data.

TheLethalCoder
  • 6,668
  • 6
  • 34
  • 69
Tomislav Simić
  • 207
  • 2
  • 8
  • `e` is undefined. should be `evt.preventDefault()`. I tried to edit, but my edits have to be > 6 characters and I just added 2 :( – kevnk Sep 23 '16 at 14:22
  • 3
    @kevnk, I typically include a brief description of the edit in the form of a line comment. This should increase the submitted character count. – recurse Aug 22 '17 at 15:52
  • 1
    don't know why this answer was not upvoted more, this is really useful. Works with propagation stops too with `event.isPropagationStopped = function(){ return false; };`. I also added a custom property to the event so that I can detect in the handler if the check that prevented the action was done so it's not made again. Great! – Kaddath Apr 29 '19 at 10:45
  • I used for Bootstrap 4 Tabs, It worked perfectly fine. Many Thanks. $('#v-pills-tab a').on('click', function (e) { e.preventDefault(); setTimeout(function(){ e.isDefaultPrevented = function(){return false;} $('#v-pills-home-tab').on('shown.bs.tab', function(){ $('.mainDashboard').show(); $('#changePlans').hide();}); }, 1000); $(this).tab('show'); }); – Surya R Praveen Apr 02 '20 at 08:37
  • 1
    wouldn't this go in loops , the click evt is basically triggered again. – sajanyamaha Sep 07 '20 at 13:26
  • the original javascript event will still be prevented – Josué Zatarain Oct 07 '21 at 17:33
12

A more recent answer skillfully uses jQuery.one()

$('form').one('submit', function(e) {
    e.preventDefault();
    // do your things ...

    // and when you done:
    $(this).submit();
});

https://stackoverflow.com/a/41440902/510905

Oranges13
  • 1,084
  • 1
  • 10
  • 33
10

It is possible to use currentTarget of the event. Example shows how to proceed with form submit. Likewise you could get function from onclick attribute etc.

$('form').on('submit', function(event) {
  event.preventDefault();

  // code

  event.currentTarget.submit();
});
Salvis Blūzma
  • 354
  • 4
  • 7
8

Just don't perform e.preventDefault();, or perform it conditionally.

You certainly can't alter when the original event action occurs.

If you want to "recreate" the original UI event some time later (say, in the callback for an AJAX request) then you'll just have to fake it some other way (like in vzwick's answer)... though I'd question the usability of such an approach.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
4

The approach I use is this:

$('a').on('click', function(event){
    if (yourCondition === true) { //Put here the condition you want
        event.preventDefault(); // Here triggering stops
        // Here you can put code relevant when event stops;
        return;
    }
    // Here your event works as expected and continue triggering
    // Here you can put code you want before triggering
});
Hokusai
  • 2,219
  • 1
  • 21
  • 22
3

Another solution is to use window.setTimeout in the event listener and execute the code after the event's process has finished. Something like...

window.setTimeout(function() {
  // do your thing
}, 0);

I use 0 for the period since I do not care about waiting.

agelbess
  • 4,249
  • 3
  • 20
  • 21
3

The accepted solution wont work in case you are working with an anchor tag. In this case you wont be able to click the link again after calling e.preventDefault(). Thats because the click event generated by jQuery is just layer on top of native browser events. So triggering a 'click' event on an anchor tag wont follow the link. Instead you could use a library like jquery-simulate that will allow you to launch native browser events.

More details about this can be found in this link

Miguel Carvajal
  • 1,785
  • 19
  • 23
3

you can use it with Timer or without Timer.

const form = document.querySelector('#form');

form.addEventListener('submit', (x) => {

    x.preventDefault()

    // Ajax or nay Code

    setTimeout(() => {
        x.target.submit();
    }, 1000)

})
cakan
  • 2,099
  • 5
  • 32
  • 42
3

as long as "lots of stuff" isn't doing something asynchronous this is absolutely unneccessary - the event will call every handler on his way in sequence, so if theres a onklick-event on a parent-element this will fire after the onclik-event of the child has processed completely. javascript doesn't do some kind of "multithreading" here that makes "stopping" the event processing neccessary. conclusion: "pausing" an event just to resume it in the same handler doesn't make any sense.

if "lots of stuff" is something asynchronous this also doesn't make sense as it prevents the asynchonous things to do what they should do (asynchonous stuff) and make them bahave like everything is in sequence (where we come back to my first paragraph)

oezi
  • 51,017
  • 10
  • 98
  • 115
  • The process in the middle is asynchronous, I want to fire the result in the ajax callback... – Mazatec Sep 30 '11 at 13:21
  • 1
    if you have to wait for an ajax-request make it synchonous (for jquery, theres the `async`-fag: http://api.jquery.com/jQuery.ajax/)... but making synchonous ajax-request is a bad idea in almost every case, so it would be better to find a different solution. – oezi Sep 30 '11 at 13:29
1

I know this topic is old but I think I can contribute. You can trigger the default behavior of an event on a specific element any time in your handler function if you already know that behavior. For example, when you trigger the click event on the reset button, you actually call the reset function on the closest form as the default behavior. In your handler function, after using the preventDefault function, you can recall the default behavior by calling the reset function on the closest form anywhere in your handler code.

Patrik
  • 401
  • 1
  • 6
  • 23
0

If this example can help, adds a "custom confirm popin" on some links (I keep the code of "$.ui.Modal.confirm", it's just an exemple for the callback that executes the original action) :

//Register "custom confirm popin" on click on specific links
$(document).on(
    "click", 
    "A.confirm", 
    function(event){
        //prevent default click action
        event.preventDefault();
        //show "custom confirm popin"
        $.ui.Modal.confirm(
            //popin text
            "Do you confirm ?", 
            //action on click 'ok'
            function() {
                //Unregister handler (prevent loop)
                $(document).off("click", "A.confirm");
                //Do default click action
                $(event.target)[0].click();
            }
        );
    }
);
Ronan Kerdudou
  • 308
  • 2
  • 6
0

If you add an event listener to a form and await its submission then after checking what you need to check you can call the submission of the form with .submit ie

const checkoutForm = document.getElementById('checkout-form');
const cart = {};
if (checkoutForm) {
    checkoutForm.addEventListener('submit', e => {
        e.preventDefault();
        if(JSON.stringify(cart) === '{}'){
            console.log('YOUR CART IS EMPTY')
            alert('YOUR CART IS EMPTY');
            return;
        }
        else{
            checkoutForm.submit();
        }
    })
}
<form id="checkout-form" action="action-page" method="post">
  <input type="text" name="name" />
  <button>Submit</button>
</form>

With this you can solve the form submission issues like checking for the strength of the password and checking whether all fields needed have the correct data

0

Here is my old idea of using preventDefault and trigger "click" inside. I just pass argument "prevent" to the function:

$(document).on('click', '.attachments_all', function(e, prevent = true){

    if(prevent){

        e.preventDefault();

        var button = $(this);
        var id = button.data('id');
    
        $.ajax({
            type: 'POST',
            url: window.location.protocol + '//' + window.location.host + path + '/attachments/attachments-in-order/' + id, 
            dataType: 'json',
            success: function(data){
                if(data.success && data.attachments){ 
                    button.trigger('click', false);
                } else {
                    swal({
                        title: "Brak załączników!",
                        text: "To zamówienie nie posiada żadnych załączników!",
                        type: "error"
                    });
                    return;
                }
            }
        });
    }

});

I hope someone will find it useful

0

if it's simple stuff you can play with mouseenter

$(document).on('mouseenter','.button',function(e) {
    //if class not exist yet -> do ajax
    
    //after complete add class
    $(this).addClass('ajx-event-completed');
});

$(document).on('click','.button',function(e) {
    if($(this).hasClass('ajx-event-completed')){
        //do something else
    }
    else{
        e.preventDefault();
        //we are CLOSED today! See you next week
    }
    //do more
});
Arthur Veselov
  • 1,167
  • 10
  • 9