How to convert a jQuery function over to JavaScript?

91
September 12, 2021, at 11:10 AM

I'm working on switching some things from jQuery over to vanilla JS. I'm having trouble with some of the methods. I think I have some of them, but some of them not.

jQuery

$(".control-ct .arrow").on("click", function () {
  if ($(this).is("#left-arrow")) {
    $("#testimonialSlides .customer-block.active")
      .removeClass("active")
      .prev(".customer-block")
      .add("#testimonialSlides .customer-block:last")
      .first()
      .addClass("active");
  } else {
    $("#testimonialSlides .customer-block.active")
      .removeClass("active")
      .next(".customer-block")
      .add("#testimonialSlides .customer-block:first")
      .last()
      .addClass("active");
  }
});

JavaScript

let control = document.querySelector('.control-ct .arrow');
control.addEventListener('click', function() {
  let activeSlides = document.querySelector('#testimonialSlides .customer-block.active');
  if (control.matches('#left-arrow')) {
    activeSlides.classList.remove('active')
    activeSlides.prevElementSibling('.customer-block')
    activeSlides.add('#testimonialSlides .customer-block:last')
    activeSlides.at(0)
    activeSlides.classList.add('active');
  } else {
    activeSlides.classList.remove('active')
    activeSlides.nextElementSibling('.customer-block')
    activeSlides.add('#testimonialSlides .customer-block:first')
    activeSlides.at(-1)
    activeSlides.classList.add('active');
  }
});

let control = document.querySelector('.control-ct .arrow');
control.addEventListener('click', function() {
  let activeSlides = document.querySelector('#testimonialSlides .customer-block.active');
  if (control.matches('#left-arrow')) {
    activeSlides.classList.remove('active')
    activeSlides.prevElementSibling('.customer-block')
    activeSlides.add('#testimonialSlides .customer-block:last')
    activeSlides.at(0)
    activeSlides.classList.add('active');
  } else {
    activeSlides.classList.remove('active')
    activeSlides.nextElementSibling('.customer-block')
    activeSlides.add('#testimonialSlides .customer-block:first')
    activeSlides.at(-1)
    activeSlides.classList.add('active');
  }
});
#testimonialSlides {
  position: relative;
}
.testimonials-2020 {
  padding-top: 256px;
  padding-bottom: 128px;
}
.testimonials-2020 .container-fluid {
  background: #f2f5f9;
}
.testimonials-2020 .customer-block {
  visibility: hidden;
  opacity: 0;
  display: none;
}
.testimonials-2020 .customer-block.active {
  visibility: visible;
  opacity: 1;
  display: flex;
}
.testimonials-2020 .img-ct {
  position: relative;
}
.testimonials-2020 .img-ct img {
  position: absolute;
  bottom: -70px;
  right: 0;
  display: block;
}
.testimonials-2020 .txt-ct {
  padding: 80px 40px 30px 0;
}
.quote-slider h3 {
  max-width: 500px;
}
.quote-slider p.quote {
  font-size: 28px;
  line-height: 40px;
  margin: 36px 0 24px;
  min-height: 96px;
}
.quote-slider p.quote-small {
  margin: 36px 0 24px;
  padding-right: 48px;
  font-size: 20px;
  line-height: 32px;
}
.quote-slider p.quote span {
  color: #005fec;
  font-size: inherit;
}
.quote-slider p {
  font-size: 16px;
  line-height: 24px;
}
.quote-slider p.name {
  font-weight: 700;
}
.testimonials-2020 .control-ct {
  padding-bottom: 48px;
}
.testimonials-2020 .control-ct #right-arrow {
  margin-left: 22px;
}
.testimonials-2020 .control-ct .arrow {
  cursor: pointer;
}
.testimonials-2020 .control-ct .arrow g {
  transition: all 0.2s ease;
}
.testimonials-2020 .control-ct .arrow:hover g {
  stroke: #005fec;
}
<section class="testimonials-2020">
  <div class="container-fluid no-pad">
    <div class="container no-pad">
      <div class="quote-slider" id="testimonialSlides">
        <div class="row customer-block justify-content-center" data-customer-quote="01">
          <h2>One</h2>
        </div>
        <div class="row customer-block justify-content-center" data-customer-quote="02">
          <h2>Two</h2>
        </div>
        <div class="row customer-block justify-content-center" data-customer-quote="03">
          <h2>Three</h2>
        </div>
        <div class="control-ct">
          <svg id="left-arrow" class="arrow" fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
            <g stroke="#000a70" stroke-width="2.5">
              <path d="m9 1-7 7 7 7"></path>
              <path d="m2.5 8h13.5"></path>
            </g>
          </svg><svg id="right-arrow" class="arrow" fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
            <g stroke="#000A70" stroke-width="2.5">
              <path d="m1 8h13.5"></path>
              <path d="m8 15 7-7-7-7"></path>
            </g>
          </svg>
        </div>
      </div>
    </div>
  </div>
