Introduction & Project Goals
All right, here we go with our third and final jQuery Ajax tutorial. Our app is in the place where we can now load up all the orders when we arrive and we can add new orders. Lauren wants a latte. There we go. So we can add new orders, we can refresh our app, and our orders exist because they're coming from the back end.
So what we need to do now is we need to add a delete button so we can remove orders, and we also want to fix this right here. This is kind of a messy, hacky way of piecing together a string in order for us to build out a template. It works totally fine with something this small, but if we want to add three or four elements and a lot more markup, that's going to be messy really, really fast. So we've got to do a better way of doing that. We're going to use the Mustache.js templating engine. So let's go and add Mustache.js onto our page.
Basic Templating with Mustache.js
Done, that's easy. And let's go ahead and create our template. All a template is is a string, and so we can go <li>... name. And then what you'll do is double curly braces anytime you want to insert a value. So instead of doing order.name, we're going to pass order into our template and we can do name right there. So it's going to look in order for the value of name and spit it out right there. And so that would be the same as order.drink. So that's our template for what we have right now. Let me close that <li> tag.
And let's go ahead, instead of doing this, we're going to go mustache.render. And you pass it two things: you pass it your template and then your object of values. So we're going to go orderTemplate and then we're going to go order, which is our object right here which has all our values in it. Let's go ahead and refresh and it should look exactly the same. Excellent, no change at all.
But what we've done now is we've given ourselves the ability to add a lot more complicated templating to this. And so what you can actually do is a sneaky way, if you have a template that's not too big and bad, you can build it by adding, you know, a bunch of plus signs. And as long as you start off with an empty string, you can do new line breaks. So I'm going to go <li>. I'm going to go <strong>. I'll actually do a paragraph.
<p><strong>Name</strong>: {{name}}</p>
There you go. And I'll add some more, do another paragraph. <strong>... let's capitalize those. And I'm also going to add a remove button. And this is actually a pretty annoying way to do a template this big. There are many, there's run plugins, there's a lot of ways to make a better template that's in an actual HTML file and import it in and use Mustache to render it, but for now this will, this will have to do for our example.
And let's do a button. And we're going to go data-id... we want to print the ID and I'll show you why in a little bit. {{id}}. There we go. Let's put an 'x' here in this button. We could style that a lot better with CSS, but we don't have to for this video. And let's give it a class of remove. Just call it 'remove'. So there we go. This should work fine now. And now we have our larger order, and we have our 'x' button, which CSS should put over there or something, but whatever, it's good for now. And when we click this, we want it to remove it.
So let's go ahead and look at our back-end. And it says to delete an order, just send a DELETE request to /api/orders/ and then your ID of your order that you're wanting to delete. So that's why we're printing this ID here. Let's look at what HTML we're actually creating, is we're adding a paragraph tag with name, paragraph tag with that, and then a button with the ID of three and a class of remove.
Wiring the Delete Functionality & The Binding Problem
So now we can do click, or we can do .on('click'). I like to do .on('click'), I feel like it's more consistent. On our click function, we simply want to do Ajax: type is DELETE, and then our url is going to be /api/orders/ and then plus... now I can get the attribute out, data-id. I think I was calling it, yep, data-id. I can grab the data-id. So this will be, if I click here on ID number one, it'll be /api/orders/1, so that's grabbing that attribute out.
And this will not work, and I'm going to tell you why. So here we go. I'm going to do this and, uh oh, something's broken. I probably have something bad in my code somewhere. Unexpected token on line 58. Oh yeah, there we go. Got that.
Understanding Event Delegation for Dynamic Elements
Okay, so this button will not fire any requests. Let me show you. Let's go to the Network tab. Click this all I want, and no requests are firing. Why are no requests firing? This is one of the most complicated things to figure out if you're new to JavaScript and jQuery, is I have the selector, right? I have this .remove selector. Correct. But when I'm firing this, when I'm running this JavaScript, there have not been any things added to my page yet, because my page loads up with no orders.
My page loads up, it starts a GET request that's in process. Let's go get the orders. Remember I said it's asynchronous JavaScript and XML. It's asynchronous, meaning it doesn't block your code. So it starts that GET request and then it starts these other things going. And then it looks for all the .remove items and it tells it to listen to those, but there's no .remove items added yet. Those are going to get added 3 or 4 milliseconds later on this success function.
So how do we make it listen to the remove items that aren't there yet? What we do is we listen to a click on remove items within the parent container. So my parent container is my orders ul, and I've already cached that right here, orders equals that. So what I can do is instead of listening to remove, I can listen to orders. And instead of on, I'm going to go delegate. delegate('.remove', 'click', ...). So this is going to listen to any click events on the orders thing, and it's going to only fire if it's a part of a .remove class. It's going to listen for any clicks on any remove elements to come, both now or in the future, and it's going to fire them all. Now our JavaScript will work. If I refresh, let's refresh. There's my GET request. This should fire a DELETE request. There we go, a DELETE request to /api/orders/1. And now this is still there, but if I refresh my page, my order is gone. Awesome. So now what we need to do is on success of that, we need to remove that element.
Updating the UI After a Successful Deletion
Okay, so you'll notice a couple of my items are gone. It's because I had a code error and I needed to fix it real quick. But what we need to do now is we need to delete these items from the list once our DELETE request is successful. So we need to hit the 'x', it makes the successful delete request to the back end, and now we need to remove this from the list so we don't have to refresh our page to see our changes.
So what we need to do is we need to, on success, we need to get the parent <li> from this button that was clicked. So I can store that here. this.closest('li'). So that's going to start going up the chain till it finds an <li>. It's going to be this button, it's going to go up, and it's going to grab it really quick. So I've stored that. And now on success, I can go li.remove(). Let's see if that works. Refresh. Yay, it's gone and it's gone. Let's add a new order... let's add that. Let's add a few more... coffees. Refresh, make sure they're there. Make sure they all go away. It's gone. Let's refresh. It's still gone. It's gone and it's gone.
Let's actually make that a little cleaner. Let's go li.fadeOut(300) and then on callback to that fade out, let's remove it. There we go. Let's do that. Add some orders... again. Ah, the fade out. Ah, the fade out. Much better. So there we go. We have built a complete application now.
Key Concept: this Context in Ajax Callbacks
A few things that I need to specify that are very, very, very important is you cannot use this inside of a success function. Let me tell you what I mean. Is see how I did this.closest('li')? If I did this.closest('li') in here, that would not do anything because this no longer refers to the remove button that was clicked. this now refers to the Ajax operation that is in progress.
So what I actually have to do here is I have to grab the li up here. I have to refer to this when I'm inside of this function, and now I can pull the li here. If that made sense... I have no way of knowing which element I'm in when I'm inside of this, when I'm inside of the success function. I cannot use this to find out the element that was clicked. I have to use this when I'm in here, and now I can store it as a variable, and then I can access it later on.
If you need to use this inside of success, the way you do that is you go var self = this;. And so now I'm saving a reference to self calling it this, and now I can do self. So it's accessing a variable, it's going to look up my scope and find a variable. If it looks for this, this already exists inside of the Ajax item so it's going to pull the wrong this. If I do self, there's no such thing called self yet, so it's going to start looking up my scope and it's going to find something called self which refers to this. I know that was a tongue twister for you, but that's why I did it the way I did it right there. It's a scoping issue. It's one of the trickier issues of JavaScript. I really need to do a whole video on it, but this is about Ajax, so I'll leave you where we are. Have a great day and good luck with Ajaxing.