🛠Carousel: Refactoring the dots part
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')
})
const 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.
const 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)
})
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
.
const showHideArrowButtons = _ => {
// ...
}
We start building showHideArrowButtons
by copy-pasting all the code we need into it:
const 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:
clickedDotIndex
dots
previousButton
nextButton
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
.
const 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.