2

Is there any way in JavaScript or jQuery to detect when an event is finished propagating up the DOM? I have a jQuery click handler that gets attached (with target "html") on a click, and the very click that adds it is triggering it once it bubbles up to the target. I want to delay the handler attachment until propagation is done. I hear event.stopPropagation() is risky business, so I'd rather not use that.

Here's my code. #title-editable is an anchor tag. vclick is an event defined by jQuery Mobile. I get both console logs with one click on #title-editable.

  $("#title-editable").on("vclick", function(event){
    event.preventDefault();
    console.log("First handler fired.");
    /* Would like to detect completion of event propagation here 
      and only execute the next statement after that. */
    $("html").one("vclick",function(){
      console.log("Second handler fired.");
    });
  });

Edit: I think I have a workaround. Instead of .one for the inner event, I use .on so the event isn't detached the first time it fires. I check the target of the click event and only execute changes for the inner event, and detach the inner event, if the target isn't #title-editable.

  $("#title-editable").on("vclick", function(event){
    event.preventDefault();
    console.log("First handler fired.");
    $("html").on("vclick",function(event2){
      console.log("Second handler fired.");
      if(!$(event2.target).closest("#title-editable").length){
        console.log("Execute changes here.");
        $("html").off("vclick");
      }
    });
  • That makes no sense at all? This is most likely an X/Y problem, if anyone can understand it ? – adeneo Sep 21 '14 at 18:50
  • Normally the `body` is the destination of the event bubbling. But if you handle the event there, what if some code calls `stopPropagation()` somewhere under the tree? – King King Sep 21 '14 at 18:51
  • Can you point to what doesn't make sense about it? The basic thing I'm trying to do is have a click event attached to the document, but not fired, when #title-editable is clicked. Is there a better way to do this? – Kenji Yamada Sep 21 '14 at 18:54
  • Here's the behavior I want. User clicks on #title-editable. Some changes happen. (Specifically, I set a class on an ancestor of #title-editable, #title-editable disappears with display:none, and an input element appears.) If the user then clicks anywhere else, the reverse changes happen. – Kenji Yamada Sep 21 '14 at 18:57
  • Found a workaround and added it to the question. – Kenji Yamada Sep 21 '14 at 19:43
  • 1
    That's a jQuery bug. In native DOM, handlers that are installed during dispatching don't fire. – Bergi Sep 21 '14 at 19:45

1 Answers1

0

based on your last comment, the easiest logic would be something like this:

var changed=false;
$('#title-editable').on('vclick',function(event){
    event.preventDefault();
    console.log("First handler fired.");
    changed=true;
});
$(document).on("vclick",function(){
    if(changed){
        console.log("Second handler fired.");
        //here you can revert your actions and then the next line
        changed=false;
    }
});

UPDATE:

The Second Theory:

you said that an ancestor gets a class on click of #title-editable so you can try the code below:

$(document).on("vclick",function(event){
    if($('#title-editable').parent().hasClass('givenClass')){ //assuming the class is called "givenClass" and the ancestor is the parent of "#title-editable" (you need to sort that out)
        console.log("Second handler fired.");
        //here you can revert your actions and then the next line
    }
    else{
        event.preventDefault();
        console.log("First handler fired.");
    }
});

The Third Theory:

$(document).on("vclick",function(event){
    if(!$('#title-editable').is(':visible')){
        console.log("Second handler fired.");
        //here you can revert your actions and then the next line
    }
    else{
        event.preventDefault();
        console.log("First handler fired.");
    }
});

The Fourth Theory:

$(document).on("vclick",function(event){
    if(!$('#title-editable').is(event.target) || !$('#title-editable').has(event.target)){
        console.log("Second handler fired.");
        //here you can revert your actions and then the next line
    }
    else{
        event.preventDefault();
        console.log("First handler fired.");
    }
});
Amin Jafari
  • 7,157
  • 2
  • 18
  • 43
  • Thanks for the suggestion, but it seems to have the same problem. I tried it out, and both console logs fired on a single click again. I think what's happening is that changed=true; executes before the event has bubbled up to document root. – Kenji Yamada Sep 21 '14 at 19:08
  • The second theory seems to have the same problems - the timing of the class addition and class check mirror the timing of the boolean variable in the first theory. – Kenji Yamada Sep 21 '14 at 19:26
  • Same for the third theory, I think. #title-editable becomes invisible upon being clicked, and this seems to get executed before the click event reaches document. So that "if" always evaluates true. – Kenji Yamada Sep 21 '14 at 19:27
  • I came up with a workaround that seems to do it. Added it to the original question. Thanks for trying to help me! – Kenji Yamada Sep 21 '14 at 19:28
  • you're welcome, glad you got the answer but just in case try the fourth theory that I just added, I think it would work as well! – Amin Jafari Sep 21 '14 at 19:35
  • Thanks. Yes, I think the fourth theory is basically the same idea. Explicitly filter the inner event by target. – Kenji Yamada Sep 21 '14 at 19:43