Cómo activar un evento después de usar event.preventDefault ()

156

Quiero celebrar un evento hasta que esté listo para dispararlo, por ejemplo

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

   e.preventDefault(); 

   // do lots of stuff

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

}

¿Hay un equivalente a la run()función descrita anteriormente?

Mazateca
fuente
El comportamiento predeterminado solo ocurre después de que regresa su controlador. Tiene poco sentido prevenir ese comportamiento solo para permitirlo más adelante en su controlador.
Frédéric Hamidi
77
@ FrédéricHamidi Desafortunadamente, las cosas asíncronas ($ .ajax, devoluciones de llamada, etc.) permitirán que ocurra un comportamiento predeterminado.
vzwick

Respuestas:

165

No Una vez que el evento ha sido cancelado, se cancela.

Sin embargo, puede volver a disparar el evento más adelante, utilizando una bandera para determinar si su código personalizado ya se ha ejecutado o no, como este (ignore la flagrante contaminación del espacio de nombres):

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

Una variante más generalizada (con el beneficio adicional de evitar la contaminación global del espacio de nombres) podría ser:

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

Uso:

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

Bonus jQuery súper minimalista con Promisesoporte:

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

Uso:

$('.button').onButFirst('click',
    function(){
        console.log('doing lots of work!');
    },
    function(){
        console.log('done lots of work!');
    });
vzwick
fuente
44
.live es depricated. Use .on usado en el ejemplo de @Cory Danielson a continuación.
nwolybug
Esto nuevamente está entrando en .click y finalmente veo "demasiada recursión"
Himanshu Pathak
55
@HimanshuPathak: probablemente olvidó configurar la lots_of_stuff_already_done = true;bandera; de lo contrario, no hay forma de que la función se repita.
vzwick
73

Una versión más reciente de la respuesta aceptada.

Versión breve:

