selectors-api

querySelector to querySelectorAll


Hi I want to make this code work for each element in the page but it only works for the first one. I can't make querySelectorAll() work. Only the first button works and the others don't. Thanks to all.

const button = document.querySelector('#like');
const counter = '#counter';
let state = false;
let like = 0;

writeCounter(like, counter);
setState(state, button);

function writeCounter(number, id) {
  const elm = button.querySelector(id);
  elm.innerHTML = number;
}

function setState(state, elm) {
  if (state) {
    elm.classList.remove('dislike');
    elm.classList.add('active');
  } else {
    elm.classList.remove('active');
    elm.classList.add('dislike');
  }
}

function addLike() {
  if (!state) {
    like++;
    state = true;
    writeCounter(like, counter);
    setState(state, button);
  } else {
    like--;
    state = false;
    writeCounter(like, counter);
    setState(state, button);
  }
}

button.addEventListener('click', addLike);
.button__like {
  border: none;
  border-radius: 1rem;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: row;
  cursor: pointer;
  transition: 150ms all;
  background-color: #ffffff;
  width: 6rem;

}

button.dislike .icon svg.prime {
  -webkit-animation: dislike 550ms forwards ease-in-out;
  animation: dislike 550ms forwards ease-in-out;
}

button.active {
  background-color: #c55151;
}

button.active .counter {
  color: #ffffff;
}

button.active .icon svg {
  stroke-width: 0;
  color: #ffffff;
}

button.active .icon svg.prime {
  -webkit-animation: like 550ms forwards ease-in-out;
  animation: like 550ms forwards ease-in-out;
}

button .counter {
  font-size: 1.8rem;
  font-weight: 700;
  color: var(--primary-color);
  transition: 150ms all;
  padding: 0.5rem 0;
}

button .icon {
  padding: 1rem;
  transition: 150ms all;
  position: relative;
  width: 2.5rem;
  height: 2rem;

}

button .icon svg {
  color: rgb(255, 255, 255);
  position: absolute;
  top: 0;
  left: 0;
  width: 2rem;
  margin-left: 0.5rem;
  stroke-width: 60;
  stroke: var(--primary-color);
}

button .icon svg.normal {
  z-index: 1;
  opacity: 0.5;

}

button .icon svg.prime {
  z-index: 2;

}

@-webkit-keyframes dislike {
  0% {
    color: #ff0000;
    transform: translate(0, 0%);
  }

  100% {
    color: #ffffff;
    transform: translate(0, 300%) scale(0);
  }
}

@keyframes dislike {
  0% {
    color: #ff0000;
    transform: translate(0, 0%);
  }

  100% {
    color: #ffffff;
    transform: translate(0, 300%) scale(0);
  }
}

@-webkit-keyframes like {
  0% {
    color: white;
    transform: translate(0, 0%);
  }

  100% {
    color: #ff0101;
    transform: translate(0, -300%) scale(0);
  }
}

@keyframes like {
  0% {
    color: white;
    transform: translate(0, 0%);
  }

  100% {
    color: #f60000;
    transform: translate(0, -300%) scale(0);
  }
}
<button class="button__like" id="like">
                    <div class="icon">

                        <svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                            <path  fill="currentColor"
                                d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
                            </path>
                        </svg>
                        
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
                          <path  fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
                          </path></svg>

                    </div>
                    <div class="counter" id="counter"></div>
                </button>


