HTML & CSS Structure for the Tab Panel
So in this video, we're going to be building a tab panel using jQuery, similar to the ones you've seen around the internet. Basically, I want to be able to click on these different tabs and see different kinds of content. So here's how you would lay that out in the HTML. I've kind of got this whole thing which is my tab panels, and then I've built a UL with an LI, and that is these panels up here, kind of like you would a navigation on a web page.
And then I've got a bunch of divs here, each with an ID of 1, 2, 3, 4. And so I've set up a couple CSS rules here. Is by default, any div with a class of panel is going to be hidden. But then if I also add a class of active, then it's going to have—it goes from display: none to display: block. So here's my tab-panels .panel is display: none, and then tab-panels .panel.active is display: block. So the one that has active is the one that'll get seen.
I've kind of done the same thing for my LI here is whichever one has the class of active is a darker color. Also, anything you hover on gets a darker color. So if I add that class over here, then panel two looks like it's the one that's selected.
So now I need to write the jQuery that when I click on this, it's going to move the active class to the correct one, and it's going to find the correct one of these. It's going to hide the one that's shown now, and then it's going to show the one that I want to show, and it's going to add the active class to it. So let's go ahead, get into jQuery and make this happen.
Setting Up the jQuery Click Event
So I'm going to come down here at the bottom of my page where my JavaScript is loaded. You see I'm loading jQuery, and then after jQuery—it's important that it's after—I'm going to start doing my JavaScript, which, you know, usually you'll want in your own JavaScript file, but for the sake of this example, we're just going to do it right here.
I'm going to do a document ready function, which you should know by now. Basically that just waits till the whole HTML document is loaded before we start doing any jQuery. And so then what I'm going to do is I'm going to listen for clicks on these guys. So let me see... that's .tab-panels ul.tabs lis. Okay.
So I'm going to go .tab-panels .tabs li, on('click', ...)
And so then one way that's easy for me, especially when you're starting out with JavaScript, I'll do comments for what I know needs to happen:
- Figure out which panel to show.
- Hide current panel.
And these can happen probably in a couple different orders.
- Show new panel.
So that's kind of what I need to do here. Let's figure out how to do this.
Linking Tabs to Panels with Attributes
I need to figure out by clicking on an LI, I need to know which div I'm going to show. So I'm going to add an a here rel="panel1". So that way I know that to look for ID number one, ID panel1, and we'll get that guy. rel="panel2". rel equals... If you're not going to use rel, then you can come up with anything you want. You can go data-panel-id or something like that. You just need to make sure you start it off with data-. But rel is one of the HTML attributes that you can use in a case like this.
So now I just need to grab that rel attribute, and I know that when I click on this guy, that I need to grab panel number one, which will be this guy. So let's go this. If you remember from other tutorials I've done, whenever something gets clicked on or whenever an action takes place, this is the thing that got clicked on. So it's basically the same as this selector, or in this case, there's multiple tab LIs. So it's the tab LI that got the click event. So I'm going to go over there.
panelToShow = this.attr('rel');
So now I should be grabbing that. Let me alert that just so we can kind of test this here. So now I click panel1... this should say panel2... panel2. Yep. Excellent. So now I know I need to find the ID of panelToShow. So let me go ahead and hide the current panel. That will be easy.
Animating Panels with Callbacks
I'm just going to go
$('.tab-panels .panel.active')
Just like I would in CSS.
.hide()
I'll actually make it .slideUp(300). So there we go. So now whenever I click, yep, the existing one slides up. So no matter which button I click, the existing one slides up. Great. That's a good start. And I also... I accidentally removed my class="active" for my first one. There you go. We want to make sure we start off on panel one.
So that first one is done. Great. Now I need to show the new panel, but I only want to show it after this one's done sliding up. So I can add a function. Almost anything you can do in jQuery, you can add a function to the end of it, which is what's called our callback, which means that it's going to wait 'til it has slid up for 300 milliseconds and then it's going to fire our function, which is great.
So if I just show you an alert here, you're going to notice it'll wait for 300 milliseconds while the other slides up and then it's going to fire my alert. Slide up, and then alert. There we go. I'll show you again. Slide up, then alert. So that's what's called a callback. JavaScript can be running something and it can know what to do, but wait for the right moment to do it. That's called a callback.
Chaining Callbacks and Managing State
So, um, once I have slidden up my old one, then I'm going to go: it's an ID, so I'm going to start with that, and then I'm going to go panelToShow and then this will slideDown(300). So once that's done sliding up, it's going to fire this function, which is going to find the ID of panelToShow and it's going to slide it down. So let's see if this works. Great. And you'll notice that it doesn't know how to keep going because it doesn't... I didn't change my class now, so it doesn't know which one to slide up.
So I need to add another callback function. Once it's slid down all the way, let's addClass('active'). So now this no longer is the LI that was clicked. This is in a callback function to panelToShow. So this is panelToShow. This is one of the most confusing things about JavaScript and especially jQuery, is when you start getting callbacks and then callbacks within callbacks, you get what the developers, I guess, like to call callback hell, where it just becomes very messy very quickly.
So by the end of this video, we're going to have that corrected. But for now, here's what's going on. The LI got clicked, so we're running all the code within that function. Anywhere in this code, this refers to the LI that got clicked. So now we're going to run, we're going to find the active panel and slide it up, and we have a callback function for the slideUp. So anywhere inside of this function, if I were to do this right now, this would be the .panel.active that we selected, if that makes sense. So this would no longer refer to the LI tab. That's one thing that people very quickly get confused on in jQuery. You have to know where you are because this will change based off of what's going on.
And so now we're going to find the panelToShow, slide it down, and now this refers to panelToShow because it's happening inside of that. So once the panelToShow has slidden down all the way, we're going to add the class active to our panel. So that'll get us most of the way there. We're almost done.
So now it slides down, now we have the active, and now you can still see it's doing some weird stuff. I'm not really sure what's going on. It's kind of acting weird. And you know what's going on is, we're never removing the active class from the one that we're sliding up. So the first thing we want to do here then is go this.removeClass. If you haven't gotten completely lost, this is our panel, we're done, it's complete, we're finished.
So here's kind of how we had to do it: we had to figure out which panel to show, that's the easy part. And then we have to slide this one up, and as soon as it's slid up, we're going to remove the class of active. This is pointing to this, the active panel that we've selected. And then once we've removed our active class, we're going to slide down our new one, and once it's all the way done, then we're going to fire this function and add the class of active to it. So this goes up, active class is gone, the next one comes down, and then the active class gets added.
Finalizing Tab Navigation
So one more thing we have to do is you notice our panel isn't changing either. That's a very easy thing to do. We're just going to go this.addClass('active'). So now this gets the active class. And we also want to find the li that has the active class and we're going to want to remove that first. There we go.
So whenever we click on the LI, the first thing we do is we find the one that's active, remove that class, and then add that class to me, and then go and do the rest of our panel. Let's see what we got. There we go. It's working just fine. Working just fine. Working just fine. Okay. So here's our functioning panel. But there's a problem here.
Refactoring to Avoid "Callback Hell"
We have some call, once again, it's called callback hell. That's kind of actually an official term. Whenever callbacks get beyond one or two levels, they call it callback hell. I'm not trying to offend anyone, but that's just what it's called. So we want to try to get rid of a little of this. So an easy way to do that is to go function, let's actually name this function showNextPanel. And now what I can do is I can cut that out and I can put that function down here. So now I have function showNextPanel. We can call this at any point in time now. So instead of a callback, a written out callback here, I'm just going to go showNextPanel.
So now it's a little cleaner looking. So now I'm going to hide current panel and then I'm going to show the next panel. That's a lot cleaner. So now we only have one level of callbacks going on that we're visibly seeing. And you know what, I'm going to leave that there. I don't think this is detailed enough for me to have to pull this function out into its own function. So I'm gonna leave that there. Let's verify everything works just as it should. Everything still looks great.
The Reusability Problem: Global Selectors
Okay, one more problem here is we are referencing on('click'). We're doing this search right here, and we're doing this search right here. This is bad for two reasons. One, every time you click, we're having to search the entire web page to find any tab panels with a class of active. And they're having to do the whole thing again right here. Every time you run a jQuery selector like this, just like you would use CSS, it's having to search the whole web page to find your stuff. And so that can actually, it can slow down your application, but it can also cause some problems. Let me show you why that is.
I'm going to go ahead and copy and paste. Let's add another tab panel to our page, another completely different tab panel. And so this one has panel 5, 6, 7, 8. 5, 6, 7, 8. And you notice the IDs are different: 5, 6, 7, 8. The rels are all different. And so now when we run this listener, it's going to listen to all eight tab panels. And, oh no, it is hiding our active tab panel no matter where it shows up. So when I click here, it's going to find any tab panels that are active and it's going to remove the class. So you'll notice this will now look like it's turned off and this will now go away. Okay, that's not okay.
So what we need to do is in order to scale this a—it's called scaling, when something has to move beyond its initial intention into a more reusable intention—we actually have to make it smarter. We have to have it only operate within the panel that was clicked on, within the tab panel that was clicked.
So here's what we're going to do. We're actually going to go and... let's go ahead and our first order of business will be to find out which panel we are in. So we're going to go this. So whichever LI was clicked, we're going to go .closest('.tab-panels'). So what that's going to do is, I'll come back up here to my HTML, is whenever you click on an LI, it's going to find the closest .tab-panels that it's a part of, and that's going to be what we use for the rest of our plugin. So here we go.
panel is the closest tab panel. So now we know which tab panels we're working on. And so now what we can do is we can go panel.find() and we don't need to find tab-panels because we're already within a tab panel. We're going to find .tabs li.active within the panel that we're part of right now. And let's do the same thing here: panel.find(). And we can remove the tab panel that we're part of.
So now we're just searching within our existing panel to find any li.actives, make those changes. And now we're going to find any .panel.actives. Here's how you know you've done it right, is we are not seeing any of these selectors going on inside of our function. We're only seeing this selectors, which means that we already know which one we're working with and we're not doing anything beyond this. So this is the only specific one we're doing because we already know which specific panel to go to. Let's see if that fixed it. Excellent, that fixed it just fine.
And kind of one thing that's a good rule to do is whenever a variable is referencing a jQuery selector, which is what we're doing here, it's a good idea to put a dollar in front of it. That way you kind of know this thing represents a jQuery selector. It's not just any old variable. So that's how we did it. Hopefully that made sense to you.
Final Code Recap
I tried not to go into too much detail, and I tried to not avoid detail. So feel free to re-watch it a couple times if you feel like we went through it too quickly.
Once again, let's do the final recap on what's going on. We're listening to any li, any .tab-panels .tabs li that get clicked. As soon as they get clicked, we're going to do find our closest tab panel and save it to this, so we know what panel we're working with. And so then we're going to start doing some things. We're going to find whichever tab, whichever li is active, and we're going to turn that active off. And then we're going to add active to whichever one got clicked.
Then we're going to find out which panel to show by listening, by finding the rel to the li that got clicked. That is, once again, right here. If you clicked on this li, we're going to grab this value out of it by looking at the rel attribute. So, where was... okay, there we go.
We found out panelToShow, and then we're going to hide the current panel. So we're going to find within our existing set of panels, we're going to find a panel with an active class. We're going to slide that up, and once that's done, we're going to show the next panel. Showing the next panel is a two-part thing where we are removing the active class from the panel that we're talking to, which is this, 'cause this is happening on this action right here. I know that's what gets a little bit confusing about JavaScript. You learn to think it, but at first it's pretty confusing. You're like, everything is this, how do I know what this is? That's one of the things you start running into when you start learning JavaScript is this.
And so we've removed the active class from the panel that got hidden. We're going to use panelToShow, and we're going to slide that down and we're going to add the active class once it's done. So there you go. That's how to build a tab panel in jQuery. Hope you enjoyed the lesson.