getElementsByClassName

Search all children of targetElement for all elements with classNames

by rustyjeans

HTML

<div id="target">
  <div class="a b c">
    <div class="b c">1</div>
    <div class="c a">2</div>
    <div class="a b">3</div>
  </div>
  <div class="d e f">
    <div class="b c">4</div>
    <div class="c a">
      <div class="b c">5</div>
      <div class="c a">6</div>
      <div class="a b">7</div>
    </div>
    <div class="a b">8</div>
  </div>
</div>

CSS

div {
  border: 1px solid #000;
  margin: 10px;
  padding: 20px;
}

TypeScript

/**
   * Search all children of targetElement for all elements with classNames
   * @param {HTMLElement} targetElement 
   * @param {string} classNames - Class names seperated by spaces
   */
const getElementsByClassName = (targetElement, classNames) => {
  // check that the traget element is an HTMLElement
  if (!targetElement instanceof HTMLElement) {
    throw new Error("target element not an instance of HTMLElement");
  }

  // validate that className is a string
  if (typeof classNames !== 'string') {
    throw new Error('classNames must be a string')
  }

  // store class names in an array by spliting classNames by space
  const classNamesArr = classNames.trim().split(/\s+/);

  const _recursiveGetElementsByClassName = (targetElement, classNamesArr) => {
    let returnElements = [];
    // check each child to see if it has the class names provided
    targetElement.childNodes.forEach(childNode => {
      // if node type is an element
      if (childNode.nodeType === Node.ELEMENT_NODE) {
        let matched = true;

        // store child class name is hash for faster access
        let childClass = childNode.className.trim().split(/\s+/).reduce((hash, value) => {
          hash[value] = true;
          return hash;
        }, {});

        // compare all input classNames to see if they have all of them
        for (let i = 0; i < classNamesArr.length; i++) {
          if (!childClass[classNamesArr[i]]) {
            matched = false;
            break;
          }
        }
        if (matched) {
          returnElements.push(childNode);
        }

        // call this function on all child nodes
        if (childNode.hasChildNodes()) {
          returnElements = [...returnElements, ..._recursiveGetElementsByClassName(childNode, classNamesArr)];
        }
      }
    });
    return returnElements;
  }

  return _recursiveGetElementsByClassName(targetElement, classNamesArr);
};

console.log("a b", getElementsByClassName(document.getElementById("target"), "a...