Solution

  • Example 1

    Using Event Delegation (recommended)

    Event delegation is a programming paradigm that allows us to assign just one element to control an unlimited amount of elements even if added dynamically afterwards. Details are commented in the example.

    Example 2

    Using .querySelectorAll() (as requested)

    Using document.querySelectorAll("button") creates a NodeList of all of the <button>s. Using the spread operator (...) in the NodeList converts it into a real array which is needed since we'll be using .indexOf() to find out the index in the array that corresponds to the <button> the user clicked.

    Concerning Both Examples

    Note: The CSS still needed to be changed because it originally animated when the page loaded which looks confusing. Moreover the <button> is placed at the lowest point in the stacking order making it clickable only to the small space between .counter and the right border.

    Concerning Example 2

    The original JavaScript relied on #id which isn't used when dealing with multiple elements due to the fact that #ids must be unique so if you were to use unique #ids (ex. #like1, #like2, etc.) you'd be severely limiting future functionality and unnecessarily complicating your code. So as a compromise everything is determined by the index number within the buttons array. At least with that layout you won't have to modify anything if you add or delete <button>s before the page loads (in Example 1 the number of <button>s can change at any time). Basically changing the JavaScript and CSS is unavoidable due to its flawed design.

    Example 1

    Using Event Delegation (recommended)

    /**
     * Register a common ancestor element (eg <main>)
     * to listen for all "click" events that fire
     * on itself and any of its children elements.
     * This strategy is part of "Event Delegation".
     * When a "click" event happens on <main>, the
     * event handler toggleState() is called.
     */
    const main = document.querySelector("main")
    main.addEventListener("click", toggleState);
    
    /**
     * Handles "click" events by delegating them only
     * to any <button> that is a child of <main>.
     * By acting only when the user clicks a <button>
     * that's within <main> and ignoring every other
     * element is the most important step in "Event
     * Delegation".
     * @param {object} event - Event object
     */
    /**
     * All event handlers pass the Event object by
     * default.
     */
    function toggleState(event) {
      /**
       * Get the actual element the user clicked by
       * using Event objects property: .target
       */
      const clicked = event.target;
      // If the user clicked a <button>...
      if (clicked.matches("button")) {
        // reference .counter within this <button>...
        const counter = clicked.querySelector(".counter");
        // if this <button> has class .initial...
        if (clicked.matches(".initial")) {
          /**
           * add class .like and remove class .initial
           * and change .counter text to: "1"...
           */
          clicked.classList.add("like");
          clicked.classList.remove("initial");
          counter.textContent = "1";
          // otherwise if this <button> has class .like...
        } else if (clicked.matches(".like")) {
          /**
           * add class .dislike and remove class
           * .like
           * and change .counter text to "0"...
           */
          clicked.classList.add(".dislike");
          clicked.classList.remove("like");
          counter.textContent = "0";
          // otherwise...
        } else {
          /**
           * add class .like and remove class .dislike
           * and change .counter text to: "1"
           */
          clicked.classList.add("like");
          clicked.classList.remove("dislike");
          counter.textContent = "1";
        }
      }
    };
    /* All --webkit properties have been removed since
     * all of the applicable properties are baseline.
     */
    
    /* To avoid the "dislike" animation from running
     * when the page loads, the transition  property 
     * was removed.
     */
    button {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 6rem;
      border: none;
      border-radius: 1rem;
      background-color: #fff;
      cursor: pointer;
    }
    
    /* All of the children elements of <button> were 
     * interfering with clicks. So the easiest way
     * to bypass the stacking order, 
     * pointer-events: none was assigned to all children
     * elements of <button>.
     */
    button * {
      pointer-events: none;
    }
    
    /* Initiate animation when class .dislike is
     * assigned to <button>
     */
    button.dislike {
      transition: 150ms all;
    }
    
    /* Default "dislike" animation is assigned to 
     * <button> as long as it doesn't have class
     * .initial. Note "dislike" animation is overridden
     * by "like" animation (when button.like)
     */
    button:not(.initial) .icon svg.prime {
      animation: dislike 550ms forwards ease-in-out;
    }
    
    /* .active class is now class .like (it makes
     * more sense).
     */
    
    button.like {
      background-color: #c55151;
    }
    
    button.like .counter {
      color: #fff;
    }
    
    button.like .icon svg {
      color: #fff;
      stroke-width: 0;
    }
    
    button.like .icon svg.prime {
      animation: like 550ms forwards ease-in-out;
    }
    
    /* The remaining styles are unaltered */
    button .counter {
      padding: 0.5rem 0;
      font-size: 1.8rem;
      font-weight: 700;
      color: var(--primary-color);
      transition: 150ms all;
    }
    
    button .icon {
      position: relative;
      width: 2.5rem;
      height: 2rem;
      padding: 1rem;
      transition: 150ms all;
    }
    
    button .icon svg {
      position: absolute;
      top: 0;
      left: 0;
      width: 2rem;
      margin-left: 0.5rem;
      color: #fff;
      stroke-width: 60;
      stroke: var(--primary-color);
    }
    
    button .icon svg.normal {
      z-index: 1;
      opacity: 0.5;
    }
    
    button .icon svg.prime {
      z-index: 2;
    }
    
    @keyframes dislike {
      0% {
        color: #ff0000;
        transform: translate(0, 0%);
      }
    
      100% {
        color: #fff;
        transform: translate(0, 300%) scale(0);
      }
    }
    
    @keyframes like {
      0% {
        color: #fff;
        transform: translate(0, 0%);
      }
    
      100% {
        color: #f60000;
        transform: translate(0, -300%) scale(0);
      }
    }
    <!-- Everything is wrapped in <main> -->
    <!-- All #ids were removed because there are
         multiple targets to click. Since all #ids
         must be unique, it would be problematic to
         keep track of multiple #ids. -->
    <!-- Each <button> is assigned a class: .initial. 
         .initial represents a neutral state without
         any animation. -->
    <main>
      <button class="initial">
    <div class="icon">
    <svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
    </path>
    </svg>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
    </path></svg>
    </div>
    <div class="counter">0</div>
    </button>
    
      <button class="initial">
    <div class="icon">
    <svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
    </path>
    </svg>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
    </path></svg>
    </div>
    <div class="counter">0</div>
    </button>
    
      <button class="initial">
    <div class="icon">
    <svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
    </path>
    </svg>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
    </path></svg>
    </div>
    <div class="counter">0</div>
    </button>
    </main>

    Example 2

    Using querySelectorAll() (as requested)

    // This is for demonstration purposes only
    const log = data => console.log(JSON.stringify(data));
    
    // Collect all <button> into an array
    const buttons = [...document.querySelectorAll("button")];
    
    /**
     * Create an array that has a 0 for each <button>
     * 0 = dislike, 1 = like, initially all are
     * 0 but .dislike isn't assigned yet.
     */
    let states = Array(buttons.length).fill(0);
    log("Initial state of each button");
    log(states);
    
    /**
     * Initially, each <button> is registered to the
     * "click" event.
     * and writeCounter() is called for each <button>
     */
    buttons.forEach((btn, idx) => {
      btn.addEventListener('click', addLike);
      writeCounter(idx);
    });
    
    /**
     * With a given index number, the correct .counter
     * will display the current value of 0 or 1
     * according to its index in the states array
     * @param {number} index - Current index number
     */
    function writeCounter(index) {
      const elm = buttons[index];
      elm.querySelector(".counter").textContent = states[index];
    }
    
    /**
     * With a given index number, the corresponding
     * <button> will be assigned classes according
     * to its value in the states array and if
     * it's at its initial state.
     * @param {number} index - Current index number
     */
    function setState(index) {
      const elm = buttons[index];
      if (elm.matches(".initial")) {
        elm.classList.remove('initial');
        elm.classList.add('like');
      } else if (states[index] === 1) {
        elm.classList.remove('dislike');
        elm.classList.add('like');
      } else {
        elm.classList.remove('like');
        elm.classList.add('dislike');
      }
    }
    
    /**
     * Handles the "click" event on a <button>. 
     * @param {object} event - Event object
     */
    function addLike(event) {
      // Determine which <button> the user clicked
      const clicked = event.target;
      // Determine the index number of clicked <button>
      const index = buttons.indexOf(clicked);
      // Toggle the value of states array at index
      if (states[index] === 0) {
        states[index] = 1;
      } else {
        states[index] = 0;
      }
      log("State of each button");
      log(states);
      // Call setState() and writeCounter()
      setState(index);
      writeCounter(index);
    }
    /* For demonstration purposes only */
    .as-console-wrapper { left: auto!important; top: 0; width: 60%; }
    
    
    /* All --webkit properties have been removed since
     * all of the applicable properties are baseline.
     */
    
    /* To avoid the "dislike" animation from running
     * when the page loads, the transition  property 
     * was removed.
     */
    button {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 6rem;
      border: none;
      border-radius: 1rem;
      background-color: #fff;
      cursor: pointer;
    }
    
    /* All of the children elements of <button> were 
     * interfering with clicks. So the easiest way
     * to bypass the stacking order, 
     * pointer-events: none was assigned to all children
     * elements of <button>.
     */
    button * {
      pointer-events: none;
    }
    
    /* Initiate animation when class .dislike is
     * assigned to <button>
     */
    button.dislike {
      transition: 150ms all;
    }
    
    /* Default "dislike" animation is assigned to 
     * <button> as long as it doesn't have class
     * .initial. Note "dislike" animation is overridden
     * by "like" animation (when button.like)
     */
    button:not(.initial) .icon svg.prime {
      animation: dislike 550ms forwards ease-in-out;
    }
    
    /* .active class is now class .like (it makes
     * more sense).
     */
    
    button.like {
      background-color: #c55151;
    }
    
    button.like .counter {
      color: #fff;
    }
    
    button.like .icon svg {
      color: #fff;
      stroke-width: 0;
    }
    
    button.like .icon svg.prime {
      animation: like 550ms forwards ease-in-out;
    }
    
    /* The remaining styles are unaltered */
    button .counter {
      padding: 0.5rem 0;
      font-size: 1.8rem;
      font-weight: 700;
      color: var(--primary-color);
      transition: 150ms all;
    }
    
    button .icon {
      position: relative;
      width: 2.5rem;
      height: 2rem;
      padding: 1rem;
      transition: 150ms all;
    }
    
    button .icon svg {
      position: absolute;
      top: 0;
      left: 0;
      width: 2rem;
      margin-left: 0.5rem;
      color: #fff;
      stroke-width: 60;
      stroke: var(--primary-color);
    }
    
    button .icon svg.normal {
      z-index: 1;
      opacity: 0.5;
    }
    
    button .icon svg.prime {
      z-index: 2;
    }
    
    @keyframes dislike {
      0% {
        color: #ff0000;
        transform: translate(0, 0%);
      }
    
      100% {
        color: #fff;
        transform: translate(0, 300%) scale(0);
      }
    }
    
    @keyframes like {
      0% {
        color: #fff;
        transform: translate(0, 0%);
      }
    
      100% {
        color: #f60000;
        transform: translate(0, -300%) scale(0);
      }
    }
    <!-- .initial class was added to each <button> -->
    <button class="initial">
    <div class="icon">
    <svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
    </path>
    </svg>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
    </path></svg>
    </div>
    <div class="counter"></div>
    </button>
    
    <button class="initial">
    <div class="icon">
    <svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
    </path>
    </svg>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
    </path></svg>
    </div>
    <div class="counter"></div>
    </button>
    
    <button class="initial">
    <div class="icon">
    <svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
    </path>
    </svg>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    <path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
    </path></svg>
    </div>
    <div class="counter"></div>
    </button>