web-dev-qa-db-ja.com

キャンセル後にイベントの伝播を継続する方法は?

ユーザーが特定のリンクをクリックすると、確認ダイアログが表示されます。 [はい]をクリックした場合、元のナビゲーションを続けたいと思います。ワンキャッチ:確認ダイアログは、ユーザーが[はい]ボタンをクリックした場合にのみ解決される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
    })
})

もちろん、フラグを設定してクリックを再トリガーすることもできますが、それは面倒です。これを行う自然な方法はありますか?

57
George Mauer

以下は、Chrome 13で実際に動作したコードの一部です。驚いたことに。

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

これは非常にクロスブラウザではなく、将来的に修正されるかもしれません。

そして、イベントの伝播をキャンセルするとどうなるかわかりません。

11
c69

問題を正しく理解している場合、イベントを更新して、そのクロージャーの元のイベントにすることができると思います。したがって、.done関数でe = e.originalEventを設定するだけです。

https://jsfiddle.net/oyetxu54/

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

これは別の例のフィドルです(メッセージを表示できるようにコンソールを開いたままにしてください):これはchrome and firefoxで機能しました

3
user2977636

私は自分のプロジェクトの1つでこの方法で問題を解決しました。この例は、クリックなどの基本的なイベント処理で動作します。確認用のハンドラーは、最初にハンドラーにバインドする必要があります。

    // 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
Jan Míšek

私たちのプロジェクトには同様の要件があり、これは私にとってはうまくいきます。 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();
    })
  }

})
0
Karuna Lakshman

コードを編集しました。追加した新機能:

  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");
    });
});
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 [〜#〜]これは非常に状態ベースです。つまり、ユーザーがリンクをクリックするたびにユーザーに確認する必要がある場合は、2番目のリスナーを追加してanother-pageクラスを再度リンクに追加する必要があります。すなわち:

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

side noteユーザーが同意した場合はcontainerのイベントリスナーを削除することもできます。これを行う場合は、必ずnamespaceイベントを使用してください。誤って削除する可能性のあるコンテナの他のリスナーである可能性があります。詳細については、 https://api.jquery.com/event.namespace/ を参照してください。

0
devkaoru