The Magical this Variable & currentTarget

Turning the icon red is jolly good and all, but since we'll soon make an AJAX call, it would be way jollier if we could turn that icon into a spinning loader icon. But, there's a problem.

After the trash icon, type "Delete":

83 lines app/Resources/views/lift/index.html.twig
... lines 1 - 2
{% block body %}
<div class="row">
<div class="col-md-7">
... lines 6 - 12
<table class="table table-striped js-rep-log-table">
... lines 14 - 22
{% for repLog in repLogs %}
<tr>
... lines 25 - 27
<td>
<a href="#" class="js-delete-rep-log">
<span class="fa fa-trash"></span>
Delete
</a>
</td>
</tr>
... lines 35 - 38
{% endfor %}
... lines 40 - 48
</table>
... lines 50 - 51
</div>
... lines 53 - 59
</div>
{% endblock %}
... lines 62 - 83

Now we have a trash icon with the word delete next to it. Back in our JavaScript, once again, console.log() the actual element that was clicked: e.target:

83 lines app/Resources/views/lift/index.html.twig
... lines 1 - 62
{% block javascripts %}
... lines 64 - 65
<script>
$(document).ready(function() {
... lines 68 - 69
$table.find('.js-delete-rep-log').on('click', function (e) {
e.preventDefault();
$(e.target).addClass('text-danger');
console.log(e.target);
});
... lines 76 - 79
});
</script>
{% endblock %}

e.target is Fooling Us!

Now, behold the madness! If I click the trash icon, e.target is a span. But if I click the delete text, it's actually the anchor! Woh!

True to what I said, e.target will be the exact one element that originally received the event, so click in this case. And that's a problem for us! Why? Well, I want to be able to find the fa span element and change it to a spinning icon. Doing that is going to be annoying, because if we click on the trash icon, e.target is that element. But if we click on the word delete, then we need to look inside of e.target to find the span.

Hello e.currentTarget

It would be WAY more hipster if we could retrieve the element that the listener was attached to. In other words, which js-delete-rep-log was clicked? That would make it super easy to look for the fa span inside of it and make the changes we need.

No problem! Change e.target to e.currentTarget and high-five yourself:

83 lines app/Resources/views/lift/index.html.twig
... lines 1 - 62
{% block javascripts %}
... lines 64 - 65
<script>
$(document).ready(function() {
... lines 68 - 69
$table.find('.js-delete-rep-log').on('click', function (e) {
e.preventDefault();
$(e.target).addClass('text-danger');
console.log(e.currentTarget);
});
... lines 76 - 79
});
</script>
{% endblock %}

Yep, this ends up being much more useful than e.target. Now when we refresh and click the trash icon, it's the anchor tag. Click the delete icon, it's still the anchor tag. No matter which element we actually click, e.currentTarget returns the original element that we attached the listener to.

Enter: this (versus currentTarget)

In fact, try this: console.log(e.currentTarget === this):

83 lines app/Resources/views/lift/index.html.twig
... lines 1 - 62
{% block javascripts %}
... lines 64 - 65
<script>
$(document).ready(function() {
... lines 68 - 69
$table.find('.js-delete-rep-log').on('click', function (e) {
e.preventDefault();
$(e.target).addClass('text-danger');
console.log(e.currentTarget === this);
});
... lines 76 - 79
});
</script>
{% endblock %}

Refresh! And click anywhere on the delete link. It's always true.

There's a good chance that you've been using the this variable for years inside of your listener functions to find the element that was clicked. And now we know the true and dramatic story behind it! this is equivalent to e.currentTarget, the DOM Element that we originally attached our listener to.

Ultimately that means that we can say, $(this).addClass('text-danger'):

82 lines app/Resources/views/lift/index.html.twig
... lines 1 - 62
{% block javascripts %}
... lines 64 - 65
<script>
$(document).ready(function() {
... lines 68 - 69
$table.find('.js-delete-rep-log').on('click', function (e) {
e.preventDefault();
$(this).addClass('text-danger');
});
... lines 75 - 78
});
</script>
{% endblock %}

That will always add the text-danger link to the anchor tag.

And finally, we can easily change our icon to a spinner! Just use $(this).find('.fa') to find the icon inside of the anchor. Then, .removeClass('fa-trash'), .addClass('fa-spinner') and .addClass('fa-spin'):

86 lines app/Resources/views/lift/index.html.twig
... lines 1 - 62
{% block javascripts %}
... lines 64 - 65
<script>
$(document).ready(function() {
... lines 68 - 69
$table.find('.js-delete-rep-log').on('click', function (e) {
e.preventDefault();
$(this).addClass('text-danger');
$(this).find('.fa')
.removeClass('fa-trash')
.addClass('fa-spinner')
.addClass('fa-spin');
});
... lines 79 - 82
});
</script>
{% endblock %}

Refresh! Show me a spinner! There it is! It doesn't matter if we click the "Delete" text or the trash icon itself.

So, use the this variable, it's your friend. But realize what's going on: this is just a shortcut to e.currentTarget. That fact is going to become critically important in just a little while.

Now that we've learned this, remove the "delete" text... it's kinda ugly:

85 lines app/Resources/views/lift/index.html.twig
... lines 1 - 2
{% block body %}
<div class="row">
<div class="col-md-7">
... lines 6 - 12
<table class="table table-striped js-rep-log-table">
... lines 14 - 22
{% for repLog in repLogs %}
<tr>
... lines 25 - 27
<td>
<a href="#" class="js-delete-rep-log">
<span class="fa fa-trash"></span>
</a>
</td>
</tr>
... lines 34 - 37
{% endfor %}
... lines 39 - 47
</table>
... lines 49 - 50
</div>
... lines 52 - 58
</div>
{% endblock %}
... lines 61 - 85

Leave a comment!

  • 2017-06-29 weaverryan

    Haha, right!? I promise, now JavaScript will be WAY more fun ;).

  • 2017-06-29 Yan Yong

    Thank you so much for showing me how 'this' actually works in js. Good work.