Comment déclencher un événement après avoir utilisé event.preventDefault ()

156

Je souhaite organiser un événement jusqu'à ce que je sois prêt à le déclencher, par exemple

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

   e.preventDefault(); 

   // do lots of stuff

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

}

Existe-t-il un équivalent à la run()fonction décrite ci-dessus?

Mazatec
la source
Le comportement par défaut se produit uniquement après le retour de votre gestionnaire. Cela n'a pas de sens d'empêcher ce comportement uniquement pour l'autoriser plus tard dans votre gestionnaire.
Frédéric Hamidi
7
@ FrédéricHamidi Malheureusement, les trucs asynchrones ($ .ajax, callbacks, etc.) permettront le comportement par défaut.
vzwick

Réponses:

165

Nan. Une fois l'événement annulé, il est annulé.

Vous pouvez toutefois relancer l'événement plus tard, en utilisant un indicateur pour déterminer si votre code personnalisé a déjà été exécuté ou non - comme celui-ci (veuillez ignorer la pollution flagrante de l'espace de noms):

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');
});

Une variante plus généralisée (avec l'avantage supplémentaire d'éviter la pollution globale de l'espace de noms) pourrait être:

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));

Plugin jQuery super-minimaliste bonus avec Promisesupport:

(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
la source
4
.live est obsolète. Utilisez .on utilisé dans l'exemple @Cory Danielson ci-dessous.
nwolybug
Cela rentre à nouveau dans .click et enfin je vois "trop ​​de récursion"
Himanshu Pathak
5
@HimanshuPathak - vous avez probablement oublié de définir le lots_of_stuff_already_done = true;drapeau - sinon, il n'y a aucun moyen pour la fonction de continuer à se répéter.
vzwick
73

Une version plus récente de la réponse acceptée.

Version abrégée:

$('#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 */
    }

});



Un bon cas d'utilisation pour quelque chose comme celui-ci est où vous pouvez avoir un code de formulaire hérité qui fonctionne, mais il vous a été demandé d'améliorer le formulaire en ajoutant quelque chose comme la validation de l'adresse e-mail avant de soumettre le formulaire. Au lieu de fouiller dans le code postal du formulaire principal, vous pouvez écrire une API, puis mettre à jour votre code frontal pour accéder à cette API avant d'autoriser le formulaire à faire son POST traditionnel.

Pour ce faire, vous pouvez implémenter un code similaire à ce que j'ai écrit ici:

$('#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
la source
1
"Au lieu de fouiller dans le code postal du formulaire back-end" ... En fait, vous devez le faire de toute façon, vous ne pouvez pas compter uniquement sur la validation côté client.
Diego V
18

Vous pouvez faire quelque chose comme

$(this).unbind('click').click();
Rafael Oliveira
la source
C'est une très bonne solution - mais ne semble pas fonctionner sur IE10 / 11; (
JonB
47
Pourquoi avez-vous censuré le mot «douleur»?
Sean Kendle
Vous avez déclenché le clic mais pouvez-vous cliquer à nouveau?
Tom Anderson
16

Remplacez la propriété isDefaultPreventedcomme ceci:

$('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, c'est le moyen le plus complet de redéclencher l'événement avec exactement les mêmes données.

Tomislav Simić
la source
en'est pas défini. devrait être evt.preventDefault(). J'ai essayé de modifier, mais mes modifications doivent être> 6 caractères et je viens d'ajouter 2 :(
kevnk
3
@kevnk, j'inclus généralement une brève description de l'édition sous la forme d'un commentaire de ligne. Cela devrait augmenter le nombre de caractères soumis.
recurse
Je ne sais pas pourquoi cette réponse n'a pas été plus votée, c'est vraiment utile. Fonctionne également avec les arrêts de propagation event.isPropagationStopped = function(){ return false; };. J'ai également ajouté une propriété personnalisée à l'événement afin que je puisse détecter dans le gestionnaire si la vérification qui a empêché l'action a été effectuée afin qu'elle ne soit plus effectuée. Génial!
Kaddath
J'ai utilisé pour Bootstrap 4 Tabs, cela a parfaitement fonctionné. Merci beaucoup. $ ('# 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 le
8

Il est possible d'utiliser currentTargetle event. L'exemple montre comment procéder avec l'envoi du formulaire. De même, vous pouvez obtenir la fonction de l' onclickattribut, etc.

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

  // code

  event.currentTarget.submit();
});
Salvis Blūzma
la source
soumettre n'est pas une fonction valide
Devaffair
si vous appelez submit()le même élément, ne reviendrez-vous pas sur votre code `` $ ('form'). on ('submit') et ne le referez-vous pas encore et encore?
Fanie Void
7

