const - JS | lectureAdvanced DOM and Events

Building a Carousel Component Part 2

Advanced DOM and Events

Let's start part 2 of our carousel component by attaching an event handler to a keyboard event so that we can also slide through the carousel using the left and right arrow keys. And remember that we handle keyboard events on the document object.

document.addEventListener('keydown', function (e) {
  e.key === 'ArrowLeft' && goToPreviousSlide();
  e.key === 'ArrowRight' && goToNextSlide();
});

Now let's work on the dots. In the HTML file, you will see that we have an empty div element in which we are going to create the dots.

<div class="dots"></div>

So , let's start by selecting that element.

const dotsContainer = document.querySelector('.dots');

Next le create a function to create the dots.

const createDotsHandler = function () {
  slides.forEach(function (_, index) {
    dotsContainer.insertAdjacentHTML(
      'beforeend',
      `<button class="dots__dot" data-slide="${index}"></button>`
    );
  });
};

The data attribute is use to store the index of the slide so that in the next step we can read its value and use it to move exactly to the slide that the dot is representing once it is clicked.

Let's now call the function to create the dots.

createDotsHandler();updateSlidesPositions();

dots

Let's now add some logic to the dots. We are once more going to use event delegation. So we are not going to attach an event handler to each dot but instead, to the common parent element.

dotsContainer.addEventListener('click', function (e) {
  // Matching strategy
  if (e.target.classList.contains('dots__dot')) {
    // Get the slide number from the data attribute
    const { slide } = e.target.dataset;
    // Move to the slide
    updateSlidesPositions(slide);
  }
});

Beautiful! It works! Now, all we need to do is activate the dot representing the current slide. This will be done by adding the class dots__dot--active to the dot. We will do this in another function because activating the dot will be a common task we will need to do in different places. For example, we must activate the dot representing the current slide whenever we move to the next or previous slide.

const updateActiveDot = function (slide = 0) {
  // Remove the active class from all the dots
  document
    .querySelectorAll('.dots__dot')
    .forEach((dotEl) => dotEl.classList.remove('dots__dot--active'));

  /**
   * Add the active class to the dot that has the data attribute
   * that matches the slide number
   */
  document
    .querySelector(`.dots__dot[data-slide="${slide}"]`)
    .classList.add('dots__dot--active');
};

Let's now call this function in all the necessary places.

// On first load
createDotsHandler();
updateSlidesPositions();
updateActiveDot();
// On next slide
const goToNextSlide = function () {
  //...
  updateActiveDot(currentSlide);};

// On previous slide
const goToPreviousSlide = function () {
  //...
  updateActiveDot(currentSlide);};

// On dot click
dotsContainer.addEventListener('click', function (e) {
  //...
  updateActiveDot(slide);});

Great! Now we have a fully functional carousel component. To finish below is the final code refactored.

const runCarousel = function () {
  // Selecting elements
  const slides = document.querySelectorAll('.slide');
  const carouselButtonLeft = document.querySelector('.carousel__btn--left');
  const carouselButtonRight = document.querySelector('.carousel__btn--right');
  const dotsContainer = document.querySelector('.dots');

  // Variable declarations
  const numberOfSlides = slides.length;
  let currentSlide = 0;

  // Functions
  const updateSlidesPositions = function (position = 0) {
    slides.forEach((slideEl, index) => {
      slideEl.style.transform = `translateX(${100 * (index - position)}%)`;
    });
  };

  const createDotsHandler = function () {
    slides.forEach(function (_, index) {
      dotsContainer.insertAdjacentHTML(
        'beforeend',
        `<button class="dots__dot" data-slide="${index}"></button>`
      );
    });
  };

  const updateActiveDot = function (slide = 0) {
    document
      .querySelectorAll('.dots__dot')
      .forEach((dotEl) => dotEl.classList.remove('dots__dot--active'));

    document
      .querySelector(`.dots__dot[data-slide="${slide}"]`)
      .classList.add('dots__dot--active');
  };

  const goToNextSlide = function () {
    if (currentSlide >= numberOfSlides - 1) {
      currentSlide = 0;
    } else {
      currentSlide++;
    }

    updateSlidesPositions(currentSlide);
    updateActiveDot(currentSlide);
  };

  const goToPreviousSlide = function () {
    if (currentSlide <= 0) {
      currentSlide = numberOfSlides - 1;
    } else {
      currentSlide--;
    }

    updateSlidesPositions(currentSlide);
    updateActiveDot(currentSlide);
  };

  const initializeCarousel = function () {
    createDotsHandler();
    updateSlidesPositions();
    updateActiveDot();
  };

  initializeCarousel();

  // Event handlers
  carouselButtonLeft.addEventListener('click', goToPreviousSlide);

  carouselButtonRight.addEventListener('click', goToNextSlide);

  document.addEventListener('keydown', function (e) {
    e.key === 'ArrowLeft' && goToPreviousSlide();
    e.key === 'ArrowRight' && goToNextSlide();
  });

  dotsContainer.addEventListener('click', function (e) {
    if (e.target.classList.contains('dots__dot')) {
      const { slide } = e.target.dataset;
      updateSlidesPositions(slide);
      updateActiveDot(slide);
    }
  });
};

runCarousel();