取消后如何继续事件传播?


71

当用户单击某个链接时,我想向他们显示一个确认对话框。如果他们单击“是”,我想继续原始导航。一个陷阱:我的确认对话框是通过返回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
您是否尝试过$this.trigger(e);(如果需要编辑,则需要标志)或$this.parent().trigger(e)(在回调中)在哪里$this引用单击的元素?
Felix Kling

@FelixKling那不会起作用,因为e.preventDefault(); e.stopPropogation();已经在e上发送了标志。如果有某种方法可以将e复制到e2,那么我可以按照e.handler(e2)我认为可行的方式进行调用。
乔治·莫尔

好吧,我以为jQuery足够聪明,可以在传递给trigger...时重设标志...
Felix Kling

@FelixKling很好,但是我只是检查了代码-没有这种运气。
乔治·莫尔

1
@FelixKling:谢谢,但是如果不需要原始事件对象,那么.parent().click()它将是最简单的。
user113716 2011年

Answers:


11

以下是Chrome 13中实际可用的代码中的一些内容,令我惊讶的是。

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

这不是一个跨浏览器,并且可能会在将来修复,因为它感觉像安全风险,恕我直言。

而且我不知道会发生什么,如果您取消事件传播。


那么return false应该调用e.stopPropogation()-但可能是一个浏览器的实现
乔治·莫尔

5
为什么不导致无限循环?
ryabenko-pro

4

它可能有风险,但似乎至少在撰写本文时就可以使用,我们正在生产中使用它。

这是ES6和React,我已经测试过并发现它适用于以下浏览器。一个额外的好处是,如果有异常(在进行此操作的过程中有几个),它会像普通<a>链接一样进入该链接,但不会是SPA,然后是OFC。

桌面:

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

手机/平板电脑:

  • 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>  
  }
}

有趣,因此您可以重新分配原始目标。不幸的是,这意味着任何在ui树下的人都必须知道检查realClick那些与第三方依赖关系不切实际的东西。我也想知道这是否仅由于react处理事件的特定方式而起作用。无论如何,对于特定的用例来说,这是一个好主意
George Mauer

@GeorgeMauer我不会在重新分发后停止stopPropagation或防止默认设置,因此我认为它也应该是安全的。除非dispatchEvent由于某种原因没有传播。
OZZIE

只是想说我已经在生产环境中运行了几个月,但现在还没有问题:-)但是请考虑是否添加了GA / Analtytics跟踪功能,例如某些用户阻止了GA / Analytics,然后调用该方法可能会引发错误,甚至可能断开链接的工作。在这种情况下,只需尝试(仅)捕获该语句。
OZZIE

3

通过这种方式,我在一个项目中解决了问题。此示例适用于某些基本事件处理,例如单击等。用于确认的处理程序必须首先绑定处理程序。

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

作为记录,$(el).data('events')已删除了jquery的最新版本。有一种非官方且不受支持的方式来获取事件,方法是$._data(el, 'events')注意el必须是DOM对象而不是jquery
George Mauer 2013年

惊人的解决方案,我不知道stopImmediatePropagation。谢谢!
迭戈·福特斯

2

如果我正确地理解了问题,我认为您可以将事件更新为该事件所在的原始事件。因此,只需在.done函数中设置e = e.originalEvent。

https://jsfiddle.net/oyetxu54/

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

这是一个带有其他示例的小提琴(使控制台保持打开状态,以便您可以看到消息):在chrome和firefox中对我有用


1
你的小提琴不起作用。即使单击“停止”,事件也会传播。经过最新的镀铬测试
松尾

0

我通过以下方法解决了这个问题:

  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来解决此问题。

GOTCHAS这是非常基于状态的。即,如果您需要每次询问用户,他们都必须单击链接,则必须添加第二听众才能将another-page类读回链接。即:

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

旁注:container如果用户接受,您也可以删除事件侦听器,如果这样做,请确保使用namespace事件,因为容器上可能有其他侦听器,您可能会无意中将其删除。有关更多详细信息,请参见https://api.jquery.com/event.namespace/


0

我们在项目中有类似的要求,这对我有用。经过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();
    })
  }

})

1
请注意,这不是同一件事!它还将触发 附加到同一元素的其他点击处理程序再次触发。实际上,如果两个人使用相同的技术,您将获得无限的点击循环。
乔治·莫尔

0

我编辑了您的代码。我添加的新功能:

  1. 为事件添加了名称空间;
  2. 单击元素后,事件将被命名空间删除;
  3. 最后,完成“ MyApp”部分中所需的操作后,通过触发其他元素“ click”事件来继续传播。

码:

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

嗨,请在发布答案时也说明您的代码-这将有助于更好地理解它
阿里(Ali)

给我看看代码!是关于你说的话吗?但是,如果这就是您想要的!?好的。
ЮраКосяк

嗨,这是网站规则,用于在发布答案时对您的代码进行一些解释-强烈建议您,以防其他人也遇到相同的问题,并且遇到此问题。
阿里

这是我说过要避免的事情-您只是重新触发另一个事件。
乔治·莫尔

-2

这未经测试,但可以作为您的解决方法

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

这将取消绑定所有click事件处理程序!同样,这种保护只会发生一次,因此任何客户端页面缓存都不会出现。我有解决方法,只是想知道是否还有一些惯用语。
乔治·莫尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.