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

Когда пользователь нажимает на определенную ссылку, я хотел бы представить им диалоговое окно подтверждения. Если они нажмут «Да», я хотел бы продолжить исходную навигацию. Одна загвоздка: мой диалог подтверждения реализуется путем возврата объекта 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
    })
})

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


person George Mauer    schedule 18.10.2011    source источник
comment
Пробовали ли вы $this.trigger(e); (редактировать, для этого потребуется флаг, если он сработает) или $this.parent().trigger(e) (в обратном вызове), где $this относится к элементам, на которые нажали?   -  person Felix Kling    schedule 18.10.2011
comment
@FelixKling Это не сработает, потому что e.preventDefault(); e.stopPropogation(); отправил флаги на e. Если бы был какой-то способ скопировать e в e2, то я мог бы вызвать что-то вроде e.handler(e2), что, я думаю, сработает.   -  person George Mauer    schedule 18.10.2011
comment
Ну, я подумал, может быть, jQuery достаточно умен и сбрасывает флаги, когда он передается в trigger...   -  person Felix Kling    schedule 18.10.2011
comment
@FelixKling хороший звонок, но я только что проверил код - не повезло.   -  person George Mauer    schedule 18.10.2011
comment
@FelixKling: я только что попробовал ваше решение .parent() с использованием исходного объекта события, и, похоже, оно работает нормально. jsfiddle.net/vTzDM Интересно, почему это не сработало для Джорджа.   -  person user113716    schedule 19.10.2011
comment
@Ӫ_._Ӫ: Думаю, он пробовал .trigger(e) (не работает). В вашем случае объект события просто передается в качестве параметра обработчику события, но его свойства не используются для нового объекта события, поэтому его передача не требуется. Я не знаю, нужно ли это ОП или нет. .parent().click() кажется хорошей альтернативой, но это зависит от того, что делают другие обработчики событий в иерархии. Спасибо за попытку :)   -  person Felix Kling    schedule 19.10.2011
comment
@FelixKling: Ах, ты прав. У меня тоже ошибка в ответе.   -  person user113716    schedule 19.10.2011
comment
@Ӫ_._Ӫ: Я думаю, что ваше второе и третье предложения действительно хороши.   -  person Felix Kling    schedule 19.10.2011
comment
@FelixKling: Спасибо, но если исходный объект события не нужен, то .parent().click() будет самым простым.   -  person user113716    schedule 19.10.2011


Ответы (8)


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

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

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

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

person c69    schedule 18.10.2011
comment
Что ж, return false должен вызывать e.stopPropogation(), но это может быть реализация браузера. - person George Mauer; 20.10.2011
comment
Почему это не приводит к бесконечному циклу? - person ryabenko-pro; 04.10.2019

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

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

Компьютер:

  • Chrome версии 76.0.3809.132
  • Сафари v.12.1.2
  • Firefox Квант v.69.0.1
  • Край 18
  • Край 17
  • IE11

Мобильный телефон/планшет:

  • Android v.8 Самсунг Интернет
  • Android v.8 Хром
  • Android версии 9 Chrome
  • iOS11.4 Сафари
  • iOS12.1 Сафари

.

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>  
  }
}
person OZZIE    schedule 20.09.2019
comment
Интересно, значит, вы перенаправляете на первоначальную цель. К сожалению, это означает, что любой, кто находится ниже по дереву пользовательского интерфейса, должен знать, что нужно проверять realClick, что может быть нереалистичным, когда речь идет о сторонних зависимостях. Также мне интересно, работает ли это только из-за определенного способа обработки событий. В любом случае, это хорошая идея для конкретного случая использования. - person George Mauer; 24.09.2019
comment
@GeorgeMauer Я не останавливаю распространение и не предотвращаю использование по умолчанию после его повторной отправки, поэтому я думаю, что это также должно быть безопасно. Если только dispatchEvent не распространяется по какой-либо причине. - person OZZIE; 26.09.2019
comment
Просто хотел сказать, что у меня это работает без проблем уже несколько месяцев :-) Но подумайте, если вы добавите, например, отслеживание GA/Analytics, что некоторые пользователи блокируют GA/Analytics, а затем вызов методов для этого может вызвать ошибки тогда и, возможно, сломай ссылку из рабочей. В этом случае просто попробуйте поймать (только) это утверждение. - person OZZIE; 28.08.2020
comment
В React 16 (Typescript) это дает мне ошибку react__WEBPACK_IMPORTED_MODULE_0__.MouseEvent is not a constructor в React 16 ... есть идеи? - person Sasgorilla; 17.07.2021

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

    // 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 :)
