Reputation: 8650
I want to be able to click on the <i>
, the <a>
, or the <li>
and get all the <li>
s.
The point is to detect the closest repetitive element no matter how deep in it's children you click.
This is what I have so far: It shows the count of the siblings but what I'd like to isolate is same-ness.
For example: <body>
shows 2 siblings but they are not both <body>
. <header>
shows 4 but they not all <header>
. How do I only count same siblings. In this case <li>
but can be anything.
//Run in full-page↗️
$('body').on('click', (e) => {
$el = $(e.target);
$parentsAndSelf = $el.parents().addBack();
$parentsAndSelf.each( (i,el) => {
if( $(el).siblings().addBack().length > 1){
console.log( el, $(el).siblings().addBack().length );
//return false;
}
});
});
* {
padding: 5px;
margin:5px;
outline: 1px solid pink;
list-style-position: inside;
}
a {
display: block
}
.highlight{
background:yellow;
}
*:hover {
outline: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
HEADER:
<nav>
NAV:
<ul>
UL:
<li>li <a href="#">item <i>1</i></a> </li>
<li>li <a href="#">item <i>2</i></a> </li>
<li>li <a href="#">item <i>3</i></a> </li>
<li>li <a href="#">item <i>4</i></a> </li>
</ul>
</nav>
</header>
Upvotes: 4
Views: 282
Reputation: 6532
This really got me occupied hehe.
So if:
The point is to detect the closest repetitive element no matter how deep in it's children you click.
It means:
I want to be able to click on the
<i>
, the<a>
, or the<li>
and get all the<li>
s.
Example:
let num
document.addEventListener("click", function(event) {
num = 1;
let el = event.target
let s
check(s, el)
})
function check(s, el) {
console.clear()
// jumping parents part and getting only first level children
let ss = ".parentNode"
s = "el" + ss.repeat(num) + ".children"
// map tagnames
let c = [...eval(s)].map(type => type.tagName)
// isolate duplicates part
const yourArray = c
const yourArrayWithoutDuplicates = [...new Set(yourArray)]
let duplicates = [...yourArray]
yourArrayWithoutDuplicates.forEach((item) => {
const i = duplicates.indexOf(item)
duplicates = duplicates
.slice(0, i)
.concat(duplicates.slice(i + 1, duplicates.length))
})
// if no duplicates call this same function just add num++ to add .parentNode
// it will loop unitl it hits 2 same tagnames on same level, sibilings
duplicates.length === 0 ? (num++, check(eval(s), el)) : (console.log("Repeting element :" + duplicates[0]), console.log("Repets: " + (parseInt(duplicates.length) + 1)), console.log("Position from clicked element: " + s))
}
* {
padding: 5px;
margin: 5px;
outline: 1px solid pink;
list-style-position: inside;
}
a {
display: block
}
.highlight {
background: yellow;
}
*:hover {
outline: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
<div>1</div>
<div>2</div>
HEADER:
<nav>
NAV:
<ul>
UL:
<li>li <a href="#">item <i>1</i></a> </li>
<li>li <a href="#">item <i>2</i></a> </li>
<li>li <a href="#">item <i>3</i></a> </li>
<li>li <a href="#">item <i>4</i></a> </li>
</ul>
</nav>
</header>
EDIT:
And here is a bit more simplified version still following same logic:
let num
document.addEventListener("click", function(event) {
num = 1;
let s
check(s, event.target)
})
function check(s, el) {
console.clear()
const ss = ".parentNode"
s = "el" + ss.repeat(num) + ".children";
let count = {};
[...eval(s)].map(type => type.tagName).forEach( i => {count[i] = (count[i] || 0) + 1})
const max = Math.max.apply(null, Object.values(count));
max <= 1 ? (num++, check(eval(s), el)) : (console.log("Repeting element :" + Object.keys(count)[0]), console.log("Repets: " + max), console.log("Position from clicked element: " + s))
}
* {
padding: 5px;
margin: 5px;
outline: 1px solid pink;
list-style-position: inside;
}
a {
display: block
}
.highlight {
background: yellow;
}
*:hover {
outline: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
<div>1</div>
<div>2</div>
HEADER:
<nav>
NAV:
<ul>
UL:
<li>li <a href="#">item <i>1</i></a> </li>
<li>li <a href="#">item <i>2</i></a> </li>
<li>li <a href="#">item <i>3</i></a> </li>
<li>li <a href="#">item <i>4</i></a> </li>
</ul>
</nav>
</header>
Upvotes: 2
Reputation: 206151
Let me start with a pretty simple JavaScript idea before presenting the jQuery one:
This could be easily made by using Element.querySelectorAll() with a ":scope >"
query for immediate child elements of the same Element.tagName
, and a recursive function:
const closestMultiple = el => {
const par = el.parentElement;
const sib = par && par.querySelectorAll(`:scope > ${el.tagName}`);
return (sib && sib.length > 1 ? sib : par && closestMultiple(par))
}
document.body.addEventListener("click", (ev) => {
const cm = closestMultiple(ev.target)
if(cm) cm.forEach(EL => EL.classList.toggle("highlight"));
});
* {
padding: 5px;
margin: 5px;
outline: 1px solid pink;
list-style-position: inside;
background: #fff;
}
*:hover {
outline: 1px solid red;
}
a {
display: block
}
.highlight {
background: yellow;
}
<header>
<div>
HEADER:
</div>
<div>
<nav>
NAV:
<ul>
UL:
<li>li <a href="#">item <i>1</i></a> </li>
<li>li <a href="#">item <i>2</i></a> </li>
<li>li <a href="#">item <i>3</i></a> </li>
<li>li <a href="#">item <i>4</i></a> </li>
</ul>
</nav>
</div>
</header>
.reverse()
to speed up the process starting from the Target Element (instead of document).length > 1
criteria:function closestMultiple(el, $mu) {
return $(el).parents().addBack().get().reverse().some(el => {
const $gr = $(el).siblings(el.tagName).addBack();
return ($gr.length > 1 ? $mu = $gr : 0);
}) && $mu || $();
}
function closestMultiple(el, $mu) {
return $(el).parents().addBack().get().reverse().some(el => {
const $gr = $(el).siblings(el.tagName).addBack();
return ($gr.length > 1 ? $mu = $gr : 0);
}) && $mu || $();
}
$(document).on('click', (ev) => {
const $clRep = closestMultiple(ev.target).toggleClass("highlight");
console.clear(); console.log($clRep.length +" "+ $clRep.prop("tagName"));
});
* {
padding: 5px;
margin: 5px;
outline: 1px solid pink;
list-style-position: inside;
background: #fff;
}
*:hover {
outline: 1px solid red;
}
a {
display: block
}
.highlight {
background: yellow;
}
<header>
<div>
HEADER:
</div>
<div>
<nav>
NAV:
<ul>
UL:
<li>li <a href="#">item <i>1</i></a> </li>
<li>li <a href="#">item <i>2</i></a> </li>
<li>li <a href="#">item <i>3</i></a> </li>
<li>li <a href="#">item <i>4</i></a> </li>
</ul>
</nav>
</div>
</header>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
For a nicer experience (since you're already using jQuery) you could extend the jQuery
constructor with your own .closestMultiple()
method.
Here's a suggestion for a Micro-plugin:
$.fn.closestMultiple = function($mu) {
return this.parents().addBack().get().reverse().some(el => {
const $gr = $(el).siblings(el.tagName).addBack();
return ($gr.length > 1 ? $mu = $gr : 0);
}) && $mu || $();
}
$(document).on('click', (ev) => {
$(ev.target).closestMultiple().toggleClass("highlight");
});
* {
padding: 5px;
margin: 5px;
outline: 1px solid pink;
list-style-position: inside;
background: #fff;
}
a {
display: block
}
.highlight {
background: yellow;
}
*:hover {
outline: 1px solid red;
}
<header>
<div>
HEADER:
</div>
<div>
<nav>
NAV:
<ul>
UL:
<li>li <a href="#">item <i>1</i></a> </li>
<li>li <a href="#">item <i>2</i></a> </li>
<li>li <a href="#">item <i>3</i></a> </li>
<li>li <a href="#">item <i>4</i></a> </li>
</ul>
</nav>
</div>
</header>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
Upvotes: 2
Reputation: 798
I think this simple way may help you:
$("i, a, li").on('click', function(event) {
var closest_li = $(this).closest('ul').find('li');
console.log(closest_li);
});
So you will get array of all <li>
elements which exist in parent <ul>
.
Upvotes: -2