The Event Argument & stopPropagation

Back to our mission: when I click a delete link, it works... but I hate that it puts that annoying # in my URL and scrolls me up to the top of the page. You guys have probably seen and fixed that a million times. The easiest way is by finding your listener function and - at the bottom - returning false:

81 lines app/Resources/views/lift/index.html.twig
... lines 1 - 61
{% block javascripts %}
... lines 63 - 64
<script>
$(document).ready(function() {
var $table = $('.js-rep-log-table');
$table.find('.js-delete-rep-log').on('click', function () {
console.log('todo delete!');
return false;
});
... lines 74 - 77
});
</script>
{% endblock %}

Go back, remove that pound sign, refresh, and click! Haha! Get outta here pound sign!

But woh, something else changed: we're also not getting the "row clicked" text anymore. If I click just the row, I get it, but if I click the delete icon, it only triggers the event on that element. What the heck just happened?

The Event (e) Listener Argument

Back up a step. Whenever a listener function is called, your browser passes it an event argument, commonly just named e:

82 lines app/Resources/views/lift/index.html.twig
... lines 1 - 61
{% block javascripts %}
... lines 63 - 64
<script>
$(document).ready(function() {
... lines 67 - 68
$table.find('.js-delete-rep-log').on('click', function (e) {
... lines 70 - 73
});
... lines 75 - 78
});
</script>
{% endblock %}

This e variable is packed with information and some functions. The most important is e.preventDefault():

82 lines app/Resources/views/lift/index.html.twig
... lines 1 - 61
{% block javascripts %}
... lines 63 - 64
<script>
$(document).ready(function() {
... lines 67 - 68
$table.find('.js-delete-rep-log').on('click', function (e) {
e.preventDefault();
... lines 71 - 73
});
... lines 75 - 78
});
</script>
{% endblock %}

Another is e.stopPropagation():

82 lines app/Resources/views/lift/index.html.twig
... lines 1 - 61
{% block javascripts %}
... lines 63 - 64
<script>
$(document).ready(function() {
... lines 67 - 68
$table.find('.js-delete-rep-log').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
... lines 72 - 73
});
... lines 75 - 78
});
</script>
{% endblock %}

It turns out that when you return false from a listener function, it is equivalent to calling e.preventDefault() and e.stopPropagation(). To prove it, remove the return false and refresh:

82 lines app/Resources/views/lift/index.html.twig
... lines 1 - 61
{% block javascripts %}
... lines 63 - 64
<script>
$(document).ready(function() {
... lines 67 - 68
$table.find('.js-delete-rep-log').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
console.log('todo delete!');
});
... lines 75 - 78
});
</script>
{% endblock %}

Yep, same behavior: no # sign, but still no "row clicked" when we click the delete icon.

e.preventDefault() versus e.stopPropagation()

The e.preventDefault() says: don't do the default, browser behavior for this event. Normally, when you "click" a "link", your browser navigates to its href... which is a #. So cool, e.preventDefault() stops that! But e.stopPropagation() tells your browser to not bubble this event any further up the DOM tree. And that's probably not what you want. Do you really want your event listener to be so bold that it decides to prevent all other listeners from firing? I've literally never had a use-case for this.

So get rid of that pesky e.stopPropagation() and refresh again:

81 lines app/Resources/views/lift/index.html.twig
... lines 1 - 61
{% block javascripts %}
... lines 63 - 64
<script>
$(document).ready(function() {
... lines 67 - 68
$table.find('.js-delete-rep-log').on('click', function (e) {
e.preventDefault();
console.log('todo delete!');
});
... lines 74 - 77
});
</script>
{% endblock %}

And things are back to normal!

You should use e.preventDefault() in most cases, but not always. Sometimes, like with a keyup event, if you call preventDefault(), that'll prevent whatever the user just typed from actually going into the text box.

Now, what else can this magical event argument help us with?

Leave a comment!

  • 2017-07-24 Victor Bocharsky

    Hey Jozef,

    Yes, you can use it as well. But actually, it's something different than href="#" so we can't compare it. Actually, it's more similar to "event.preventDefault();". because without preventDefault() you will add '#' to the end of the current URL. But I think, event.preventDefault() is more proper way than using href="javascript:void(0)" nowadays and worth you nothing if you handle clicks with JS code - just add e.preventDefault() to the beginning of your handler.

    Cheers!

  • 2017-07-23 Jozef Repáň

    what about using href="javascript:void(0)" instead of href="#" in jQuery handled links?

  • 2017-01-19 weaverryan

    Hey Johan!

    I think that's indeed a valid use-case! If what you described is your requirements, I can't think of a better way of doing this. LOVE it - thanks for commenting :).

    Cheers!

  • 2017-01-19 Johan

    I think I've used the stopPropagation() function before.

    I had a table where each row had a delete button and each row was clickable as well, navigating to some other page. If you're clicking the delete button, you don't want to trigger the event that navigates to the page. I'm not sure if I used stopPropagation() for that but I think that function could be useful in that case.