nextSibling returns null while sibling exists - javascript

In this part of code, why nextSibling returns null ?
const formIt = () => {
const titles = document.querySelectorAll('h1');
document.getElementById('content').innerHTML = '';
titles.forEach(title => {
console.log(title.nextSibling);
let p = title.nextSibling; //Returns null
let pWrapper = document.createElement('div');
pWrapper.appendChild(p);
document.getElementById('content').appendChild(pWrapper);
});
};
formIt();
<div id='content'>
<h1>...</h1>
<p>...</p>
<h1>...</h1>
<p>...</p>
<h1>...</h1>
<p>...</p>
</div>

On line 3 you set the innerHTML of content to an empty string.
That removes all the h1 and p elements from the DOM.
They aren’t siblings after that.
——
Fiddle with innerHTML after you have finished the loop.

Simply because, by the time the forEach() runs, you've removed all those objects from the DOM:
document.getElementById('content').innerHTML = '';
...so they no longer have any siblings.

There are two properties to iterate on Nodes or elements:
nextSibling
nextElementSibling
See the note at documentation of nextSibling:
Note: Browsers insert Text nodes into a document to represent whitespace in the source markup. Therefore a node obtained, for example, using Node.firstChild or Node.previousSibling may refer to a whitespace text node rather than the actual element the author intended to get.
[..]
You can use Element.nextElementSibling to obtain the next element skipping any whitespace nodes, other between-element text, or comments.
(emphasis mine)
See similar question:
javascript nextsibling function
Example
const headings = document.querySelectorAll('h1');
console.log("headings (count):", headings.length);
let firstHeading = headings[0];
console.log("first h1 nextSibling (data):", firstHeading.nextSibling.data);
console.log("first h1 nextElementSibling (data):", firstHeading.nextElementSibling.data);
let secondHeading = headings[1];
console.log("second h1 nextSibling (data):", secondHeading.nextSibling.data);
console.log("second h1 nextElementSibling (data):", secondHeading.nextElementSibling.data);
<div id='content'>
<h1>heading_1</h1>text_1
<p>paragraph_1</p>
<h1>heading_2</h1>
<p>paragraph_2</p>
</div>

Related

Target multiple elements from the style of element

Lets say i have multiple divs with the same class like
<div class="myDiv">1</div>
<div class="myDiv">2</div>
<div class="myDiv" style="display:none">3</div>
<div class="myDiv">4</div>
And here i want to target the div that has display:none which is the third div <div class="x" style="display:none">3</div>
I tried using this
const elements = document.querySelectorAll('.myDiv');
const element = elements.find(element => element.style.display == "none");
console.log(element)
but i got an error saying
elements.find is not a function
i tried to console.log the style of the elements to check the styles
const elements = document.querySelectorAll('.myDiv');
elements.forEach(element => console.log(element.style.display));
and it outputs this which is correct
""
""
"none"
""
But why doesn't the code that i tried work and why is elements.find not a function?
but i got an error saying
elements.find is not a function
That's because the NodeList returned by querySelectorAll isn't an array, it's a NodeList. You could create an array from it before doing the find:
const elements = document.querySelectorAll('.myDiv');
const element = [...elements].find(element => element.style.display == "none");
// −−−−−−−−−−−−−^^^^−−−−−−−−^
console.log(element);
That works because NodeList instances are iterable, so you can use spread notation to expand them into an array literal. (Other options are Array.from(elements), Array.prototype.slice.call(elements), or just using a for loop.)
You can also narrow your selector from .myDiv to .myDiv[style] so it only matches .myDiv elements that have a style attribute.
Ideally, though, if you can change things so that you don't have that inline style in the first place, I'd do that. For instance, if you use a class to hide the element:
<div class="myDiv">1</div>
<div class="myDiv">2</div>
<div class="myDiv hidden">3</div>
<div class="myDiv">4</div>
where hidden is defined as :
.hidden {
display: none;
}
then you can ask the browser for the specific element you're interested in:
const element = document.querySelector(".myDiv.hidden");
Another way is to use Array.from
like so :
const elements = document.querySelectorAll('.myDiv');
const element = Array.from(elements).find(element => element.style.display == "none");
console.log(element)
Try that it's will diplay none the div number 3, any way you can use id as well to display none
<div class="myDiv">1</div>
<div class="myDiv">2</div>
<div class="myDiv" style="display:none">3</div>
<div class="myDiv">4</div>
JS***
let myDiv = document.getElementsByClassName("myDiv");
myDiv[2].style.display = "none";

JavaScript: querySelector - match also the top element

Is it possible for querySelector to somehow match also the top element? So for example:
Element:
<div class="container">
<div>Something</div>
</div>
JavaScript:
const container = element.querySelector('.container');
This won't match anything, because "container" is the top element. Is there an elegant way of querying element, that would test not only its children, but also the top element? I'm talking pure JavaScript.
You can test whether the selector refers to the top-level, and use a conditional expression:
const container = element.matches(".container") ? element : element.querySelector(".container");
For querySelectorAll you can do:
const containers = [...(element.matches(".container") ? [element] : []), ...element.querySelectorAll(".container")];
This returns an array instead of a NodeList, but for most purposes that difference shouldn't matter.
Not really elegant, but this would work.
If we resort to element.parentNode we would not guarantee targeting only the element.
//dummy up
const element = document.querySelector('.container');
const container = element.querySelector('.container');
const container2 = element.querySelector('.container')||(element.classList.contains('container')) ? element : null;
console.log(container);
console.log(container2);
<div class="container">
<div>Something</div>
</div>
const container = element.querySelector('.container');

How to select next sibling of a certain type with JS?

