Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

"Static" Objects & the this Variable

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

We 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!

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:

... 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:

... 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!

Leave a comment!

3
Login or Register to join the conversation
Default user avatar
Default user avatar sokphea chea | posted 5 years ago

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

Reply

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!

Reply
Default user avatar
Default user avatar sokphea chea | weaverryan | posted 5 years ago | edited

weaverryan thank you

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony... but since it's a JavaScript tutorial, the concepts are still ? valid!

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice