Find nearest element, not closest - javascript

I had an impression that closest() will give me nearest matching element as it suggest but I was wrong, it will give me nearest ancestor, so does parents(). So how can I get nearest element?
e.g. I have 2 divs as below
<div id="clickme">
Click me
</div>
<div class="findme" style="display:none">
Find me
</div>
what to do if I want to get .findme with reference with #clickme.
Can I do something like $('#click').helpfullFunctionThatGivesNearestElement('.findme')?
Or do I need to scan entire DOM like $('findme')? but there can be 100s of .findme then how will I find nearest to specific element?
Update
.findme can be anywhere in DOM.

This is how recursively traversing the DOM looks like.
I've implemented the function $.fn.nearest, as OP asumes to use jQuery, so it can be called with $('clickme').nearest('.findme');
The method will find multiple elements** (if they share the same distance from the starting node) by looking towards and backwards in every direction (parent, children, next and prev) by recursively searching through the nearest checked elements. It also avoids checking an element over and over again (i.e. the parent of multiple children is only checked once).
If you don't need a particular direction to be checked, i.e. children or prev you can just comment that part.
Some checks are made before the recursion is done. When the selector is not found in the DOM an empty jQuery element is returned, also when there is only one element found that found element is returned.
I haven't tested it's efficiency with a large HTML, it all depends on how far the desired element is located, and that is directly related to the complexity of the HTML structure. But for sure it is exponential, something close to O(n³) or O(n⁴).
Give it a try.
$.fn.nearest = function(selector) {
var allFound = $(selector);
if (!allFound.length) // selector not found in the dom
return $([]);
if (allFound.length == 1) // found one elem only
return allFound;
else
return nearestRec($(this), selector);
function nearestRec(elems, selector) {
if (elems.length == 0)
return this;
var selector = selector;
var newList = [],
found = $([]);
$(elems).each(function(i, e) {
var options = e[1] || {};
e = $($(e)[0]);
// children
if (!options.ignoreChildren)
updateFound(e.children(), selector, newList, found, {
ignoreParent: true
});
// next
if (!options.ignoreNext)
updateFound(e.next(), selector, newList, found, {
ignoreParent: true,
ignorePrev: true
});
// prev
if (!options.ignorePrev)
updateFound(e.prev(), selector, newList, found, {
ignoreParent: true,
ignoreNext: true
});
// parent
if (!options.ignoreParent)
updateFound(e.parent(), selector, newList, found, {
ignoreChildren: true
});
});
return found.length && found || nearestRec(newList, selector);
function updateFound(e, selector, newList, found, options) {
e.each(function() {
var el = $(this);
if (el.is(selector)) {
found.push(el);
return;
}
newList.push([el, options]);
});
}
}
};
$(function() {
// multiple elems found, traverse dom
$(".clickme").nearest(".findme").each(function() {
$(this).addClass("found");
});
});
div {
padding: 5px 3px 5px 10px;
border: 1px solid #666;
background-color: #fff;
margin: 3px;
}
.found {
background-color: red;
}
.clickme {
background-color: #37a;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="findme">
findme
<div class="findme">
findme
<div>
<div>
</div>
<div>
</div>
<div class="clickme">
clickme
<div>
</div>
<div>
<div class="findme">
findme
<div class="findme">
findme
</div>
</div>
<div class="findme">
findme
</div>
</div>
</div>
<div>
<div class="findme">
findme
</div>
</div>
<div class="findme">
findme
</div>
</div>
<div class="findme">
findme
<div>
</div>
<div>
</div>
<div class="findme">
findme
<div class="findme">
findme
</div>
</div>
<div>
</div>
<div class="findme">
findme
</div>
</div>
</div>
</div>

jquery plugin:
(function( $ ){
$.fn.nextElementInDom = function(selector, options) {
var defaults = { stopAt : 'body' };
options = $.extend(defaults, options);
var parent = $(this).parent();
var found = parent.find(selector + ":first");
switch(true){
case (found.length > 0):
return found;
case (parent.length === 0 || parent.is(options.stopAt)):
return $([]);
default:
return parent.nextElementInDom(selector);
}
};
})( jQuery );
Usage:
$('#clickme').nextElementInDom('.findme');

Instead of traversing the entire DOM tree, you can try to locate an element with reference to its enclosing parent.
<div>
<div id="clickme" onclick="$(this).parent().find('.findme').show();">
Click me
</div>
<div class="findme" style="display:none">
Find me
</div>
</div>
This will however work only if the element that you search has a same ancestral parent.

Related

check for only selected element if that has text in it but not its child

i am trying to check if a selected element have text inside it , the problem is when i am trying to checking value by innerText its also returning values from child which is why boolean function is returning true but what i am expecting to do is just verify one element and avoiding child's i hope i have exlpained it correct so what can be a proper way to achive this
const main = document.querySelector(".main")
const first = main.querySelector(".selected")
const second = main.querySelector(".another")
const third = main.querySelector(".another-one")
function hasText(el){
return el.innerText != ""
}
console.log(hasText(first),hasText(second),hasText(third)) // true true true expected result is = [true false,true]
<div class="main">
<div class="selected">
i have text inside me
</div>
<div class="another">
<div class="child">i am inside a child</div>
</div>
<div class="another-one">
i am inside another one
<div class="child">i am inside a child</div>
</div>
</div>
There are several options to achieve your goal. You could just check if el has children elements and in case .children.length > 0 just consider it as a text container and measure its innerText length.
But in case those parent elements are also supposed to optionally contain both children elements and text, you should check every single childNode of theirs, verify if that's a text portion and consider that text to be part of parent content.Those nodes will be text portions sparse in between legit children elements.
There's also the textContent property of Node that could give you an information similar to that returned by my function grabPureTextContent but there are caveats that I preferred to cut off implementing my own logic.
https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
Here's a demo adding such function to your code and using its logic to determine if a given element has text content or not.
I added a third case (.special) showing what happens when there's mixed content.
const main = document.querySelector(".main")
const first = main.querySelector(".selected")
const second = main.querySelector(".another")
const third = main.querySelector(".special")
/*
Returns the pure text content defined in el
looping through its childNodes and concatenating content,
(if child is text only) before returning the whole string.
There's also the option to use textContent property of Node
https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
But here the algorithm is more clear on what's going on
*/
function grabPureTextContent(el){
let content = '';
for(childText of el.childNodes){
if (childText.constructor.name == 'Text'){
content += childText.nodeValue.trim();
}
}
return content;
}
function hasText(el){
if (grabPureTextContent(el).length > 0)
return true;
return false;
}
console.log(hasText(third)) //=> true
console.log(hasText(second)) //=> false
console.log(hasText(third)) //=> true
.main > div {
border: solid 1px gray;
margin-bottom: 1em;
}
.container {
background: lightgreen;
}
.child {
background: lightpink;
}
<div class="main">
<div class="container selected">
i have text inside me
</div>
<div class="container another">
<div class="child">i am inside a child</div>
</div>
<!-- special case having both children and text -->
<div class="container special">
<div class="child">i am inside a child</div>
TEXT HERE after child
</div>
</div>
I would test if some tags inside the string. If not you can use your check if is empty.
const main = document.querySelector(".main")
const first = main.querySelector(".selected")
const second = main.querySelector(".another")
const third = main.querySelector(".another2")
function hasText(el) {
const pattern = /<.*>.*<\/.*>/;
return pattern.test(el.innerHTML) ? false : (el.innerText != "");
}
console.log(hasText(first),hasText(second), hasText(third)) // true true expected result is = [true false true]
<div class="main">
<div class="selected">
i have text inside me
</div>
<div class="another">
<div class="child">i am inside a child</div>
</div>
<div class="another2">
i am inside a child
</div>
</div>

Clearing div child elements without removing from the DOM

My goal is, using Jquery or vanilla JS, to clear the inner text only of a div and each of its child elements while keeping all elements intact after the fact. In the example below, the div is student_profile.
Answers on SO have recommended the functions .html('') and .text('') but, as my example shows below, this completely removes the child element from the DOM (my example shows only one function but both actually remove the element). Is there a function that would remove all of the text from the current div and child divs while keeping the elements themselves intact?
Any advice here would be appreciated!
function cleardiv() {
console.log(document.getElementById("student_name"));
$('#student_profile').html('');
console.log(document.getElementById("student_name"));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='student_profile'>
<h1 id="student_name">Foo Bar</h1>
<p id="student_id">123</p>
<p id="studen_course">math</p>
<p id="last_reported">2021-01-01</p>
</div>
<button onclick="cleardiv()">Clear</button>
One option is to select all text node descendants and .remove() them, leaving the actual elements intact:
const getTextDecendants = (parent) => {
const walker = document.createTreeWalker(
parent,
NodeFilter.SHOW_TEXT,
null,
false
);
const nodes = [];
let node;
while (node = walker.nextNode()) {
nodes.push(node);
}
return nodes;
}
function cleardiv() {
for (const child of getTextDecendants($('#student_profile')[0])) {
child.remove();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='student_profile'>
<h1 id="student_name">Foo Bar</h1>
<p id="student_id">123</p>
<p id="studen_course">math</p>
<p id="last_reported">2021-01-01</p>
</div>
<button onclick="cleardiv()">Clear</button>
You can try the selector #student_profile * to include all the child elements.
function cleardiv() {
console.log(document.getElementById("student_name"));
$('#student_profile *').text('');
console.log(document.getElementById("student_name"));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='student_profile'>
<h1 id="student_name">Foo Bar</h1>
<p id="student_id">123</p>
<p id="studen_course">math</p>
<p id="last_reported">2021-01-01</p>
</div>
<button onclick="cleardiv()">Clear</button>
If it's only direct children you're looking to affect, you can iterate the childNodes of the parent element. This will clear both element nodes as well as non-element nodes such as text nodes. Here using the NodeList#forEach() method provided by the returned NodeList.
function cleardiv() {
document.getElementById('student_profile')
.childNodes
.forEach((node) => (node.textContent = ''));
console.log(document.getElementById('student_name'));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='student_profile'>
<h1 id="student_name">Foo Bar</h1>
<p id="student_id">123</p>
<p id="studen_course">math</p>
<p id="last_reported">2021-01-01</p>
</div>
<button onclick="cleardiv()">Clear</button>

Find elements of similar hierarchy by JavaScript (for web scraping)

For example, when I select one of the p.item-title elements below, all p.item-title elements should be found (not by the class name). Also, when I select one of the table elements below, all similar tables should be found. I need this for web scraping.
<div>
<div>
<p class="item-title">...</p>
<table>...</table>
</div>
</div>
<div>
<div>
<p class="item-title">...</p>
<table>...</table>
</div>
</div>
jQuery's siblings() method is similar in concept, but it finds similar elements under the same parent node. Is there any method or library to find similar elements from different parent nodes?
Just do querySelectorAll by the path (hierarchy) you want:
var allElements = document.querySelectorAll("div > div > p");
allElements.forEach(p => console.log(p));
<div>
<div>
<p class="item-title">Text 1</p>
<table>...</table>
</div>
</div>
<div>
<div>
<p class="item-title">Text 2</p>
<table>...</table>
</div>
</div>
Try this:
jQuery.fn.addEvent = function(type, handler) {
this.bind(type, {'selector': this.selector}, handler);
};
$(document).ready(function() {
$('.item-title').addEvent('click', function(event) {
console.log(event.data.selector);
let elements = document.querySelectorAll(event.data.selector);
elements.forEach(e => console.log(e));
});
});
Thanks to Jack, I could create a running script.
// tags only selector (I need to improve depending on the use case)
function getSelector(element){
var tagNames = [];
while (element.parentNode){
tagNames.unshift(element.tagName);
element = element.parentNode;
}
return tagNames.join(" > ");
}
function getSimilarElements(element) {
return document.querySelectorAll(element);
}

using jquery remove part of a cloned element

having a difficult time removing a div inside of a cloned element. run the snippet and notice the do not clone me part gets appended even though it is removed.
let myhtml = `<div style="border: 1px solid black;" class="mycontainer">
clone me
<div class="noClone">
do not clone me
</div>
<button class="clonebtn"> clone it </button>
</div>`
$(document).ready(function() {
let content = $(myhtml);
$('.row').append(content);
$('.row').on('click', '.clonebtn', function() {
let container = $(this).closest('.mycontainer');
let clonedContainer = container.clone();
clonedContainer.remove('.noClone');
$('.row').append(clonedContainer);
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="row">
</div>
</div>
or run the fiddle here https://jsfiddle.net/k6jz9xe2/3/
You need to use .find() to find all elements inside the parent div with a class of noClone to remove.
$(selector).remove(anotherselector) in jQuery only removes any elements matching anotherselector from the Array returned by selector. The selector given to the remove() function is only applied to the elements contained in the jQuery collection not to the children of those elements. It is analogous to $(selector).filter(anotherselector).remove().
Consider the following HTML and jQuery code:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="foo">
Foo
<div id="bar">Bar</div>
</div>
<script>
$('#foo').remove('#bar');
</script>
You may expect that the div with the id "bar" inside the div with the id "foo" will be removed, but this is not the case. Why? The $('#foo') selector returns an Array with just one item: the div with the id of "foo". jQuery attempts to filter through the Array and find an element matching the $('#bar') selector. No element is found and nothing will happen.
The following selector, however, will remove the div with the id of "bar".
$('div').remove('#bar');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="foo">
Foo
<div id="bar">Bar</div>
</div>
<script>
$('div').remove('#bar');
</script>
The $('div') selector returns an Array with all the divs on the page. jQuery filters through all of the divs to find an div matching the $('#bar') selector (having an id of "bar"). Having found one, it removes it.
let myhtml = `<div style="border: 1px solid black;" class="mycontainer">
clone me
<div class="noClone">
do not clone me
</div>
<button class="clonebtn"> clone it </button>
</div>`;
$(document).ready(function() {
let content = $(myhtml);
$('.row').append(content);
$('.row').on('click', '.clonebtn', function() {
let container = $(this).closest('.mycontainer');
let clonedContainer = container.clone();
clonedContainer.find('.noClone').remove();
$('.row').append(clonedContainer);
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="row">
</div>
</div>
let myhtml = `<div style="border: 1px solid black;" class="mycontainer">
clone me
<div class="noClone">
do not clone me
</div>
<button class="clonebtn"> clone it </button>
</div>`
$(document).ready(function() {
let content = $(myhtml);
$('.row').append(content);
$('.row').on('click', '.clonebtn', function() {
let container = $(this).closest('.mycontainer');
let clonedContainer = container.clone();
clonedContainer.find('.noClone').remove();
$('.row').append(clonedContainer);
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="row">
</div>
</div>

Get index of child using jQuery

I want to get the index of the clicked child
<div class="parent">
<div class="child">
<div class="c1"></div>
<div class="c2"></div>
</div>
<div class="child">
<div class="c1"></div>
<div class="c2"></div>
</div>
<div class="child">
<div class="c1"></div>
<div class="c2"></div>
</div>
</div>
jQuery:
$('.child .c1').click(function(){
alert($(this).parent().index())
})
I always get -1. How can i do this work?
EDIT:
I tried this:
$('.child .c1').click(function(){
alert($(this).index())
})
The result is -1 all the time.
What could be wrong?
var child_index = '';
$('.c1').click(function() {
var parent = $(this).parent();
child_index = $(parent).index();
alert(child_index);
});
.div{
position:absolute;
left:45%;
top:0;
}
.child{
margin:1%;
text-align:center;
background-color:gray;
width:100px;
height:100px;
}
.c1,.c2{
color:white;
background-color:blue;
}
.c2{
color:red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<div class="div">
<div class="child">
<div class="c1">c1</div>
<div class="c2">c2</div>
<p>click only work on <strong>c1</strong></p>
</div>
<div class="child">
<div class="c1">c1</div>
<div class="c2">c2</div>
<p>click only work on <strong>c1</strong></p>
</div>
<div class="child">
<div class="c1">c1</div>
<div class="c2">c2</div>
<p>click only work on <strong>c1</strong></p>
</div>
</div>
var child_index = ''; //to store .child class parent index when clicked
$('.c1').click(function() {
var parent = $(this).parent(); //getting the specific parent of c1
//parent variable value now is = .child
child_index = $(parent).index();
alert(child_index);
});
The reason that it returns -1 every time is because in Jquery -1 is a boolean for false and 0 is true. .index() basically asks whether an element exists.
If you want to find what number it is this is a code which I think would work.
var loop = $('.parent .child').length;
$('.child .c1').click(function(){
for(i=1; i<= loop; i++) {
if($(this).parent() === $('.parent').children().eq(i)) {
alert(i);
}
}
});
EDIT Since no one can reproduce the problem, here are some possibilities:
The version of jQuery you're using is conflicting with another script on your page. Since it seems your click event handler is firing because your getting an alert box.
Try using the latest version of jQuery.
Your version of jQuery is conflicting with the current browser you are using. When you're having JavaScript issues you should report the versions of the library you are using (if any) and of your browser.
Check your results in another browser and report back.
Your HTML is being manipulated before your click event is fired. What other code do you have on the page?
Share a more complete source code listing.
alert($(this).parent().prevAll().length);
Use .prevAll to get all previous siblings, then use the length of that set as your index.
edit Use .prevAll(".child") if you are expecting other elements that you want to ignore for indexing purposes.
edit You could try removing jQuery all together
var elms=document.getElementsByClassName("c1");
for(var i=0; i<elms.length; i++)
elms[i].onclick = function() {
var elm = this;
var index = 0;
elm = elm.parentNode;
if(elm) {
while(elm=elm.previousSibling) {
if(elm.nodeType == 1) {
index++;
}
}
} else {
alert("There's no parent node");
}
alert(index);
};

Categories

Resources