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 SubscribeOk, 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.
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.
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 %} |
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 %} |
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.
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.
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!
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 ..
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!
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?
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!
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...
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!
// 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 knp :)
Is it possible to pass variables into the event listener callback ?