The dots part looks complicated. First, let’s examine what we wrote:
dotsContainer.addEventListener('click', event => { const dot = event.target.closest('button') if (!dot) return
// Show slide const clickedDotIndex = dots.findIndex(d => d === dot) const slideToShow = slides[clickedDotIndex] const destination = getComputedStyle(slideToShow).left
contents.style.transform = `translateX(-${destination})` slides.forEach(slide => { slide.classList.remove('is-selected') }) slideToShow.classList.add('is-selected')
// Highlight dot dots.forEach(d => { d.classList.remove('is-selected') }) dot.classList.add('is-selected')
// Show / hide buttons if (clickedDotIndex === 0) { previousButton.setAttribute('hidden', true) nextButton.removeAttribute('hidden') } else if (clickedDotIndex === dots.length - 1) { previousButton.removeAttribute('hidden') nextButton.setAttribute('hidden', true) } else { previousButton.removeAttribute('hidden') nextButton.removeAttribute('hidden') }})This code looks complicated and overwhelming. But if you look closely, it does four things:
- Decides whether to act on the dots
- Gets the variables we need to switch slides (
dot,clickedDotIndex,slideToShow) - Highlights the clicked dot
- Show/hide previous and next buttons
Deciding whether to act on the dots
The first two lines of this event listener checks whether we should act. We act only if the user clicks on a dot. We do nothing if the user doesn’t click on a dot.
This two lines should remain in the event listener.
dotsContainer.addEventListener('click', event => { const dot = event.target.closest('button') if (!dot) return
// ...})Gets the variables
The next two lines tells us what’s the target slide and target dots. They are essential to the event listener, and they should remain where they are.
dotsContainer.addEventListener('click', event => { // ... const clickedDotIndex = dots.findIndex(d => d === dot) const slideToShow = slides[clickedDotIndex]})Showing the slides
The next four lines lets us change slides. They’re very similar to switchSlides.
dotsContainer.addEventListener('click', event => { // ... const destination = getComputedStyle(slideToShow).left contents.style.transform = `translateX(-${destination})` slides.forEach(slide => { slide.classList.remove('is-selected') }) slideToShow.classList.add('is-selected')})function switchSlide(currentSlide, targetSlide) { const destination = getComputedStyle(targetSlide).left contents.style.transform = `translateX(-${destination})` currentSlide.classList.remove('is-selected') targetSlide.classList.add('is-selected')}The major difference is we loop and removed is-selected from all slides in dotsContainer’s event listener, but we only removed is-selected from currentSlide in switchSlide.
Both versions work. You can pick either one and they’ll work fine. (Theoretically, the looping code takes more work, but the real difference in performance is negligible).
In this case, I choose to use the non-looping version. This means I need to find currentSlide in dotsContainer’s event listener. I can use querySelector to find the current slide.
dotsContainer.addEventListener('click', event => { // ... const currentSlide = contents.querySelector('.is-selected') const clickedDotIndex = dots.findIndex(d => d === dot) const slideToShow = slides[clickedDotIndex]
const destination = getComputedStyle(slideToShow).left contents.style.transform = `translateX(-${destination})` slides.forEach(slide => { slide.classList.remove('is-selected') }) slideToShow.classList.add('is-selected')
// ...})Since I have currentSlide, I can use switchSlide in dotsContainer’s event listener:
dotsContainer.addEventListener('click', event => { const currentSlide = contents.querySelector('.is-selected') const clickedDotIndex = dots.findIndex(d => d === dot) const slideToShow = slides[clickedDotIndex]
switchSlide(currentSlide, slideToShow)
// ...})Highlight dots
In the next two lines, we highlighted the dots by removing the is-selected class from all dots, then adding the is-selected class back to the clicked dot.
dotsContainer.addEventListener('click', event => { // ... // Highlight dot dots.forEach(d => { d.classList.remove('is-selected') }) dot.classList.add('is-selected')})These two lines are similar to hightlightDot. Again, the difference is we removed is-selected from every dot in the dotsContainer version.
function highlightDot(currentDot, targetDot) { currentDot.classList.remove('is-selected') targetDot.classList.add('is-selected')}Since we chose to use the non-looping version for slides, we should also use the non-looping version for dots. This keeps things consistent.
We can get the selected dot with querySelector.
dotsContainer.addEventListener('click', event => { // ... const currentDot = dotsContainer.querySelector('.is-selected')})And we can highlight the correct dot this way:
dotsContainer.addEventListener('click', event => { // ... hightlightDot(currentDot, dot)})Showing/hiding previous and next buttons
Here’s the code we used to show/hide previous and next buttons.
dotsContainer.addEventListener('click', event => { // ... // Show / hide buttons if (clickedDotIndex === 0) { previousButton.setAttribute('hidden', true) nextButton.removeAttribute('hidden') } else if (clickedDotIndex === dots.length - 1) { previousButton.removeAttribute('hidden') nextButton.setAttribute('hidden', true) } else { previousButton.removeAttribute('hidden') nextButton.removeAttribute('hidden') }})As always, this code is imperative. We have to look through the code to understand what it’s doing.
We can simplify the code by putting this whole show/hide chunk into a function. Let’s call this function showHideArrowButtons.
function showHideArrowButtons() { // ...}We start building showHideArrowButtons by copy-pasting all the code we need into it:
function showHideArrowButtons() { if (clickedDotIndex === 0) { previousButton.setAttribute('hidden', true) nextButton.removeAttribute('hidden') } else if (clickedDotIndex === dots.length - 1) { previousButton.removeAttribute('hidden') nextButton.setAttribute('hidden', true) } else { previousButton.removeAttribute('hidden') nextButton.removeAttribute('hidden') }}Here, you can see we need four variables:
clickedDotIndexdotspreviousButtonnextButton
dots, previousButton, and nextButton are used in many areas for the component. I suggest we reuse them as much as possible. These variables should be declared before the functions.
const dots = ...const previousButton = ...const nextButton = ...
// Functions// ...The only variable we need in showHideArrowButtons is clickedDotIndex.
function showHideArrowButtons(clickedDotIndex) { if (clickedDotIndex === 0) { previousButton.setAttribute('hidden', true) nextButton.removeAttribute('hidden') } else if (clickedDotIndex === dots.length - 1) { previousButton.removeAttribute('hidden') nextButton.setAttribute('hidden', true) } else { previousButton.removeAttribute('hidden') nextButton.removeAttribute('hidden') }}Using showHideArrowButtons:
dotsContainer.addEventListener('click', event => { // ... showHideArrowButtons(clickedDotIndex)})A quick summary
For all three event listeners, we had to write code to do three things:
- Switch slides
- Highlight the correct dot
- Show/hide arrow buttons
We’ve already refactored all three event listeners to use the same code for switching slides and highlighting the correct dot. But don’t have a common function to show/hide arrow buttons yet.
That’s what we’ll deal with in the next lesson.
Welcome! Unfortunately, you don’t have access to this lesson. To get access, please purchase the course or enroll in Magical Dev School.
Unlock this lesson