Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Organizing with Objects!

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

Ok, this all looks pretty good... except that our code is just a bunch of functions and callback functions! Come on people, if this were PHP code, we would be using classes and objects. Let's hold our JavaScript to that same standard: let's use objects.

Creating an Object

How do you create an object? There are a few ways, but for now, it's as simple as var RepLogApp = {}:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
... lines 70 - 108
};
... lines 110 - 114
</script>
{% endblock %}

Yep, that's an object. Yea, I know, it's just an associative array but an associative array is an object in JavaScript. And its keys become the properties and methods on the object. See, JavaScript doesn't have classes like PHP, only objects. Well, that's not entirely true, but we'll save that for a future tutorial.

Adding a Method

Anyways, let's give our object a new method: an initialize key set to a function(). We'll call this when the page loads, and its job will be to attach all the event handlers for all the events that we need on our table. Give it a $wrapper argument:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
initialize: function($wrapper) {
... lines 71 - 80
},
... lines 82 - 108
};
... lines 110 - 114
</script>
{% endblock %}

Setting a Property

Before we do anything else, set that $wrapper argument onto a property: this.$wrapper = $wrapper:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
initialize: function($wrapper) {
this.$wrapper = $wrapper;
... lines 72 - 80
},
... lines 82 - 108
};
... lines 110 - 114
</script>
{% endblock %}

Yep, we just dynamically added a new property. This is the second time we've seen the this variable in JavaScript. And this time, it's more familiar: it refers to this object.

Next, copy our first listener registration code, but change $table to this.$wrapper. And instead of using a big ugly anonymous function, let's make this event call a new method on our object: this.handleRepLogDelete:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
initialize: function($wrapper) {
this.$wrapper = $wrapper;
this.$wrapper.find('.js-delete-rep-log').on(
'click',
this.handleRepLogDelete
);
... lines 77 - 80
},
... lines 82 - 108
};
... lines 110 - 114
</script>
{% endblock %}

We'll add that in a moment.

Repeat this for the other event listener: copy the registration line, change $table to this.$wrapper, and then on click, call this.handleRowClick:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
initialize: function($wrapper) {
this.$wrapper = $wrapper;
... lines 72 - 76
this.$wrapper.find('tbody tr').on(
'click',
this.handleRowClick
);
},
... lines 82 - 108
};
... lines 110 - 114
</script>
{% endblock %}

I already like it!

After initialize, create these methods! Add a key called, handleRepLogDelete set to a new function:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
... lines 70 - 82
handleRepLogDelete: function(e) {
... lines 84 - 103
},
... lines 105 - 108
};
... lines 110 - 114
</script>
{% endblock %}

Then go copy all of our original handler code, delete it, and put it here:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
... lines 70 - 82
handleRepLogDelete: function(e) {
e.preventDefault();
$(this).addClass('text-danger');
$(this).find('.fa')
.removeClass('fa-trash')
.addClass('fa-spinner')
.addClass('fa-spin');
var deleteUrl = $(this).data('url');
var $row = $(this).closest('tr');
var $totalWeightContainer = $table.find('.js-total-weight');
var newWeight = $totalWeightContainer.html() - $row.data('weight');
$.ajax({
url: deleteUrl,
method: 'DELETE',
success: function() {
$row.fadeOut();
$totalWeightContainer.html(newWeight);
}
});
},
... lines 105 - 108
};
... lines 110 - 114
</script>
{% endblock %}

Make sure you have the, e argument exactly like before.

Do the same thing for our other method: handleRowClick set to a function() {}:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
... lines 70 - 105
handleRowClick: function() {
... line 107
}
};
... lines 110 - 114
</script>
{% endblock %}

I'm not using the, e argument, so I don't need to add it. Copy the console.log() line, delete it, and put it here:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
... lines 70 - 105
handleRowClick: function() {
console.log('row clicked!');
}
};
... lines 110 - 114
</script>
{% endblock %}

Don't Call your Handler Function: Pass It

