Wes Tyler
Wes Tyler

Reputation: 140

Javascript HTML .children traversal

I'm trying to go through all of the elements in the document and pull the ones with a target class name. Importantly, I'm needing to do it without the use of document.getElementsByClassName(className) / document.querySelectorAll, etc. — that's the point of this learning exercise.

Here's the javascript:

var getElementsByClassName = function(className){
  var rootElem = document.body;
  var collectionResult = [];
  if (rootElem.getAttribute("class") == className) {
    collectionResult.push(rootElem);
  };
  var nextTier = function(collectionResult, rootElem) {
    var thisTier = rootElem.children;
    for (i=0; i<thisTier.length; i++) {
      var classes = thisTier[i].getAttribute("class");
      if (classes != undefined && classes.includes(className)) {
        collectionResult.push(thisTier[i]);
      };
      var childrenArray = thisTier[i].children; 
      if (childrenArray.length > 0) {
        nextTier(collectionresult, childrenArray)
      };
    };
  };
  nextTier(collectionResult, rootElem);
  return collectionResult;
};

Here's the section of the HTML structure I'm having trouble with:

<p>
  <div class="somediv">
    <div class="innerdiv">
      <span class="targetClassName">yay</span>
    </div>
   </div>
 </p>

The code works for the rest of the page with any number of non-nested elements. But as soon as var childrenArray = thisTier[i].children get to the div.somediv element, it has childrenArray == undefined rather than pulling the div.innerdiv element.

Am I misunderstanding how element.children works?

Upvotes: 2

Views: 112

Answers (2)

Mulan
Mulan

Reputation: 135187

Array.prototype.flatMap is an effective tool for flattening trees (like DOM) to an array of values (like a list of elements) -

function getElementsByClassName (node, query) {   
  function matchAll (children) {
    return Array
      .from(children)
      .flatMap(c => getElementsByClassName(c, query))
  }
  if (node.classList && node.classList.contains(query))
    return [ node, ...matchAll(node.childNodes) ]
  else
    return matchAll(node.childNodes)
}

const result =
  getElementsByClassName(document, "targetClassName")
  
console.log(result)

// [ <div class="somediv targetClassName">…</div>
// , <span class="targetClassName">yay1</span>
// , <span class="targetClassName">yay2</span>
// , <span class="targetClassName">yay3</span>
// ]
<div class="somediv targetClassName">
  <div class="innerdiv">
    <span class="targetClassName">yay1</span>
  </div>
</div>
<div class="somediv">
  <div class="innerdiv">
    <span class="targetClassName">yay2</span>
  </div>
</div>
<div class="somediv">
  <div class="innerdiv">
    <span class="targetClassName">yay3</span>
  </div>
</div>

Upvotes: 2

Oriol
Oriol

Reputation: 287950

You seem to be overcomplicating things.

function getElementsByClassName(className, root) {
  if(!root) root = document.documentElement;
  return [].reduce.call(root.children, function(arr, child) {
    if(child.classList.contains(className)) arr.push(child);
    return arr.concat(getElementsByClassName(className, child))
  }, []);
}

function getElementsByClassName(className, root) {
  if(!root) root = document.documentElement;
  return [].reduce.call(root.children, function(arr, child) {
    if(child.classList.contains(className)) arr.push(child);
    return arr.concat(getElementsByClassName(className, child))
  }, []);
}
console.log(getElementsByClassName("targetClassName"));
<div class="somediv targetClassName">
  <div class="innerdiv">
    <span class="targetClassName">yay1</span>
  </div>
</div>
<div class="somediv targetClassName">
  <div class="innerdiv targetClassName">
    <span class="targetClassName">yay2</span>
  </div>
</div>
<div class="somediv">
  <div class="innerdiv">
    <span class="targetClassName">yay3</span>
  </div>
</div>

Upvotes: 1

Related Questions