🛠 Carousel: Refactoring the dots part

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

🛠 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:

  1. Decides whether to act on the dots
  2. Gets the variables we need to switch slides (dot, clickedDotIndex, slideToShow)
  3. Highlights the clicked dot
  4. 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)
})

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.

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:

  1. clickedDotIndex
  2. dots
  3. previousButton
  4. 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:

  1. Switch slides
  2. Highlight the correct dot
  3. 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.