Delegate Selectors FTW!
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 SubscribeSo dang. Each time we submit, it adds a new row to the table, but its delete button doesn't work until we refresh. What's going on here?
Well, let's think about it. In RepLogApp
, the constructor function is called when we instantiate it. So, inside $(document).ready()
:
// ... lines 1 - 54 | |
{% block javascripts %} | |
// ... lines 56 - 59 | |
<script> | |
$(document).ready(function() { | |
var $wrapper = $('.js-rep-log-table'); | |
var repLogApp = new RepLogApp($wrapper); | |
}); | |
</script> | |
{% endblock %} |
That means it's executed after the entire page has loaded.
Then, at that exact moment, our code finds all elements with a js-delete-rep-log
class in the HTML, and attaches the listener to each DOM Element:
// ... lines 1 - 2 | |
(function(window, $) { | |
window.RepLogApp = function ($wrapper) { | |
// ... lines 5 - 7 | |
this.$wrapper.find('.js-delete-rep-log').on( | |
'click', | |
this.handleRepLogDelete.bind(this) | |
); | |
// ... lines 12 - 19 | |
}; | |
// ... lines 21 - 97 | |
})(window, jQuery); |
So if we have 10 delete links on the page initially, it attaches this listener to those 10 individual DOM Elements. If we add a new js-delete-rep-log
element later, there will be no listener attached to it. So when we click delete, nothing happens! So, what's the fix?
If you're like me, you've probably fixed this in a really crappy way before. Back then, after dynamically adding something to my page, I would manually try to attach whatever listeners it needed. This is SUPER error prone and annoying!
Your New Best Friend: Delegate Selectors
But there's a much, much, much better way. AND, it comes with a fancy name: a delegate selector. Here's the idea, instead of attaching the listener to DOM elements that might be dynamically added to the page later, attach the listener to an element that will always be on the page. In our case, we know that this.$wrapper
will always be on the page.
Here's how it looks: instead of saying this.$wrapper.find()
, use this.$wrapper.on()
to attach the listener to the wrapper:
// ... lines 1 - 2 | |
(function(window, $) { | |
window.RepLogApp = function ($wrapper) { | |
// ... lines 5 - 7 | |
this.$wrapper.on( | |
'click', | |
// ... line 10 | |
this.handleRepLogDelete.bind(this) | |
); | |
// ... lines 13 - 22 | |
}; | |
// ... lines 24 - 100 | |
})(window, jQuery); |
Then, add an extra second argument, which is the selector for the element that you truly want to react to:
// ... lines 1 - 2 | |
(function(window, $) { | |
window.RepLogApp = function ($wrapper) { | |
// ... lines 5 - 7 | |
this.$wrapper.on( | |
'click', | |
'.js-delete-rep-log', | |
this.handleRepLogDelete.bind(this) | |
); | |
// ... lines 13 - 22 | |
}; | |
// ... lines 24 - 100 | |
})(window, jQuery); |
That's it! This works exactly the same as before. It just says:
Whenever a click event bubbles up to
$wrapper
, please check to see if any elements inside of it with ajs-delete-rep-log
were also clicked. If they were, fire this function! And have a great day!
You know what else! When it calls handleRepLogDelete
, the e.currentTarget
is still the same as before: it will be the js-delete-rep-log
link element. So all our code still works!
Ah, this is sweet! So let's use delegate selectors everywhere. Get rid of the .find()
and add the selector as the second argument:
// ... lines 1 - 2 | |
(function(window, $) { | |
window.RepLogApp = function ($wrapper) { | |
// ... lines 5 - 12 | |
this.$wrapper.on( | |
'click', | |
'tbody tr', | |
this.handleRowClick.bind(this) | |
); | |
this.$wrapper.on( | |
'submit', | |
'.js-new-rep-log-form', | |
this.handleNewFormSubmit.bind(this) | |
); | |
}; | |
// ... lines 24 - 100 | |
})(window, jQuery); |
To make sure this isn't one big elaborate lie, head back and refresh! Add a new rep log to the page... and delete it! It works! And we can also submit the form again without refreshing!
So always use delegate selectors: they just make your life easy. And since we designed our RepLogApp
object around a $wrapper
element, there was no work to get this rocking.
i have been wondering this whole time the leaderboard doesn't update when i add the weight. Do you need to make a whole function for it?
Have a nice day!