</section>

Answer 1

The issues I can see there are:

  1. The jQuery code hooks up handlers on all matching elements. Your code using querySelector only hooks up the first element. You need to use querySelectorAll and a loop to do what your jQuery code was doing.

  2. jQuery's .prev(".customer-block") (and .next(...)) gives you a set with the immediately-previous element of each element in the set you call it on if they match the selector. Your code is trying to use the previousElementSibling as a function, but it's an element, not a function, and there's o DOM equivalent of "give me the previous or null if it doesn't match this selector."

  3. Your selectors are still using :last and :first, which are things added by jQuery, not native to the DOM.

Here's a take on addressing those:

// Loop through all matching elements
for (const control of document.querySelectorAll(".control-ct .arrow")) {
    // Handle click on this element
    control.addEventListener("click", function() {
        // Get the active slide
        let activeSlide = document.querySelector("#testimonialSlides .customer-block.active");
        if (control.id === "left-arrow") { // No need for `.matches` here
            // This slide is no longer active
            activeSlide.classList.remove("active");
            // Get the previous element
            let prev = activeSlide.previousElementSibling;
            // If there is none or it doesn't match, use the last instead
            if (!prev || !prev.classList.contains("customer-block")) {
                // No previous slide, use the last instead
                const slides = document.querySelectorAll("#testimonialSlides .customer-block");
                prev = slides[slides.length - 1];
            }
            // Make it active
            prev.classList.add("active");
        } else {
            // This slide is no longer active
            activeSlide.classList.remove("active");
            // Get the next element
            let next = activeSlide.nextElementSibling;
            // If there is none or it doesn't match, use the first instead
            if (!next || !next.classList.contains("customer-block")) {
                // `querySelector` only returns the first match, not a list like `querySelectorAll` does
                next = document.querySelector("#testimonialSlides .customer-block");
            }
            next.classList.add("active");
        }
    });
}

Or actually, though, I think we can simplify the prev/next part:

// Loop through all matching elements
for (const control of document.querySelectorAll(".control-ct .arrow")) {
    // Handle click on this element
    control.addEventListener("click", function() {
        // Get all slides and the active slide
        const slides = [...document.querySelectorAll("#testimonialSlides .customer-block")];
        // Get the active slide's index
        let index = slides.findIndex(slide => slide.classList.contains("active"));
        // This slide is no longer active
        slides[index].classList.remove("active");
        // Make the new one active via its index (with wrap-around)
        index = (index + (control.id === "left-arrow" ? -1 : 1) + slides.length) % slides.length;
        slides[index].classList.add("active");
    });
}

Live Example:

// Loop through all matching elements
for (const control of document.querySelectorAll(".control-ct .arrow")) {
    // Handle click on this element
    control.addEventListener("click", function() {
        // Get all slides and the active slide
        const slides = [...document.querySelectorAll("#testimonialSlides .customer-block")];
        // Get the active slide's index
        let index = slides.findIndex(slide => slide.classList.contains("active"));
        // This slide is no longer active
        slides[index].classList.remove("active");
        // Make the new one active via its index (with wrap-around)
        index = (index + (control.id === "left-arrow" ? -1 : 1) + slides.length) % slides.length;
        slides[index].classList.add("active");
    });
}
.customer-block {
    display: inline-block;
    width: 20px;
    text-align: center;
    color: grey;
    border: 1px solid transparent;
}
.customer-block.active {
    font-weight: bold;
    color: green;
    border: 1px solid green;
}
<div class="control-ct">
    <input class="arrow" type="button" id="left-arrow" value="&lt;">
    <input class="arrow" type="button" id="right-arrow" value="&gt;">
</div>
<div id="testimonialSlides">
    <div class="customer-block active">1</div>
    <div class="customer-block">2</div>
    <div class="customer-block">3</div>
    <div class="customer-block">4</div>
    <div class="customer-block">5</div>
</div>

Rent Charter Buses Company
READ ALSO
How to find USB device port in Linux?

How to find USB device port in Linux?

I have a USB device connected to my computer, it is this one:

86
Handle click outside using useRef crashes

Handle click outside using useRef crashes

I am having mobile version of my navbar, which you can trigger with normal burger menu and I would like to close it just with touching the screen outside of folded navbar

95
Node JS - How to implement a GET request with OAuth 1 flow in node JS

Node JS - How to implement a GET request with OAuth 1 flow in node JS

I want to implement GET request with OAuth 1 flow

108
Android TV - Switch to HDMI input programatically

Android TV - Switch to HDMI input programatically

Is there a way to switch to an HDMI input programmatically on Android TV? Ie

184