Ne l'exécutez pas e.preventDefault();ou ne l'exécutez pas sous condition.

Vous ne pouvez certainement pas modifier le moment où l'action d'événement d'origine se produit.

Si vous souhaitez "recréer" l'événement d'interface utilisateur d'origine quelque temps plus tard (par exemple, dans le rappel d'une requête AJAX), vous devrez simplement le simuler d'une autre manière (comme dans la réponse de vzwick) ... même si je remettre en question l'opportunité d'une telle approche.

Courses de légèreté en orbite
la source
3

tant que "beaucoup de choses" ne font pas quelque chose d'asynchrone, cela est absolument inutile - l'événement appellera chaque gestionnaire sur son chemin dans l'ordre, donc s'il y a un événement onklick sur un élément parent, cela se déclenchera après l'onclik- l'événement de l'enfant a été traité complètement. javascript ne fait pas ici une sorte de "multithreading" qui rend "l'arrêt" du traitement des événements nécessaire. conclusion: «mettre en pause» un événement juste pour le reprendre dans le même gestionnaire n'a aucun sens.

si "beaucoup de trucs" est quelque chose d'asynchrone, cela n'a pas non plus de sens car cela empêche les choses asynchrones de faire ce qu'elles devraient faire (trucs asynchrones) et les fait baisser comme si tout était en séquence (où nous revenons à mon premier paragraphe )

oezi
la source
Le processus au milieu est asynchrone, je veux déclencher le résultat dans le rappel ajax ...
Mazatec
1
si vous devez attendre une requête ajax, rendez-la synchrone (pour jquery, il y a le async-fag: api.jquery.com/jQuery.ajax ) ... mais faire une requête ajax synchrone est une mauvaise idée dans presque tous les cas, il vaudrait donc mieux trouver une solution différente.
oezi
3

L'approche que j'utilise est la suivante:

$('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
la source
2

La solution acceptée ne fonctionnera pas si vous travaillez avec une balise d'ancrage. Dans ce cas, vous ne pourrez plus cliquer sur le lien après avoir appelé e.preventDefault(). C'est parce que l'événement de clic généré par jQuery est juste une couche au-dessus des événements de navigateur natifs. Ainsi, le déclenchement d'un événement «clic» sur une balise d'ancrage ne suivra pas le lien. Au lieu de cela, vous pouvez utiliser une bibliothèque comme jquery-simulate qui vous permettra de lancer des événements de navigateur natifs.

Plus de détails à ce sujet peuvent être trouvés dans ce lien

Miguel Carvajal
la source
1

Une autre solution consiste à utiliser window.setTimeout dans l'écouteur d'événements et à exécuter le code une fois le processus de l'événement terminé. Quelque chose comme...

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

J'utilise 0 pour la période car je m'en fous d'attendre.

Αλέκος
la source
1

Je sais que ce sujet est ancien mais je pense que je peux y contribuer. Vous pouvez déclencher le comportement par défaut d'un événement sur un élément spécifique à tout moment dans votre fonction de gestionnaire si vous connaissez déjà ce comportement. Par exemple, lorsque vous déclenchez l'événement de clic sur le bouton de réinitialisation, vous appelez en fait la fonction de réinitialisation sur le formulaire le plus proche comme comportement par défaut. Dans votre fonction de gestionnaire, après avoir utilisé la fonction preventDefault, vous pouvez rappeler le comportement par défaut en appelant la fonction de réinitialisation sur le formulaire le plus proche n'importe où dans votre code de gestionnaire.

Patrik
la source
0

Si cet exemple peut aider, ajoute un "popin de confirmation personnalisé" sur certains liens (je garde le code de "$ .ui.Modal.confirm", c'est juste un exemple pour le callback qui exécute l'action d'origine):

//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
la source