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 SubscribeWe just found out that, somehow, this.$wrapper
is not our jQuery object, it's undefined!
... lines 1 - 64 | |
{% block javascripts %} | |
... lines 66 - 67 | |
<script> | |
var RepLogApp = { | |
... lines 70 - 82 | |
handleRepLogDelete: function(e) { | |
... lines 84 - 93 | |
var $totalWeightContainer = this.$wrapper.find('.js-total-weight'); | |
... lines 95 - 103 | |
}, | |
... lines 105 - 108 | |
}; | |
... lines 110 - 114 | |
</script> | |
{% endblock %} |
Rude! How is that even possible! The answer! Because JavaScript is weird, especially when it comes to the crazy this
variable!
Here's the deal: whenever you are in a callback function, like the success
callback of an AJAX call, the callback of an event listener, or even when passing a callback to the setTimeout()
function, the this
variable in your callback changes to be something else. And we already knew that! We know that this
in our event handler is actually a reference to the DOM Element object that was clicked. So the this
variable in handleRepLogDelete
is not our RepLogApp
object, even though we're inside of that object. Creepy!
We're going to talk a lot more about this situation... in a moment.
Fortunately, for now, the fix is easy. If you think about it, the RepLogApp
object is very similar to a class in PHP that has only static properties and methods. I mean, could we create multiple RepLogApp
objects? Nope! There can only ever be one. And because of that, each property - like $wrapper
- acts like a static property: you set and access it, but it's attached to our "static", single object: RepLogApp
, not to an individual instance of RepLogApp
.
If this is hard to wrap your head around, don't worry! Coming from PHP, objects in JavaScript are weird... and they'll get stranger before we're done. But, most things you can do in PHP you can also do in JavaScript... it just looks different. The stuff inside the object may not have some special static
keyword on them, but this is what static properties and methods look like in JavaScript.
And like static properties and methods in PHP, you can reference them by their class name. Well, in JavaScript, that mean, by their object name - RepLogApp
:
... lines 1 - 64 | |
{% block javascripts %} | |
... lines 66 - 67 | |
<script> | |
var RepLogApp = { | |
... lines 70 - 82 | |
handleRepLogDelete: function(e) { | |
... lines 84 - 93 | |
var $totalWeightContainer = RepLogApp.$wrapper.find('.js-total-weight'); | |
... lines 95 - 103 | |
}, | |
... lines 105 - 108 | |
}; | |
... lines 110 - 114 | |
</script> | |
{% endblock %} |
Ok, go back and refresh now. Hit delete. It actually works! Sorry, I shouldn't sound so surprised!
Since we're running out of items, let's add a few more!
Now that we have a fancy object, we can use it to get even more organized, by breaking big functions into smaller ones.
For example, we could create a new function called, updateTotalWeightLifted
:
... lines 1 - 64 | |
{% block javascripts %} | |
... lines 66 - 67 | |
<script> | |
var RepLogApp = { | |
... lines 70 - 82 | |
updateTotalWeightLifted: function() { | |
... lines 84 - 89 | |
}, | |
... lines 91 - 117 | |
}; | |
... lines 119 - 123 | |
</script> | |
{% endblock %} |
Instead of figuring out the total weight lifted here and doing the update down in the success callback:
... lines 1 - 64 | |
{% block javascripts %} | |
... lines 66 - 67 | |
<script> | |
var RepLogApp = { | |
... lines 70 - 82 | |
handleRepLogDelete: function(e) { | |
... lines 84 - 93 | |
var $totalWeightContainer = RepLogApp.$wrapper.find('.js-total-weight'); | |
var newWeight = $totalWeightContainer.html() - $row.data('weight'); | |
$.ajax({ | |
... lines 97 - 98 | |
success: function() { | |
... line 100 | |
$totalWeightContainer.html(newWeight); | |
} | |
}); | |
}, | |
... lines 105 - 108 | |
}; | |
... lines 110 - 114 | |
</script> | |
{% endblock %} |
We'll just call this method and have it do all that heavy lifting.
Add var totalWeight = 0
:
... lines 1 - 64 | |
{% block javascripts %} | |
... lines 66 - 67 | |
<script> | |
var RepLogApp = { | |
... lines 70 - 82 | |
updateTotalWeightLifted: function() { | |
var totalWeight = 0; | |
... lines 85 - 89 | |
}, | |
... lines 91 - 117 | |
}; | |
... lines 119 - 123 | |
</script> | |
{% endblock %} |
Then I'll say, this.$wrapper
, which I can do because we're not in a callback function: this
is our object. Then, .find
to look for all tbody tr
elements, and .each()
to loop over them:
... lines 1 - 64 | |
{% block javascripts %} | |
... lines 66 - 67 | |
<script> | |
var RepLogApp = { | |
... lines 70 - 82 | |
updateTotalWeightLifted: function() { | |
var totalWeight = 0; | |
this.$wrapper.find('tbody tr').each(function() { | |
... line 86 | |
}); | |
... lines 88 - 89 | |
}, | |
... lines 91 - 117 | |
}; | |
... lines 119 - 123 | |
</script> | |
{% endblock %} |
But stop! Notice that when you use .each()
, you pass it a callback function! So guess what? Inside, this
is no longer our RepLogApp
object, it's something different. In this case, this
is the individual tr
DOM Element object that we're looping over in this moment.
Inside, add up all the total weights with totalWeight += $(this).data()
and read the data-weight
attribute:
... lines 1 - 64 | |
{% block javascripts %} | |
... lines 66 - 67 | |
<script> | |
var RepLogApp = { | |
... lines 70 - 82 | |
updateTotalWeightLifted: function() { | |
var totalWeight = 0; | |
this.$wrapper.find('tbody tr').each(function() { | |
totalWeight += $(this).data('weight'); | |
}); | |
... lines 88 - 89 | |
}, | |
... lines 91 - 117 | |
}; | |
... lines 119 - 123 | |
</script> | |
{% endblock %} |
Finally use this.$wrapper.find()
to look for our js-total-weight
element and set its HTML to totalWeight
:
... lines 1 - 64 | |
{% block javascripts %} | |
... lines 66 - 67 | |
<script> | |
var RepLogApp = { | |
... lines 70 - 82 | |
updateTotalWeightLifted: function() { | |
var totalWeight = 0; | |
this.$wrapper.find('tbody tr').each(function() { | |
totalWeight += $(this).data('weight'); | |
}); | |
this.$wrapper.find('.js-total-weight').html(totalWeight); | |
}, | |
... lines 91 - 117 | |
}; | |
... lines 119 - 123 | |
</script> | |
{% endblock %} |
Cool!
Down in handleRepLogDelete
, we don't need any of this logic anymore, nor this logic. We just need to call our new function. The only gotcha is that the fadeOut()
function doesn't actually remove the row from the DOM, so our new weight-totaling function would still count its weight.
Fix it by telling fadeOut()
to use normal
speed, pass it a function to be called when it finishes fading, and then say $row.remove()
to fully remove it from the DOM:
... lines 1 - 64 | |
{% block javascripts %} | |
... lines 66 - 67 | |
<script> | |
var RepLogApp = { | |
... lines 70 - 91 | |
handleRepLogDelete: function(e) { | |
... lines 93 - 100 | |
var deleteUrl = $(this).data('url'); | |
var $row = $(this).closest('tr'); | |
$.ajax({ | |
... lines 104 - 105 | |
success: function() { | |
$row.fadeOut('normal', function() { | |
$row.remove(); | |
... line 109 | |
}); | |
} | |
}); | |
}, | |
... lines 114 - 117 | |
}; | |
... lines 119 - 123 | |
</script> | |
{% endblock %} |
Now we can call updateTotalWeightLifted
.
But check this out: we're actually inside of another callback function, which is inside of a callback function, inside of our entire function which is itself a callback! So, this
is definitely not our RepLogApp
object.
No worries, play it safe and use RepLogApp.updateTotalWeightLifted()
instead:
... lines 1 - 64 | |
{% block javascripts %} | |
... lines 66 - 67 | |
<script> | |
var RepLogApp = { | |
... lines 70 - 91 | |
handleRepLogDelete: function(e) { | |
... lines 93 - 100 | |
var deleteUrl = $(this).data('url'); | |
var $row = $(this).closest('tr'); | |
$.ajax({ | |
... lines 104 - 105 | |
success: function() { | |
$row.fadeOut('normal', function() { | |
$row.remove(); | |
RepLogApp.updateTotalWeightLifted(); | |
}); | |
} | |
}); | |
}, | |
... lines 114 - 117 | |
}; | |
... lines 119 - 123 | |
</script> | |
{% endblock %} |
That's the equivalent in PHP of calling a static method by using its class name.
Ok, try it out! Refresh the page. We're at 765. Now delete a row... 657! Nice! Let's finally figure out what's really going on with the this
variable... and how to make it act better!
Hey sokphea chea!
Hmm. You said ReLogApp is not a function
- that should be RepLogApp
- is it possible there's a simple type? :). Otherwise, exactly what line does it say the error is on? I don't think that the var $totalWeightContainer = RepLogApp.$wrapper.find('.js-total-weight');
line is the problem - you're not using RepLogApp
as a function here, so it shouldn't cause this error. The problem could be somewhere else!
Cheers!
// 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
}
}
Hello, I've follow the video but when I change this.$wrapper to this 'var $totalWeightContainer = RepLogApp.$wrapper.find('.js-total-weight');'. I got an error ReLogApp is not function. Anyone have any idea? please help. thanks