I've got some html
<h4 id="start-here">title</h4>
<p>paragraph</p>
<p>paragraph</p>
...some number of paragraphs...
link
And I've got the <h4> with the id selected in JavaScript. How do I get from that selection in JS to the first <a> which is of the class link, or just the next sibling anchor tag?
Using document.querySelector() and a CSS selector, here with the general sibling combinator ~, you can achieve that like this:
A side note, in below samples I target inline style, though it is in general better to toggle a class.
Stack snippet
(function(){
document.querySelector('#start-here ~ a.link').style.color = 'red';
})();
<h4 id="start-here">title</h4>
<p>paragraph</p>
link
<p>paragraph</p>
link
Updated based on another question/comment, how to get more than one element in return.
With document.querySelectorAll() one can do similar, and target multiple elements like this.
Stack snippet
(function(){
var elements = document.querySelectorAll('#div2, #div3');
for (var i = 0; i < elements.length; i++) {
elements[i].style.color = 'red';
}
})();
<h4 id="start-here1">title</h4>
<div id="div1">some text</div>
<h4 id="start-here2">title</h4>
<div id="div2">some text</div>
<h4 id="start-here3">title</h4>
<div id="div3">some text</div>
The "start-here" ID on your element makes this easy. But let's imagine you have a reference to a DOM element without such a convenient selector, and you don't want to add a temporary ID to it.
In that case, you could use XPath with document.evaluate and your DOM reference as the second argument. Let's say you have that reference in yourElement and you want the next <section> sibling
const nextSibling = document.evaluate("following-sibling::section", yourElement, null,
XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue
I think to start with the first sibling, then i put all the siblings inside an array. Hence I extract what you want.
var x = document.getElementById("stat-here");
console.log(x)
var result = [],
node = x.nextSibling;
while ( node ) {
if (node.nodeType === Node.ELEMENT_NODE ) {
result.push( node );
}
node = node.nextElementSibling || node.nextSibling;
}
console.log(result, '\n Result: ',result[result.length-2])
<h4 id="stat-here">title</h4>
<p>paragraph</p>
<p>paragraph</p>
link

Add Class to div that Contains innerHTML String

I am trying to add a CSS class to each div on a page that contains the string Subject:
I tried
var elList = document.querySelectorAll("div");
elList.forEach(function(el) {
if (el.innerHTML.indexOf("Subject") !== -1) {
console.log(el);
el.setAttribute('class', "newClass");
}
});
but it didn't return any nodes. And also
var headings = document.evaluate("//*[contains(normalize-space(text()), 'Subject:')]", document, null, XPathResult.ANY_TYPE, null );
while(thisHeading = headings.iterateNext()){
thisHeading.setAttribute('class', "newClass");
console.log(thisHeading);
}
which returned an XPathResult that didn't seem to have any nodes as part of the object.
This is what the HTML looks like, although it is deeply nested inside the document body.
<div class="note-stream-header">Subject: Please Reply to This</div>
How can I select all nodes that contain a string and add a class to them with JS?
Your approach is fine, but since you are interested in the content of an element, use .textContent instead of innerHTML.
See additional comments inline.
// .forEach is not supported in all browsers on node lists
// Convert them to arrays first to be safe:
var elList = Array.prototype.slice.call(
document.querySelectorAll("div"));
elList.forEach(function(el) {
// Use .textContent when you aren't interested in HTML
if (el.textContent.indexOf("Subject") > -1) {
console.log(el);
el.classList.add("newClass"); // Use the .classList API (easier)
}
});
.newClass { background-color:#ff0; }
<div>The subject of this discussion is JavaScript</div>
<div>The topic of this discussion is JavaScript</div>
<div>The queen's royal subjects weren't amused.</div>
<div>Subject: textContent DOM property</div>

How to search the children of a HTMLDivElement?

I have an HTMLDivElement, and my goal is to find a div nested beneath this.
Ideally I'd want something like getElementById, but that function doesn't work for HTMLDivElement.
Do I need to manually traverse the graph, or is there an easier way?
Demo: http://jsfiddle.net/ThinkingStiff/y9K9Y/
If the <div> you're searching for has a class, you can use getElementsByClassName():
document.getElementById( 'parentDiv' ).getElementsByClassName( 'childDiv' )[0];
If it doesn't have a class you can use getElementsByTagName():
document.getElementById( 'parentDiv' ).getElementsByTagName( 'div' )[0];
And if it has an id you can, of course, just use getElementById() to find it no matter where it is in the DOM:
document.getElementById( 'childDiv' );
//For immediate children
var children = document.getElementById('id').childNodes;
//or for all descendants
var children = document.getElementById('id').getElementsByTagName('*');
var div = ...
var divChildren = div.getElementsByTagName("div");
var divYouWant = [].filter.call(divChildren, function (el) {
return matchesSomeCondition(el);
});
Ideally, I'd want something like getElementById
And you can use getElementById just do document.getElementById(id) and since ids are unique that will find that single div item you wanted.
You can also use elem.getElementsByClassName to select a descendant of elem by class
You can use .querySelector(). The functions getElementById() and getElementByClassName() work perfectly fine on document, but they do not work on the child records returned by these functions. If you need many children elements, then consider .querySelectorAll()
Full Working Demo:
const topdiv = document.getElementById('top-div');
const seconddiv = topdiv.querySelector('#second-div');
seconddiv.innerHTML = '456';
<div id="top-div">
123
<div id="second-div">
abc
</div>
</div>
Demonstration of getElementById() Failing:
const topdiv = document.getElementById('top-div');
const seconddiv = topdiv.getElementById('second-div');
seconddiv.innerHTML = '456';
<div id="top-div">
123
<div id="second-div">
abc
</div>
</div>
Concise and readable:
document.getElementById('parentDiv').children[0];
Using .childNodes returns lots of extra children, whereas .children returns a smaller array.

Categories