1

I am trying to write a getElementByClassName from scratch, but I am not sure when to return the recursion. That's what I have came up with:

  const getElementsByClassName = (nameOfClass, parent) => {

  const result = []
  for(let el of parent) {
       // console.log(el.children)
     if(el.firstElementChild != null) {
       // do it again on node deeper
        getElementsByClassName(nameOfClass, el.children)
     }

     if(el.className === nameOfClass) {
        result.push(el)
     }
   }
   // when do I want to return result?
   console.log(result)
   return result

};

The problem is that I have one array per child node, instead of everything in the same array result. How can I solve this?

Rafael Viana
  • 61
  • 1
  • 4
  • 1
    Shouldn't `getElementsByClassName(nameOfClass, el.children)` be pushed to array if it returns something? Instead of `.push` you will have to use `.concat`. `result = result.concat(...)` – Rajesh Nov 21 '19 at 11:17

3 Answers3

0

You could utilise the querySelectorAll like this:

const getElementsByClassName = (nameOfClass, ancestor) => (
 ancestor.querySelectorAll(`.${nameOfClass}`)
);

Or if you definately want a direct descendant of the parent. the es6 way:

const getElementsByClassName = (nameOfClass, parent) => (
 [...parent.querySelectorAll(`.${nameOfClass}`)].filter(item => item.parentElement === parent))
);

the javascript way

const getElementsByClassName = (nameOfClass, parent) => (
 Array.prototype.slice.call(parent.querySelectorAll(`.${nameOfClass}`)).filter(item => item.parentElement === parent))
);

It's a shame we can't use direct descendant with querySelectorAll. Otherwise this would be perfect:

const getElementsByClassName = (nameOfClass, parent) => (
 parent.querySelectorAll(`> .${nameOfClass}`)
);
Steve Tomlin
  • 3,391
  • 3
  • 31
  • 63
0

So you are trying to traverse the DOM tree by a recursive function!?

Off course each child comes with an own array of children. Otherways it wouldn't be a tree.

As you want to return an array of all matching elements you have to concatenize the result with the recursion call.

This could work:

const getElementsByClassName = (nameOfClass, parent) => {

  const result = []
  if (parent.className === nameOfClass) {
    result.push(parent);
  }
  for(let el of parent.children) {
    result = result.concat(getElementByClassName(nameOfClass, el));
   }

   return result
};

This implementation should only be taken for educational purposes as it has a large storage complexity.

Community
  • 1
  • 1
ParrapbanzZ
  • 252
  • 2
  • 8
  • The suggestion to avoid arrow functions here makes no sense. `const fact = (n) => n < 2 ? 1 : n * fact (n - 1)` is a recursive function implemented with an arrow. – Scott Sauyet Nov 22 '19 at 19:04
  • @ScottSauyet Thanks, you're right. The javascript engine gets it right. My concern was that the analysis of the lambda expression would fail because of the (at this point) unknown entity "getElementByClassName" as assignments are evaluated from right to left, hence the evaluation of the lambda expresssion before the assignment. That's not the case though, why I corrected my answer. – ParrapbanzZ Nov 23 '19 at 09:06
  • I think the point is that the body of the function isn't run until it's called, and by then the function name has been placed in scope. – Scott Sauyet Nov 25 '19 at 13:14
0

Something like this should do it:

const getElementsByClassName  = (name, el) => 
  [
    ... (el .classList .contains (name) ? [el] : []),
    ... ([... el .childNodes] .flatMap (c => getElementsByClassName (name, c)))
  ]

Logically this just says to combine the result of testing this node (using its classList) with the flatMapped recursive call to its children.

Live example:

const getElementsByClassName  = (name, el) => 
  [
    ... ([...(el .classList || [])] .includes (name) ? [el] : []),
    ... ([... el .childNodes] .flatMap (c => getElementsByClassName (name, c)))
  ]

console .log (
  getElementsByClassName ('foo', document).map(el => el.textContent)
)
.foo {background: yellow; font-weight: bold}
<div class = 'foo'>Foo</div>
<div class = 'bar'>Bar</div>
<div class = 'baz'>
  Baz
  <div class = 'baz qux'>
    Qux 
    <div class = 'foo baz qux'>Foo Baz Qux</div>
  </div>
  <div class = 'baz foo'>Baz Foo</div>
</div>

This may be oversimplified in the DOM walking, since not all child nodes are elements, but that's probably just a second check. This should show the idea.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103