person Community    schedule 17.05.2012
comment
Для справки, в более поздних версиях jquery $(el).data('events') был удален. Существует неофициальный и неподдерживаемый способ получения событий путем выполнения $._data(el, 'events') обратите внимание, что el должен быть объектом DOM, а не jquery. - person George Mauer; 18.12.2013
comment
Удивительное решение, я не знал о stopImmediatePropagation. Спасибо! - person Diego Fortes; 18.05.2019

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

https://jsfiddle.net/oyetxu54/

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

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

person user2977636    schedule 11.05.2015
comment
Ваша скрипка не работает. Даже если нажата кнопка «стоп», событие распространяется. Проверено на последнем хроме - person Yoo Matsuo; 12.04.2017

Я решил это:

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

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>

Причина, по которой я добавил прослушиватель событий к родителю, а не к самой ссылке, заключается в том, что событие jQuery on будет привязано к элементу, пока не будет указано иное. Таким образом, даже если элемент не имеет класса another-page, к нему все еще подключен прослушиватель событий, поэтому вам нужно воспользоваться event delegation для решения этой проблемы.

ПОЛУЧИЛА это очень зависит от штата. то есть, если вам нужно спрашивать пользователя КАЖДЫЙ РАЗ, когда он нажимает на ссылку, вам нужно будет добавить второго слушателя, чтобы прочитать класс another-page обратно в ссылку. то есть:

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

Примечание: вы также можете удалить прослушиватель событий в container, если пользователь примет это. Если вы сделаете это, убедитесь, что вы используете события namespace, потому что в контейнере могут быть другие прослушиватели, которые вы можете случайно удалить. см. https://api.jquery.com/event.namespace/ для получения дополнительной информации.

person devkaoru    schedule 20.05.2015

У нас есть аналогичное требование в нашем проекте, и это работает для меня. Протестировано в Chrome и 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();
    })
  }

})
person Karuna Lakshman    schedule 09.08.2017
comment
Обратите внимание, что это не одно и то же! Это также приведет к повторному срабатыванию других обработчиков кликов, прикрепленных к тому же элементу. На самом деле, если двое используют один и тот же метод, вы получите бесконечный цикл кликов. - person George Mauer; 09.08.2017

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

  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");
    });
});
person Yura Kosyak    schedule 14.12.2018
comment
Привет. Пожалуйста, также объясните свой код при публикации ответа - это поможет лучше понять его. - person Ali; 14.12.2018
comment
Покажи мне код! Это о чем-то, что вы сказали? Но если это то, чего ты хочешь!? Хорошо. - person Yura Kosyak; 14.12.2018
comment
Привет, это правило сайта, чтобы иметь некоторое объяснение вашего кода при публикации ответов. Настоятельно рекомендуется, если у кого-то еще есть такая же проблема, и они наткнутся на этот пост. - person Ali; 14.12.2018
comment
Это похоже на то, чего, как я сказал, я хотел избежать - вы просто повторно запускаете другое событие. - person George Mauer; 15.12.2018

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

$('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()
  })
})
person irishbuzz    schedule 18.10.2011
comment
Это отменит привязку всех обработчиков событий click! Кроме того, эта защита срабатывает только один раз, поэтому любое кэширование страниц на стороне клиента исключено. У меня есть обходные пути, просто интересно, есть ли что-то более идиоматичное. - person George Mauer; 18.10.2011