 Hi, my name is Ben Allman, and I'm a senior trainer and developer at Boku. Today we're gonna look at some jQuery event handlers and a few different ways we can optimize them using event delegation and lazy evaluation. This material is actually adapted from my comprehensive jQuery class, and we should just get right into things. So let's take a look at the example I have here. I've got eight buttons. They're awesome. Well, you can't tell that yet until I actually start interacting with them. When you click these buttons, we've got a little jQuery code on them that looks at the current number in the button, decreases it by one, updates that in the button, and then when you hit zero, actually disables the button and unbinds all event handlers on it. So you can see all the buttons behave exactly the same across the buttons. So the code in the jQuery code is really kind of generic. So if we go to our page that doesn't have the jQuery in it yet, we'll see that they're not awesome because when you click one of them, and actually, well, their button's in a form, so they just submit the form normally. So when I click this three, it's not gonna go to two. It's gonna submit the form, which is gonna cause a query string to be added to the URL. And if there were any variables set in the page, etc., they would be completely erased because we're on a new page. The page gets reloaded at that point. So this isn't good. So we want to add some functionality to these event handlers, but the very first thing we need to do is actually prevent the default action so that when you click the button, it doesn't submit the form and go to a new page. So let's take a look at some source code. First of all, if we look at the form here, you can see that we've got a bunch of buttons. Each button's content has this click's remaining, the spam with a class of number and a number inside of it. And so the idea is, you know, we're gonna look at whenever each one of these buttons is clicked, we're gonna grab that value from the number span, decrease one, update it, and then if we hit zero, we'll disable the button, etc. So let's look in some JavaScript. Our solution's gonna go here, okay. So we need to first prevent the default action. So what we'll do is we'll just select the buttons. So that will select all the buttons on the page. And for each one of those, we're gonna bind an event handler so that when they're clicked, we do something. So dot on, click, oops, that needs to be a dot. And we've got a function here. And so what you may have seen in the past is something like this, return false. Inside an event handler, if you return false, it prevents the default action. We see this all the time. And so if we load the page here and click these buttons, you'll see I'm clicking them and it doesn't actually submit the form. So this is a good start. What I want to point out, though, is that returning false is not really the way you want to do it because returning false not only prevents the default action, but it also stops event propagation, which when you're doing event delegation can cause you problems. So instead of return false, which is nice, but a little bit magical, we're gonna be a little more explicit and use the event object that's passed into every single event handler and call its prevent default method. So if we call event dot prevent default in here instead of return false, let's go back and reload. So it still works like we expect, but it's in a little bit less magical way. So just the general best practice is call the prevent default method of event object in your event handlers instead of returning false. So let's see, what do we have to actually do in our code? We need to get the value from the number span and subtract one from it. We need to update the number span. We need to, okay, so that will handle like when you click on it, it will update it. Then we need to, if the number is zero, we need to disable button and unbind event handler. Okay, so this is like, you know, we're taking a problem, we're breaking up into small pieces so that we can solve them individually. And so let's do it. Okay, so we need to get the value from the number span and subtract one. So if we take a look at our HTML, we know that inside of the button, the number span is literally just a child element. We're going to use a very generic selector here to select that by its class. That way, if someone were to change the HTML to like put that span inside of another span or change the span to like a B or an I or some other element, as long as it has a class of number, we'll still be able to get that. So we need to say like their number span equals, now we, this inside of event handler functions is the element on which the event happened. So it would be the button element, but we need to have a jQuery object so we can do jQuery things like find a descended element. So we need to pass this into the jQuery function that returns a jQuery object that we can call jQuery methods on. So this dot find dot number that will select all elements that have a class of number inside of the button that are descended elements. So this is great. We've actually found the number span. I could be a little more generic and just call it number lm. That way, if span gets changed to something else later on, we'll just have the right thing. And so that gets us the number element. And we all, we know there's only ever going to be one inside of there. So it's, you know, this is going to behave like we'd expect. So we need to get the value from the number span and subtract one. I don't want to just get its value and subtract one all in one step. I actually want to get the number element first, because then when I go to update that later on, I'll already have the element. I won't have to go and select it from the document again. So let's see, I'm going to need to get the actual value, their number equals number lm.txt. That will actually get me its text contents as a string. So it's not a number. So I could course it to a number by passing it in the number function, standard JavaScript here, and then I could subtract one. And so if the number element had the, had a five inside of it, that would be the string five, we'd be turning that into the number five, subtracting one from it, and we'd have the number four. Technically, you don't have to coerce it into a number by passing in a number because when you subtract one, JavaScript will automatically do that for you. But it's not a bad thing to do that. It's a little more explicit code. Either way, we're going to get the new number and we're going to update the number span. Now number lm.txt, the jQuery text method, both gets the value out of the DOM and sets it. So now that we've subtracted one from the number, we can update it in the DOM and see the change. So if I've done this correctly, I'll save it and I'll reload and I'll click. So we can see that we're going a little bit far, but it does actually work, right? So the logic is quite simple. We're getting the current number span or the number element, we're getting its current numeric value subtracting one, and then we're updating it with the new value. So that's like, we're halfway there. So if the number is zero, we need to disable the button and unbind the event handler. So okay, if the number zero, if number, too easy to type it, is zero. Now I'm using triple equals because in JavaScript that does a strict equality check, which is generally a best practice to use. So if that is the case, I want to disable the button and unbind the event handler. Well, let's see, I want to disable the button. Now I could do this again, I could do this dot, I'll use prop disabled and set it to true. That will actually set the JavaScript disabled property of that DOM element to true, thus disabling the button. I don't like that I'm selecting this multiple times. And if I was to unbind the event handler, let's look at I would be doing this again. I would say this dot off click. So I'm doing this this thing three times. And you know, honestly, it's usually easier if I just say bear button, store that in a variable, and then every single subsequent time, I can just replace those with the reference to button. And I won't have to go, you know, pass into the jQuery function again, less function calls means faster. So okay, this is pretty much done at this point. If the number zero disable it and unbind the event handler. Now I'm going to remove these comments, because to be honest, you should write comments when you're trying to disambiguate something that's a little complicated. This is not complicated code, you should just be able to read this and understand what's happening. If the number zero, I'm going to disable the button, I'm going to disable unbind the event handler. I could change this if I want to do both of those at the same time, but I find it's a little clearer to do it this way. So let me actually take a look, see if this works. We load the page four, three, two, one, zero. Hey, look it. It's disabled. Okay. So this is working. So this is a very simple solution to this problem. Now, one thing I don't like, if we're looking at optimizing our event handlers, one thing I don't like is that every single time someone clicks one of those buttons, let's see, this can't be avoided. But this, it's actually finding the number element every single time. Now, when you click at the second time, that the element that has a cluster number isn't changing, but yet you have to go into the document every time to find it. Not only that, but you have to go into the document to actually get that numeric value of the number. Well, after the first time you click it, you should know if it was the number five and we subtracted one and it's four, we know it's four, because we've already done that code. So why go get it from the document again? So we can actually optimize this by taking the code and structuring it a little bit differently. Instead of finding a click event handler to all the buttons, we're going to use this really cool pattern. I actually like to call the jQuery each pattern where we call each directly on the jQuery object. And so we say for each button, we're going to execute this code. And what's nice is we can actually, because this is a function that gets executed, we can store local variables in there. So for example, if I were to do this, move this right here, right up front, before I do anything else, I'm iterating over all the buttons and say for each button, I'm going to store a few variables and save them for later. I'm going to find the button element of the number element. I'm going to get its numeric value and I'm going to just compute that up front. And so let's actually take a look at this. I want to grab the contents of this, paste it in here, let me re-indent. And instead of saying button on click, well, we already get a reference to the button. And then we don't have to compute this stuff anymore. We do have to subtract one from the number. So if we say number equals number minus one, this will do the exact same thing that this does. You know, I'm going to put a return in here so that code doesn't actually execute. Okay. So when I reload it, it'll execute up to this point. It'll hit the return and it won't do anything after. So here up front, for each button, we're going to get a reference to the button, a reference to the number element right up front, we're going to get the current number. Then we're going to bind a click event handler. It says every time the button is clicked, we're going to prevent the default, subtract one from the number, update the number. And if the number is zero, disable the button and unbind the event handler. So what's kind of cool about that is that we don't have to do this stuff every single time. We just do it up front, which means that over the lifetime of your button clicking, it has to do less work. So let's just try it and see if it works. Hopefully it will. All right. So it still works. I didn't screw anything up. So this is great. Over the lifetime of your button clicking, your web page is doing less work. So that's great, but there is a downside to this optimization. The downside is that, yes, it's doing less work over the lifetime of your app. If someone clicks the button twice, then it only has to figure this stuff out once, just the first time. It actually figures it up front. So if they click it once, it still figures it out the same amount of times any more than that. It's great. The problem is that when you're writing a web page or a web application, you want to do as little upfront as possible because all that stuff that's happening up front, well, everything is happening up front and it really slows down your application's load time to have tons of stuff done up front. Let's look at this example. What if nobody ever clicks any button? Then if you have eight buttons on the page, this stuff is happening for all eight buttons all up front, even though they never click them. So while this is more efficient over the lifetime of the page, that doesn't really help us in this situation. This code down here, our original code is actually a little bit more practical because it doesn't do as much, it doesn't do anything up front other than buying the event handlers. So what I want to show is a technique we can actually combine the two of these things using a technique called lazy evaluation that does a very similar thing. I'll go here and I'll just copy this code up to the top again. So we'll edit this one. So instead of calculating this stuff up front the very first time, what do we really want to have happen? We want to set some stuff up up front so that we want to say the very first time they click the button, calculate that stuff, and then save it. And then every subsequent click, never calculate it again. That's called lazy evaluation. So all we have to do, it's so simple, right? I'm going to create a couple variables and I say it's so simple, right? It's so simple because I've done this a lot of times. Once you understand conceptually how it works, though, I think you'll get it. So we're just going to create a couple of variables. Those variables will eventually store those values. So here, when it's initialized, all it's doing is it's setting these variables up to be filled. It's binding an event handler, but it's doing nothing else. The very first time you click, we're going to say if, and then we're going to say if not number lm. This code will only ever execute in here if number lm hasn't been initialized because the first thing we're going to do is we're going to remove these bears. If number lm hasn't been initialized, then we're going to set it. We're going to say number lm is the number element, and we're going to initialize a number. And so let's look at how this is going to work. Up front, all the thing does is set up a couple variables and bind to click event handler. Then when you actually click it the first time, it's going to say, oh, well, number lm is undefined, so it'll execute this code. It'll set number lm and number, which don't get stored locally to this function, but because the variable specified outside it, they actually get stored here, which means they persist across multiple clicks, which means the second time you click, this is going to be an object, and it won't run this code. So it's a little bit more complicated, but what we've got here is code that doesn't do anything up front. And whenever you click one of the buttons, it only does the hard work, the stuff in the document that takes the most time, right? It only does that really inefficient stuff once up front and then never does it any subsequent times. So this is called Lee's evaluation. Yes, it's a little bit more code to write than this one, but it's smarter. Now in this example, it doesn't really matter. Super simple contrived example. And by the way, I should just double check and make sure this works. Okay, it still works. Good. It's a super simple contrived example, but you might find uses for this technique when your code starts to get more complicated. So I did promise that I would talk about event delegation because event delegation is a really great way to bind event handlers to less elements up front so that it's doing less work up front. So think about it this way. The current code that I've got right here, it says find every button and then for each button, bind these event handlers. Or if I was doing the lazy evaluation way, it says for each button, execute this code, which is doing a little more than binding an event handler to every single one, but still that if there's 50 buttons, it's binding 50 event handlers up front. It's iterating over all 50 buttons, which it has to find up front, et cetera. So instead of doing it the normal non-event delegation way, let's use event delegation. Instead of binding to each button, we could say something like, I've got all these buttons are used form. They're all in a form on click of a button. This is jQuery's event delegation syntax. It's the same thing except you specify the element that should, the selector that should match the element. And this is the one parent element. And all we're doing is binding a single click event handler to this element that says whenever any of, any descendant matching this selector is clicked, run this code. This is really great and it allows us to add buttons dynamically. So let's see if I, this should actually work as is. Let me load this. So it still works. I've done event delegation. And what's really cool about this, if I like open up my console and say like, I want to select all the buttons dot clone, duplicate them, append to form. Now I've already bound the event handler front. I'm not binding a new event handler because I used event delegation on the form. All the old buttons work and all the new buttons work too. I can add as many buttons as I want. I'm just going to clone some more and add them to the form. And you'll see that the new ones still work. So this is one of the great pluses of event delegation is you can add new content dynamically and all the behaviors will still stay the same without having to rebind event handlers. That's a lot of buttons. Okay. So this is great, but because we're not iterating over every single button, we can't use this technique with each to do our lazy evaluation. So how do we do lazy evaluation with event delegation? And so this is really the last thing that I'm going to talk about that wraps this all up. We can't use VAERS because we don't have a function event handler inside of another function with each. We just have the one function. Each doesn't make sense in this example. So what we're going to do is we're actually going to use jQuery data to do the same thing. jQuery data allows us to take any DOM element in the page or out of the page and associate arbitrary and arbitrary object with properties to that. So I could if I wanted to say their data equals actually want to duplicate this first so I can save a copy of it. So their data equals button dot data. And we'll you just give it a name. Like this is a multiple things can store data on each element. So we'll have a unique name countdown. And then I'll just I'll get that data. And so what I'm doing here without anything else I'm getting the button. So when you click a button, we're going to get its data. If that data is undefined, then we want to initialize it. So this is again our lazy evaluation thing. If not data, then we need to create a data object. And then this is these aren't variables, these are going to be properties of this object. Data equals object, we're going to take these guys right here. And it's saying save their, let's say, data dot number. So we're actually going to create a data object. If there is no data object, we're going to set two properties on it here. Now we need to access this isn't number lm number lm is no longer a variable, it's a property of this object. So we have to say data dot number lm dot text. And so now and then the last thing we have to do is actually update, set that. So here we're going to, if the data is undefined, which means we haven't set it there, we're going to create a data object, store some properties on it and actually store that in element. And then here the rest of our code is kind of simple. Instead of using the variables though, we have to use properties of the objects. So if data dot number, and then the button prop disabled true. Oh, you know what this is one thing I forgot to mention is that when we're doing event delegation, there is no click event handler to be bound on the button. We actually bound it on the form itself, right? So this doesn't really make sense to unbind an event handler and event delegation. If I were to unbind the event handler, well, there's nothing on the button and if I unbound on the form, then any new buttons added after the fact wouldn't have any functionality to them. So it doesn't make sense there. So we'll remove that from the event delegation, we'll remove it here. But what we should do is after we're all done counting down, we should probably do button dot remove data. And that will actually clean the data up for us. So let's look at it again. Upfront, nothing's happening. We're binding a single event handler to the form that says when one of its button descended elements is clicked, execute this code, prevent the default action. Okay, we're getting a reference to the button. We're going to look and see if that button has any data stored on it. If it doesn't, then it's going to initialize that to a new object, set a couple properties on it that we're going to reuse and then store that on the object. Then it's going to update the text. It's going to see that there's nothing to do right there and continue. And then the next time when you click it, it's going to look and get the data. It's actually going to be the object that you created the last time. So this won't execute. And then this stuff will behave as normal. So hopefully this works. Uh-oh, screwed something up. Let's take a look. Let's see. Oh, data dot number is not number minus one. It's a number. And then I'll do data dot number equal data number minus one. Okay, so we're going to store the number initially. And then only subsequent times are we going to subtract one from it. So let me try this again. So that works. And then if we go in here and add a bunch of buttons, those all work as well. So this is like, you know, it gets a little more complicated when you want to go from a very simple event handler bound to something using event delegation and lazy evaluation. But the technique is valid and it's not just for event handlers. You can use this in a lot of things. The whole idea is to do as little work upfront so that when your application loads, it is usable by the user as quickly as possible. jQuery only binds a single event handler to the form. And then it does nothing else until you click. And then when you do click, it only does stuff the very first time. It only touches the DOM this many, like this stuff up front. And then it does the absolute minimum that it needs to do subsequent times. So these are just a few options around optimizing jQuery event handlers using event delegation, lazy evaluation. I hope it's been useful. And oh, I have to fix one more thing. These buttons are totally awesome. All right. All right. Well, thanks a lot. I hope you've enjoyed it and see you next time.