Как продолжить распространение событий после отмены?

Когда пользователь нажимает на определенную ссылку, я хотел бы представить их с помощью диалогового окна подтверждения. Если они нажмут "Да", я бы хотел продолжить оригинальную навигацию. Один улов: мой диалог подтверждения реализуется путем возвращения объекта jQuery.Deferred, который разрешен только тогда, когда/если пользователь нажимает кнопку "Да". Таким образом, в основном диалог подтверждения является асинхронным.

Итак, в основном я хочу что-то вроде этого:

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

Конечно, я мог бы установить флаг и повторно запускать клик, но это грязно, как черт. Любой естественный способ сделать это?

Ответ 1

Ниже приведены фрагменты кода, который действительно работал в Chrome 13, к моему удивлению.

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

Это не очень кросс-браузер и, возможно, будет исправлено в будущем, потому что это похоже на риск безопасности, imho.

И я не знаю, что произойдет, если вы отмените распространение события.

Ответ 2

Я решил проблему таким образом в одном из моих проектов. Этот пример работает с некоторыми базовыми обработками событий, такими как клики и т.д. Обработчик для подтверждения должен быть связан с первым обработчиком.

    // 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 :)

Ответ 3

Если я правильно понимаю проблему, я думаю, вы можете просто обновить событие, чтобы быть оригинальным событием в этом закрытии, которое у вас есть. Поэтому просто установите e = e.originalEvent в .done-функцию.

https://jsfiddle.net/oyetxu54/

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

вот скрипка с другим примером (держите консоль открытой, чтобы вы могли видеть сообщения): это сработало для меня в chrome и firefox

Ответ 4

Я думаю, что это может быть рискованно (!), Но, похоже, работает на момент написания статьи.

Это ES6 и React, я протестировал и обнаружил, что он работает для следующих браузеров. Одним из бонусов является то, что если есть исключение (при создании которого была пара), оно идет по ссылке, как обычная ссылка <a>, но не будет SPA, тогда ofc.

Desktop:

  • Chrome v.76.0.3809.132
  • Safari v.12.1.2
  • Firefox Quantum v.69.0.1
  • Край 18
  • Край 17
  • IE11

Mobile/Tablet:

  • Android v.8 Samsung Интернет
  • 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>  
  }
}

Ответ 5

Я решил это:

  • размещение прослушивателя событий на родительском элементе
  • удаление класса из ссылки ТОЛЬКО, когда пользователь подтверждает
  • откат ссылки после удаления класса.

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>

Ответ 6

У нас есть аналогичное требование в нашем проекте, и это работает для меня. Протестировано в хроме и IE11.

$('a.my-link').click(function(e) {
  e.preventDefault(); 
  if (do_something === true) {
    e.stopPropogation();
    MyApp.confirm("Are you sure you want to navigate away?")
    .done(function() {
      do_something = false;
      // this allows user to navigate 
      $(e.target).click();
    })
  }

})

Ответ 7

Я отредактировал твой код. Новые функции, которые я добавил:

  1. Добавлено пространство имен к событию;
  2. После нажатия на элемент событие будет удалено пространством имен;
  3. Наконец, после завершения необходимых действий в разделе "MyApp" продолжайте распространение, вызывая события "щелчка" других элементов.

Код:

$('a.my-link').on("click.myEvent", function(e) {
  var $that = $(this);
  $that.off("click.myEvent");
  e.preventDefault();
  e.stopImmediatePropagation();
  MyApp.confirm("Are you sure you want to navigate away?")
    .done(function() {
        //continue propogation of e
        $that.trigger("click");
    });
});

Ответ 8

Это непроверено, но может служить обходным путем для вас

$('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
      $(this).unbind('click').click()
  })
})