Check out a free preview of the full Accessibility in JavaScript Applications course
The "Progressive Enhancement Solution" Lesson is part of the full, Accessibility in JavaScript Applications course featured in this preview video. Here's what you'd learn in this lesson:
Marcy walks through the vanilla.js solution to the exercise.
Transcript from the "Progressive Enhancement Solution" Lesson
[00:00:00]
>> Marcy Sutton: I'm gonna go over the Vanilla JavaScript example and then we'll take a little bit of a break. So, this tab switcher, I'll leave this one, close this. So this example is just like the other one, it's got the arrow key navigation, I can cycle through these tab switchers, it's got Aria in it, added dynamically.
[00:00:26]
So this tab switcher I went and did some cleanup on the CSS classes cuz originally I didn't even write this to be thinking about progressive [LAUGH] enhancement. It's good to think about it first, but so I cleaned those up, I did add this no-js class. And in the CSS I believe, yeah, so here are the bottom I have this no-js, if that class is present because JavaScript hasn't removed it that's where I can go and play with the style.
[00:00:57]
So I just hid the tabs, like if it's not interactive what are the tabs there for, really? Maybe you might need it for heading purposes to match the content but depending if the tabs are all in a row and the tab panels are all in a row then that will make sense.
[00:01:14]
So depending on how your tab switcher is marked up, you might need to change that, but I just hid them all. And then the tab-content I have a display here just turning those so that all of them are back on. I did experiment playing with the target, the target selector in the browser, so that I think, yeah, I don't know if I actually [LAUGH] finished this.
[00:01:41]
But the idea was that if I do use these links that I could actually match the target in the URL to a region in the page, the target selector, and CSS does that, but we could go look at the JavaScript for this. So using Vanilla JavaScript, I added a document.querySelector, go and find that no-js class and remove it.
[00:02:04]
So find that element access its class list and remove no-js. And then I'm doing the scripting to actually bind this as a tab switcher. So going and finding the tabs using querySelector all, the tab panels, creating an array from the tabs. So when you use querySelector all, I don't know if anyone else has run into this, but it returns you a node list and not an array.
[00:02:29]
Huge pain for a long time, and the quickest, easiest thing I found is to just convert it to an array with array.from. That requires support for array.from, you might need a poly fill or you can just use a for-loop and iterate over them too, but since I have Babel turned on here in Code pen, I just went for it.
[00:02:50]
So then I iterate over these tabs and go and add a role of tab, the aria-controls that's a state, Aria control state, and I point it to each tab panel, so go and just grab the ID off of the tab panels. I'm removing these so to sort of help me out in dynamically binding these.
[00:03:15]
I didn't have any Aria on here cuz I didn't want screen readers to encounter this and be like, what is this controlling, it's not interactive? So I used a data attribute instead but that's a method you could use. Like if you safely want to kind of store attributes for later in your mark up before JavaScript takes over you could use data attributes.
[00:03:36]
The tab panels I'm going through each of those just since there's a one to one, there's three tabs, three tab panels. When I'm iterating over the tabs, I can go and add a role of tab panel to each one, and then I've got some code to go and get the current tab.
[00:03:53]
So starting up here, store a variable of which tab index is the active one, I start at 0. I have a kind of reusable function here to get the reference from the tab panels, or sorry, the tabs. Each one has an href, this is were that target selector came in because the href of each tab.
[00:04:21]
I believe these were bottoms originally and I converted them the links so that I could used that internal linking so that when I click on a link I send it to the ID that matches that href, the internal link. And so I can use that to get a reference between the tab and then its associated tab panel.
[00:04:42]
Let's see, let's go back down to the bottom. So for each tab, each one has a click listener, because I'm sort of like progressively layering on top, I'm overriding what the link would normally do, and so I'm preventing its default action, and I'm using that to change the tab.
[00:05:00]
So when I iterate over each tab I know which index it is cuz I'm iterating over a collection. I can go and add, I'm gonna change this to keyup based on Amy's great suggestions earlier. So I've got a keyup listener and I can change keyCode to key, I don't know, actually remember what the stream names are so you probably wanna change the deprecated key code to something more modern.
[00:05:25]
[LAUGH] Just logging the event object when you interact with it you can go and play with that and that might come up a little bit layer in our testing section. But then I have these reusable functions of a prevArrowkeyHandler and a nextArrowkeyHandler and I pass it which tab index I'm currently on.
[00:05:43]
So that way I can keep a reference of like which one was current last, and I can increment and decrement. So the prevArrowkeyHandler checks if the current index is greater than 0 cuz I don't wanna get an error trying to left arrow off the screen [LAUGH] somewhere it doesn't exist.
[00:06:00]
So if the index is greater than 0 I can go and get the tab that matches the index to the left. And I will send focus to it, so that I'm in the right place. The changeTab function just goes and unsets the tabs each time, so it goes and removes an active CSS class, that's what changes the display, it changes the aria-selected state.
[00:06:25]
So this is one of those where adding it when JavaScript loads is helpful because then screen readers won't be like what's selected? Why can't I change it? Why is this on here? So I can add that dynamically and then manage it like turning it on and off. And then, yeah, just kind of, I just go unset them that was the easiest way to do that.
[00:06:45]
And then for the currentTab I go and add aria-selected if true, give it an active class on both the tab and the tab panel. Set the currentTab index to the one that the handlers have decided is the one. So a little bit of like conditional logic to check that we're in bounds here.
[00:07:05]
So that's what I ended up doing for this one. So that's how you can use JavaScript to kind of like turn things on and off. And it does sort of go against some of the conventional wisdom of overwriting elements. Like I'm starting with an anchor and adding, like I'm stopping its default action, and I'm adding a role on top of that, so it might feel a little gross.
[00:07:27]
But what that means is that without JavaScript, that anchor will function like an anchor and I can use that. So maybe that's, for progressive enhancement purposes, that might be the right choice to make. Just know that if you get pushed back or something just try and communicate your motivation and the reasons why you would be doing this and you can come to a maybe even better solution together.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops