I can select a specific html element using
querySelector('.someClassName')
And then run tests to check aspects such as textContent.
I'd like to be able to select an element, like I did above, and then check if there is specific text in a nested html element. I'm thinking something like this:
expect(someContainer.querySelector('.someClassName').nestedTextContent)
.toEqual(expect.stringContaining('this is some nested text'))
//nestedTextContent not real: this is something I made up to show what I'd like to achieve
Is there something like this? Is my only option to iterate through the dom?
Use a recursive function to check the textContent of each element:
let res = null
function findTextContent(el, txt){
if(el && el.textContent.trim() === txt){
res = el
return res
}else{
if(el && el.hasChildNodes()){
[...el.children].forEach((ch) => {
return findTextContent(ch, txt)
})
}
return res
}
}
const el = findTextContent(document.querySelector(".grandparent"), "Child text")
el.style.color = "red"
<div class="grandparent">
Grandparent text
<div class="parent">
Parent text
<div class="child">Wrong child text</div>
</div>
<div class="parent">
Parent text
<div class="child">Wrong child text</div>
<div class="child">Child text</div>
<div class="child">Wrong child text</div>
</div>
</div>
You can use expect().stringContaining to check if a string has a certain substring (assuming you are using jest). To get the text contained in the div, use querySelector(....).innerText (again, assuming you are using jest and querySelector returns an HTMLElement.
Related
I'm hoping to get the textContent of an element (including text within nested children) but ignore a specific element which has text.
For example, such that
<div class="container">
<div class="exclude-text">Exclude me</div>
<div class="another-class">Add this text</div>
And this text here
</div>
Performing element.textContent returns Exclude me Add this text And this text here whereas I'd just like add this text And this text here.
My possible solution would be to iterate through childNodes while checking if classList.contains("exclude-text") but how would I still get the And this text here?
Thanks in advance!
You're on the right track looping through nodes. Do it recursively, building up the text of the text nodes, and recursing into elements if they don't match an exclude selector (which could be a group [".a, .b"], if you want to exclude multiple classes or similar):
function getTextExcept(element, exclude) {
return worker(element);
function worker(node, text = "") {
if (node.nodeType === Node.TEXT_NODE) {
text += node.nodeValue;
} else if (node.nodeType === Node.ELEMENT_NODE) {
if (!node.matches(exclude)) {
for (const child of node.childNodes) {
text = worker(child, text);
}
}
}
return text;
}
}
console.log(getTextExcept(
document.querySelector(".container"),
".exclude-text"
));
<div class="container">
<div class="exclude-text">Exclude me</div>
<div class="another-class">Add this text</div>
And this text here
</div>
An alternative approach is to clone the entire structure, remove the elements you don't want the text of, and then use .textContent on the result. For a really large structure, that might be problematic in terms of memory (but it would have to be really large).
function getTextExcept(element, exclude) {
const clone = element.cloneNode(true);
for (const child of clone.querySelectorAll(exclude)) {
child.remove();
}
return clone.textContent;
}
console.log(getTextExcept(
document.querySelector(".container"),
".exclude-text"
));
<div class="container">
<div class="exclude-text">Exclude me</div>
<div class="another-class">Add this text</div>
And this text here
</div>
I want to loop through a nested HTML DOM node, as shown below:
<div id="main">
<div class="nested-div-one">
<div class="nested-div-two">
<div class="nested-div-three">
</div>
</div>
</div>
<div class="nested-div-one">
<div class="nested-div-two">
<div class="nested-div-three">
</div>
</div>
</div>
</div>
How would I do this using Javascript to loop through every single one of the dividers?
I am guessing OP was not specific for DIV elements, here's a more dynamic approach:
So first you wanna get the first container, in your case it's:
var mainEl = document.getElementById('main');
Once you have that, each DOM element has a .children property with all child nodes. Since DOM is a tree object, you can also add a flag to achieve recursive behavior.
function visitChildren(el, visitor, recursive) {
for(var i = 0; i < el.children.length; i++) {
visitor(children[i]);
if(recursive)
visitChildren(children[i], visitor, recursive);
}
}
And now, let's say you want to change all div backgrounds to red:
visitChildren(mainEl, function(el) { el.style.background = 'red' });
You can use vanilla javascript for this
document.querySelectorAll('div').forEach(el => {
// el = div element
console.log(el);
});
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
Is there a way to select the deepest child in each branch (specifically divs) in cheerio?
Example:
<div id="parent">
<div>
<div id="dontSelectThisSinceThereIsADeeperDiv"></div>
<div>
<div id="selectThis"></div>
</div>
</div>
<div id="selectThisAlso"></div>
<div>
<div id="selectThisAsWell"></div>
</div>
</div>
Basically, all the divs that I want to select are the deepest within their "branch" from the parent. Is there a way to possibly do this in cheerio?
It doesn't look like there is a single function to do what you require. But you can create your own function by utilising different cheerio functions. For a recursive example (not tested, but hopefully you get the idea):
function getLeaves(parent, result = []) {
let children = $(parent).children()
if(children.length > 0){
children.each((i, elem) => getLeaves(elem, result))
}else{
result.push(parent)
}
return result
}
let leaves = getLeaves('#parent')
If I click on #parent I want to return the text inside it and not return the text inside nested
layers (#child-parent, #child)
<div id='parent'>
this text is for parent
<div id='child-parent'>
this text if for child-parent
<div id='child'>
and this text is for child.
</div>
</div>
</div>
this:
$('#parent').html() = "this text is for parent"
and not this:
$('#parent').html() = "this text is for parent this text is for child-parent and this text is for child"
You can grab it like this:
$('#parent').clone().children().remove().end().html()
You can test it out here, you may want a $.trim() call on there though, like this.
Another alternative is to loop though the text nodes, like this:
$('#parent').contents().filter(function() { return this.nodeType == 3; }).text()
You can test that here or the trimmed version here.
If the structure always looks like this, you can call:
var mytext = $('#parent').contents().first().text();
Example: http://www.jsfiddle.net/YjC6y/