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.
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.
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.
Hello knp :)
Is it possible to pass variables into the event listener callback ?