The problem is that when you do parents.map(x => x.className)
you will get an array with the full class string for each element. So the result will look like (simplified) [ "child4 hello" ]
and you try to do indexOf("child4")
on that array. Since there is no member that is simply "child4"
, that fails.
Here is the problematic code illustrated:
function getParents (el, _class) {
let doc = document
let parents = []
let p = el.parentNode
while (p !== doc) {
let o = p
parents.push(o)
p = o.parentNode
}
parents.push(doc)
let mappedClassName = parents.map(x => x.className)
console.log("mappedClassName", mappedClassName)
let indexOf = mappedClassName.indexOf(_class);
console.log("indexOf", indexOf)
return parents[indexOf]
}
document.querySelectorAll('.child1').forEach((e,i)=>{
console.log(getParents(e, 'child4'))
})
<div class="parent">
<div class="child5">
<div class="child4 hello"> <!-- problem here -->
<div class="child3">
<div class="child2">
<div class="child1">
3
</div>
</div>
</div>
</div>
</div>
</div>
This code would work with a string:
let indexOf = "child4 hello".indexOf("child4");
console.log(indexOf);
However, it doesn't necessarily work correctly:
//the class is NOT child4
let indexOf = "child42 hello".indexOf("child4");
console.log(indexOf);
You should use Element.classList for a more accurate class check:
let div1 = document.getElementById("one");
let div2 = document.getElementById("two");
let _class = "child4";
console.log(`div1 has ${_class}`, div1.classList.contains(_class))
console.log(`div2 has ${_class}`, div2.classList.contains(_class))
<div id="one" class="child4 hello"></div>
<div id="two" class="child42 hello"></div>
If you combine this with Array#findIndex
to achieve what you want:
function getParents (el, _class) {
let doc = document
let parents = []
let p = el.parentNode
while (p !== doc) {
let o = p
parents.push(o)
p = o.parentNode
}
parents.push(doc)
return parents[parents.map(x => x.classList).findIndex(cl => cl.contains(_class))]
// ^^^^^^^^^ ^^^^^^^^^
}
document.querySelectorAll('.child1').forEach((e,i)=>{
console.log(getParents(e, 'child4'))
})
<div class="parent">
<div class="child5">
<div class="child4">
<div class="child3">
<div class="child2">
<div class="child1">
1
</div>
</div>
</div>
</div>
</div>
</div>
<div class="parent">
<div class="child5">
<div class="child4">
<div class="child3">
<div class="child2">
<div class="child1">
2
</div>
</div>
</div>
</div>
</div>
</div>
<div class="parent">
<div class="child5">
<div class="child4 hello"> <!-- problem here -->
<div class="child3">
<div class="child2">
<div class="child1">
3
</div>
</div>
</div>
</div>
</div>
</div>
You can make the code slightly shorter by dropping .map
and running the logic in .findIndex
:
parents.findIndex(x => x.classList.contains(_class))
However, with all that said, your algorithm only checks classes. You can very easily extend it to work with any selector by using Element.matches
:
function getParents (el, selector) {
let doc = document
let parents = []
let p = el.parentNode
while (p !== doc) {
let o = p
parents.push(o)
p = o.parentNode
}
parents.push(doc)
return parents[parents.findIndex(x => x instanceof Element && x.matches(selector))]
// only check Elements -------------> ^^^^^^^^^^^^^^^^^^^^
}
document.querySelectorAll('.child1').forEach((e,i)=>{
console.log('.child4', getParents(e, '.child4'))
})
document.querySelectorAll('.child1').forEach((e,i)=>{
console.log('#parentTwo.child4', getParents(e, '#parentTwo.child4'))
})
<div class="parent">
<div class="child5">
<div id="parentOne" class="child4">
<div class="child3">
<div class="child2">
<div class="child1">
1
</div>
</div>
</div>
</div>
</div>
</div>
<div class="parent">
<div class="child5">
<div id="parentTwo" class="child4">
<div class="child3">
<div class="child2">
<div class="child1">
2
</div>
</div>
</div>
</div>
</div>
</div>
<div class="parent">
<div class="child5">
<div id="parentThree" class="child4 hello">
<div class="child3">
<div class="child2">
<div class="child1">
3
</div>
</div>
</div>
</div>
</div>
</div>