Reputation: 5271
I created a snippet based on .parents() without jquery - or querySelectorAll for parents
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.className).indexOf(_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>
if <div class="child4 hello">
without hello
I can get all child4
, if hello
or anything else is added, I am unable to get child4
. I use indexOf()
I think shouldn't be -1
or undefined. Could someone please correct me where is the error? Thanks
Upvotes: 2
Views: 841
Reputation: 20039
Using Element.matches()
to add support to query
the parents
function getParents(el, query) {
let parents = []
while (el.parentNode !== document.body) {
el.matches(query) && parents.push(el)
el = el.parentNode
}
return parents
}
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>
Upvotes: 1
Reputation: 28969
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>
Upvotes: 2
Reputation: 5940
You could use findIndex
of Array prototype like so :
return parents[parents.map(x => x.className).findIndex(x => x.indexOf(_class) > -1)]
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.className).findIndex(x => x.indexOf(_class) > -1)]
}
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>
Upvotes: 2
Reputation: 22474
The problem is that you're getting a list of elements and you're mapping those to the elements' className
, when the target element have more then just the child4
class, the value in the array will be child4 hello
and when you're looking for the index of the child4
value in the array, the child4 hello
won't match. Instead of Array.indexOf
you can use Array.find
.
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.find(e => e.className.includes(_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>
Instead of the getParents
function you could use Element.closest
Here is an example:
const parents = Array.from(document.querySelectorAll('.child1')).map(e => e.closest('.child4'));
console.log(parents);
<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>
As @Khauri mentioned, in the first example, instead of using the className
property and using string functions to check if that value is a match, it will be better to use the classList
property because it has a contains
function.
Upvotes: 3