"Static" Objects & the this Variable

We just found out that, somehow, this.$wrapper is not our jQuery object, it's undefined!

117 lines app/Resources/views/lift/index.html.twig
... 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!

When this is not this

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.

Referencing your Object "Statically"

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:

117 lines app/Resources/views/lift/index.html.twig
... 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!

Refactoring to More Methods!

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:

126 lines app/Resources/views/lift/index.html.twig
... 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:

117 lines app/Resources/views/lift/index.html.twig
... 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:

126 lines app/Resources/views/lift/index.html.twig
... 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:

126 lines app/Resources/views/lift/index.html.twig
... 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:

126 lines app/Resources/views/lift/index.html.twig
... 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:

126 lines app/Resources/views/lift/index.html.twig
... 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:

126 lines app/Resources/views/lift/index.html.twig
... 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:

126 lines app/Resources/views/lift/index.html.twig
... 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!

Leave a comment!

  • 2017-04-12 sokphea chea (ជា សុខភា)

    weaverryan thank you

  • 2017-04-11 weaverryan

    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!

  • 2017-04-08 sokphea chea (ជា សុខភា)

    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