$('#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 buen caso de uso para algo como esto es cuando puede tener algún código de formulario heredado que funcione, pero se le ha pedido que mejore el formulario agregando algo como validación de dirección de correo electrónico antes de enviar el formulario. En lugar de buscar en el código postal del formulario back-end, puede escribir una API y luego actualizar su código front-end para acceder a esa API primero antes de permitir que el formulario haga su POST tradicional.

Para hacer eso, puede implementar un código similar al que he escrito aquí:

$('#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
fuente
1
"En lugar de buscar en el código postal del formulario de fondo" ... De hecho, debe hacerlo de todos modos, no puede confiar solo en la validación del lado del cliente.
Diego V
18

Puedes hacer algo como

$(this).unbind('click').click();
Rafael Oliveira
fuente
Esta es una solución realmente agradable, pero no parece funcionar en IE10 / 11; (
JonB
47
¿Por qué censuraste la palabra "dolor"?
Sean Kendle
Has activado el clic, pero ¿puedes volver a hacer clic?
Tom Anderson el
16

Anule la propiedad de isDefaultPreventedesta manera:

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

En mi humilde opinión, esta es la forma más completa de volver a activar el evento con exactamente los mismos datos.

Tomislav Simić
fuente
ees indefinido. debería ser evt.preventDefault(). Traté de editar, pero mis ediciones tienen que ser> 6 caracteres y acabo de agregar 2 :(
kevnk
3
@kevnk, normalmente incluyo una breve descripción de la edición en forma de comentario de línea. Esto debería aumentar el recuento de caracteres enviado.
recurse
No sé por qué esta respuesta no se votó más, esto es realmente útil. Funciona con la propagación se detiene también con event.isPropagationStopped = function(){ return false; };. También agregué una propiedad personalizada al evento para que pueda detectar en el controlador si la verificación que impidió la acción se realizó para que no se vuelva a realizar. ¡Excelente!
Kaddath
Utilicé para Bootstrap 4 Tabs, funcionó perfectamente bien. Muchas gracias. $ ('# 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
8

Es posible utilizar currentTargetel event. El ejemplo muestra cómo proceder con el envío del formulario. Del mismo modo, podría obtener la función del onclickatributo, etc.

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

  // code

  event.currentTarget.submit();
});
Salvis Blūzma
fuente
enviar no es una función válida
Devaffair
si llama submit()al mismo elemento, ¿no devolverá su código `` $ ('form'). on ('submit') y lo rehacerá una y otra vez?
Fanie Void el
7

Simplemente no realice e.preventDefault();, o realice condicionalmente.

Ciertamente no puede modificar cuando ocurre la acción del evento original.

Si desea "recrear" el evento de IU original algún tiempo después (por ejemplo, en la devolución de llamada para una solicitud de AJAX), entonces tendrá que fingirlo de otra manera (como en la respuesta de vzwick) ... aunque yo cuestionar la usabilidad de tal enfoque.

Carreras de ligereza en órbita
fuente
6

Una respuesta más reciente utiliza hábilmente jQuery.one()

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

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

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

Naranjas13
fuente
3

mientras "un montón de cosas" no esté haciendo algo asíncrono, esto es absolutamente innecesario: el evento llamará a cada controlador en su camino en secuencia, por lo que si hay un evento onklick en un elemento padre, esto se disparará después del onclik- evento del niño ha procesado por completo. javascript no hace algún tipo de "subprocesamiento múltiple" aquí que hace que "detener" el procesamiento de eventos sea necesario. conclusión: "pausar" un evento solo para reanudarlo en el mismo controlador no tiene ningún sentido.

si "muchas cosas" es algo asíncrono, esto tampoco tiene sentido, ya que impide que las cosas asíncronas hagan lo que deberían hacer (cosas asincrónicas) y que se comporten como si todo estuviera en secuencia (donde volvemos a mi primer párrafo )

oezi
fuente
El proceso en el medio es asíncrono, quiero disparar el resultado en la devolución de llamada ajax ...
Mazatec
1
si tiene que esperar una solicitud ajax, hágalo sincrónico (para jquery, hay async-fag: api.jquery.com/jQuery.ajax ) ... pero hacer una solicitud ajax sincrónica es una mala idea en casi todos los casos, entonces sería mejor encontrar una solución diferente.
oezi
3

El enfoque que uso es este:

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

La solución aceptada no funcionará en caso de que esté trabajando con una etiqueta de anclaje. En este caso, no podrá volver a hacer clic en el enlace después de llamar e.preventDefault(). Eso es porque el evento de clic generado por jQuery es solo una capa encima de los eventos del navegador nativo. Por lo tanto, activar un evento 'clic' en una etiqueta de anclaje no seguirá el enlace. En su lugar, podría usar una biblioteca como jquery-simulate que le permitirá iniciar eventos del navegador nativo.

Más detalles sobre esto se pueden encontrar en este enlace

Miguel Carvajal
fuente
1

Otra solución es usar window.setTimeout en el detector de eventos y ejecutar el código una vez que el proceso del evento haya finalizado. Algo como...

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

Uso 0 para el período ya que no me importa esperar.

Αλέκος
fuente
1

Sé que este tema es antiguo, pero creo que puedo contribuir. Puede activar el comportamiento predeterminado de un evento en un elemento específico en cualquier momento en su función de controlador si ya conoce ese comportamiento. Por ejemplo, cuando activa el evento click en el botón de reinicio, en realidad llama a la función de reinicio en el formulario más cercano como comportamiento predeterminado. En su función de controlador, después de usar la función preventDefault, puede recuperar el comportamiento predeterminado llamando a la función de reinicio en el formulario más cercano en cualquier parte de su código de controlador.

Patrik
fuente
0

Si este ejemplo puede ayudar, agrega un "popin de confirmación personalizado" en algunos enlaces (conservo el código de "$ .ui.Modal.confirm", es solo un ejemplo para la devolución de llamada que ejecuta la acción original):

//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
fuente