There's one teenie detail I want you to notice: when we specify the event callback, this.handleRepLogDelete - we're not executing it:

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
initialize: function($wrapper) {
... lines 71 - 72
this.$wrapper.find('.js-delete-rep-log').on(
... line 74
this.handleRepLogDelete
);
this.$wrapper.find('tbody tr').on(
... line 78
this.handleRowClick
);
},
... lines 82 - 108
};
... lines 110 - 114
</script>
{% endblock %}

I mean, there are no () on the end of it. Nope, we're simply passing the function as a reference to the on() function. If you forget and add (), things will get crazy.

Initializing (not Instantiating) the Object

Back in the (document).ready(), our job is really simple: find the $table and then pass it to RepLogApp.initialize():

... lines 1 - 64
{% block javascripts %}
... lines 66 - 67
<script>
var RepLogApp = {
... lines 70 - 108
};
$(document).ready(function() {
var $table = $('.js-rep-log-table');
RepLogApp.initialize($table);
});
</script>
{% endblock %}

The cool thing about this approach is that now we have an entire object who's job is to work inside of this.$wrapper.

Ok, let's try this! Go back and refresh! Hit delete! Ah, it fails!

Variable $table is not defined.

The problem is inside of handleRepLogDelete. Ah, cool, this makes total sense. Before, we had a $table variable defined above the function. That's gone, but no problem! Just use this.$wrapper:

... 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 %}

You can already see how handy an object can be.

Ok, go back and refresh again. Open up the console, click delete and... whoa! That doesn't work either! The errors is on the exact same line. What's going on here? It says:

Cannot read property 'find' of undefined

How can this.$wrapper be undefined? Let's find out.

Leave a comment!

9
Login or Register to join the conversation
Akavir S. Avatar
Akavir S. Avatar Akavir S. | posted 3 years ago

Hello knp :)
Is it possible to pass variables into the event listener callback ?

Reply

Hey Virgile!

Hm, do you have an example of the code? How does your code looks like and what you're trying to do? Probably you can just pass variables as arguments to the callback?

Cheers!

Reply
Akavir S. Avatar
Akavir S. Avatar Akavir S. | Victor | posted 3 years ago | edited

Hello victor, thanks for taking care of my problem
Here is a ajax call

$.ajax({
        url: "localhost/start",
        method: 'POST',
        data: {link: window.location.pathname, priority: priority},
        success: function (response) {
            $('#target').html(response)}
    })```



When i do not set a static function in my object everything works fine. 
But when i do so


success:function (response) {

$('#target').html(response)

},`
success:myobject.success(this.response)`

my response is undefined ..

Reply

Hey Virgile,

The method will be called by a caller somewhere in jQuery internals and so the proper argument will be passed by caller as well. You should not worry to pass the argument because you don't have it yet. Also, if you want to set a function - you should not add parentheses to it, otherwise it will be called instead of set. Try this:


// Declare a function with an argument that will be passed by a caller on this function call later.
success: function (response) {
    $('#target').html(response)
},

// and then use it
    $.ajax({
        url: "localhost/start",
        method: 'POST',
        data: {link: window.location.pathname, priority: priority},
        success: myobject.success
    })

Please, note there is no "()" at the end of "myobject.success" - we need to set that function instead of call it right now, it will be called later.

Cheers!

Reply
Graymath technology Avatar
Graymath technology Avatar Graymath technology | posted 4 years ago

Hi Max
the wrapper object that is passed why do we need to make it a property can't we directly work on it? Instead of $this.wrapper. everywhere we could just work with $wrapper?

Reply

Hey Abdul,

Because we'll use that wrapper on other method of RepLogApp, e.g. see handleRepLogDelete() in the last code block. So if we don't set it on a class property, we won't be able to use it there, because we can't pass the wrapper into that method.

Cheers!

Reply
Max S. Avatar

Hey guys!

As always amazing work - highly instructive and fun to code along!

There is no equivalent of a __construct() function in js, right? As well call initialize explicitly...

Reply

Yo Max,

Thanks a lot! And that's a really good question. Well, yes and no :) Actually, JS has ability to add constructors, but not in that form we see it in PHP classes. Just go further and you will see how to add a constructor... but if you can't wait - take a look at: https://knpuniversity.com/s...

Cheers!

-1 Reply
Max S. Avatar

Thx! I'm going to hit that one soon ;)

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