86 votes

Comment poursuivre la propagation des événements après une annulation ?

Lorsqu'un utilisateur clique sur un certain lien, j'aimerais lui présenter une boîte de dialogue de confirmation. S'il clique sur "Oui", j'aimerais poursuivre la navigation initiale. Un hic : mon dialogue de confirmation est mis en œuvre en renvoyant un objet jQuery.Deferred qui n'est résolu que si l'utilisateur clique sur le bouton "Oui". Le dialogue de confirmation est donc asynchrone.

En gros, je veux quelque chose comme ça :

$('a.my-link').click(function(e) {
  e.preventDefault(); e.stopPropogation();
  MyApp.confirm("Are you sure you want to navigate away?")
    .done(function() {
      //continue propogation of e
    })
})

Bien sûr, je pourrais placer un drapeau et redéclencher le clic, mais c'est très compliqué. Y a-t-il un moyen naturel de faire cela ?

1 votes

Avez-vous essayé $this.trigger(e); ( éditer qui aurait besoin d'un drapeau s'il fonctionnait) ou $this.parent().trigger(e) (dans le callback), où $this se réfère aux éléments cliqués ?

0 votes

@FelixKling Cela ne fonctionnera pas car e.preventDefault(); e.stopPropogation(); a envoyé des drapeaux sur e. S'il existait un moyen d'envoyer une copie de e à e2, je pourrais appeler quelque chose du genre e.handler(e2) ce qui, à mon avis, devrait fonctionner.

0 votes

Je me suis dit que jQuery était peut-être assez intelligent pour réinitialiser les drapeaux lorsqu'ils sont transmis à trigger ...

18voto

c69 Points 5140

Voici les parties du code qui ont fonctionné dans Chrome 13, à ma grande surprise.

function handler (evt ) {
    var t = evt.target;
    ...
    setTimeout( function() {
        t.dispatchEvent( evt )
    }, 1000);
    return false;
}

Ce n'est pas très inter-navigateur, et peut-être que cela sera corrigé à l'avenir, car cela ressemble à un risque de sécurité, selon moi.

Et je ne sais pas ce qui se passe si l'on annule la propagation des événements.

0 votes

Bien return false devrait appeler e.stopPropogation() - mais il pourrait s'agir d'une implémentation du navigateur

8 votes

Pourquoi cela ne conduit-il pas à une boucle infinie ?

8voto

OZZIE Points 367

Cela pourrait être risqué mais semble fonctionner au moment où j'écris ces lignes, en tout cas, nous l'utilisons en production.

C'est ES6 et React, j'ai testé et trouvé que cela fonctionnait pour les navigateurs ci-dessous. Un bonus est que s'il y a une exception (j'en ai eu quelques unes au cours de la réalisation), il va au lien comme un lien normal <a> lien, mais ce ne sera pas SPA alors bien sûr.

Bureau :

  • Chrome v.76.0.3809.132
  • Safari v.12.1.2
  • Firefox Quantum v.69.0.1
  • Bord 18
  • Bord 17
  • IE11

Mobile/Tablette :

  • Android v.8 Samsung Internet
  • Android v.8 Chrome
  • Android v.9 Chrome
  • iOs11.4 Safari
  • iOs12.1 Safari

.

import 'mdn-polyfills/MouseEvent'; // for IE11
import React, { Component } from 'react';
import { Link } from 'react-router-dom';

class ProductListLink extends Component {
  constructor(props) {
    super(props);
    this.realClick = true;

    this.onProductClick = this.onProductClick.bind(this);
  }

  onProductClick = (e) => {
    const { target, nativeEvent } = e;
    const clonedNativeEvent = new MouseEvent('click', nativeEvent);

    if (!this.realClick) {
      this.realClick = true;
      return;
    }

    e.preventDefault();
    e.stopPropagation();

    // @todo what you want before the link is acted on here

    this.realClick = false;
    target.dispatchEvent(clonedNativeEvent);
  };

  render() {
    <Link
      onClick={(e => this.onProductClick(e))}
    >
      Lorem
    </Link>  
  }
}

3voto

Jan Míšek Points 454

J'ai résolu le problème de cette manière sur l'un de mes projets. Cet exemple fonctionne avec quelques événements de base comme les clics, etc. Le gestionnaire de confirmation doit être le premier gestionnaire lié.

    // This example assumes clickFunction is first event handled.
    //
    // you have to preserve called function handler to ignore it 
    // when you continue calling.
    //
    // store it in object to preserve function reference     
    var ignoredHandler = {
        fn: false
    };

    // function which will continues processing        
    var go = function(e, el){
        // process href
        var href = $(el).attr('href');
        if (href) {
             window.location = href;
        }

        // process events
        var events = $(el).data('events');

        for (prop in events) {
            if (events.hasOwnProperty(prop)) {
                var event = events[prop];
                $.each(event, function(idx, handler){
                    // do not run for clickFunction
                    if (ignoredHandler.fn != handler.handler) {
                        handler.handler.call(el, e);
                    }
                });
            }
        }
    }

    // click handler
    var clickFunction = function(e){
        e.preventDefault();
        e.stopImmediatePropagation();
        MyApp.confirm("Are you sure you want to navigate away?")
           .done(go.apply(this, e));
    };

    // preserve ignored handler
    ignoredHandler.fn = clickFunction;
    $('.confirmable').click(clickFunction);

    // a little bit longer but it works :)

0 votes

Pour mémoire, dans les versions plus récentes de jquery $(el).data('events') a été supprimée. Il existe un moyen non officiel et non supporté d'obtenir des événements en faisant $._data(el, 'events') noter que el doit être un objet DOM et non pas jquery

0 votes

Solution étonnante, je n'étais pas au courant de l'existence de stopImmediatePropagation. Je vous remercie de votre attention.

2voto

user2977636 Points 583

Si je comprends bien le problème, je pense que vous pouvez simplement mettre à jour l'événement pour qu'il devienne l'événement original dans la fermeture que vous avez là. Il suffit donc de définir e = e.originalEvent dans la fonction .done.

https://jsfiddle.net/oyetxu54/

MyApp.confirm("confirmation?")
.done(function(){ e = e.originalEvent;})

Voici un exemple différent (gardez la console ouverte pour voir les messages) : cela a fonctionné pour moi dans chrome et firefox

0voto

devkaoru Points 1082

J'ai résolu ce problème en

  1. placer un écouteur d'événements sur un élément parent
  2. supprimer la classe du lien UNIQUEMENT lorsque l'utilisateur confirme
  3. en recliquant sur le lien après avoir supprimé la classe.

    function async() { var dfd = $.Deferred();

    // simulate async setTimeout(function () { if (confirm('Stackoverflow FTW')) { dfd.resolve(); } else { dfd.reject(); } }, 0);

    return dfd.promise(); };

    $('.container').on('click', '.another-page', function (e) { e.stopPropagation(); e.preventDefault(); async().done(function () { $(e.currentTarget).removeClass('another-page').click(); }); });

    $('body').on('click', function (e) { alert('navigating somewhere else woot!') });

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

    <div class="container"> <a href="#" class="another-page">Somewhere else</a> </div>

La raison pour laquelle j'ai ajouté l'écouteur d'événement au parent et non au lien lui-même est que la méthode jQuery on sera lié à l'élément jusqu'à ce qu'il en soit décidé autrement. Ainsi, même si l'élément n'a pas la classe another-page il y a toujours l'écouteur d'événement attaché, donc vous devez tirer parti de event delegation pour résoudre ce problème.

GOTCHAS Si vous avez besoin de demander à l'utilisateur CHAQUE fois qu'il clique sur un lien, vous devrez ajouter un deuxième écouteur pour lire les données de l'utilisateur. another-page sur le lien, c'est à dire :

$('body').on('click', function (e) {
  $(e.currentTarget).addClass('another-page');
});

note complémentaire vous pouvez également supprimer l'écouteur d'événement sur container si l'utilisateur accepte, si vous faites cela, assurez-vous d'utiliser namespace car il peut y avoir d'autres auditeurs sur le conteneur que vous pourriez supprimer par inadvertance. voir https://api.jquery.com/event.namespace/ pour plus de détails.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X