If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeTurning 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":
... 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
:
... 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 %} |
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.
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:
... 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.
In fact, try this: console.log(e.currentTarget === this)
:
... 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')
:
... 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')
:
... 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:
... 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 |
// composer.json
{
"require": {
"php": "^7.2.0",
"symfony/symfony": "3.1.*", // v3.1.10
"twig/twig": "2.10.*", // v2.10.0
"doctrine/orm": "^2.5", // v2.7.1
"doctrine/doctrine-bundle": "^1.6", // 1.10.3
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
"symfony/swiftmailer-bundle": "^2.3", // v2.4.0
"symfony/monolog-bundle": "^2.8", // 2.12.0
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"friendsofsymfony/user-bundle": "~2.0@dev", // dev-master
"doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.1
"symfony/phpunit-bridge": "^3.0" // v3.1.6
}
}
Thank you so much for showing me how 'this' actually works in js. Good work.