Add Class to div that Contains innerHTML String - javascript

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>

Related

How to insert an adjacent element if child element contains specific text?

I'm inserting an element with the following code:
var descriptions = document.querySelectorAll(".product-item-info");
function showDescription() {
descriptions.forEach(description => {
description.insertAdjacentHTML("beforeend", "<div class='description'>Some text</div>");
});
}
showDescription();
This works well. But how do I check if a child of .product-item-info contains specific text, and if so then not add the markup to said element?
I know how to do this with jQuery.
Edit: change afterend to beforeend
You do it with the DOM the same way you do it with jQuery, it's just you have to do a bit that jQuery does behind the scenes in your own code: Seeing if elements contain that text. There's no real shortcut, you just have to look at the text of the element:
const descriptions = document.querySelectorAll(".product-item-info");
function showDescription() {
for (const description of descriptions) {
if (!description.textContent.includes("Some text")) {
description.insertAdjacentHTML("beforeend", "<div class='description'>Some text</div>");
}
}
}
showDescription();
(The optional chaining handles the case where a description doesn't have a next element sibling.)

nextSibling returns null while sibling exists

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>

Select element by tag/classname length

I'd like to select an element using javascript/jquery in Tampermonkey.
The class name and the tag of the elements are changing each time the page loads.
So I'd have to use some form of regex, but cant figure out how to do it.
This is how the html looks like:
<ivodo class="ivodo" ... </ivodo>
<ivodo class="ivodo" ... </ivodo>
<ivodo class="ivodo" ... </ivodo>
The tag always is the same as the classname.
It's always a 4/5 letter random "code"
I'm guessing it would be something like this:
$('[/^[a-z]{4,5}/}')
Could anyone please help me to get the right regexp?
You can't use regexp in selectors. You can pick some container and select its all elements and then filter them based on their class names. This probably won't be super fast, though.
I made a demo for you:
https://codepen.io/anon/pen/RZXdrL?editors=1010
html:
<div class="container">
<abc class="abc">abc</abc>
<abdef class="abdef">abdef</abdef>
<hdusf class="hdusf">hdusf</hdusf>
<ueff class="ueff">ueff</ueff>
<asdas class="asdas">asdas</asdas>
<asfg class="asfg">asfg</asfg>
<aasdasdbc class="aasdasdbc">aasdasdbc</aasdasdbc>
</div>
js (with jQuery):
const $elements = $('.container *').filter((index, element) => {
return (element.className.length === 5);
});
$elements.css('color', 'red');
The simplest way to do this would be to select those dynamic elements based on a fixed parent, for example:
$('#parent > *').each(function() {
// your logic here...
})
If the rules by which these tags are constructed are reliably as you state in the question, then you could select all elements then filter out those which are not of interest, for example :
var $elements = $('*').filter(function() {
return this.className.length === 5 && this.className.toUpperCase() === this.tagName.toUpperCase();
});
DEMO
Of course, you may want initially to select only the elements in some container(s). If so then replace '*' with a more specific selector :
var $elements = $('someSelector *').filter(function() {
return this.className.length === 5 && this.className.toUpperCase() === this.tagName.toUpperCase();
});
You can do this in vanilla JS
DEMO
Check the demo dev tools console
<body>
<things class="things">things</things>
<div class="stuff">this is not the DOM element you're looking for</div>
</body>
JS
// Grab the body children
var bodyChildren = document.getElementsByTagName("body")[0].children;
// Convert children to an array and filter out everything but the targets
var targets = [].filter.call(bodyChildren, function(el) {
var tagName = el.tagName.toLowerCase();
var classlistVal = el.classList.value.toLowerCase();
if (tagName === classlistVal) { return el; }
});
targets.forEach(function(el) {
// Do stuff
console.log(el)
})

How can I strip HTML tags that have attribute(s) from string?

I have a question and answer website like SO. Also I have a textarea and a preview under it (exactly the same as SO). I use a markdown library to converts some symbols to HTML tags. For example that JS library replaces ** with <b>. Ok all fine.
Now I need to escape HTML tags that have attribute. I can do that by PHP like this:
<?php
$data = <<<DATA
<div>
<p>These line shall stay</p>
<p class="myclass">Remove this one</p>
<p>But keep this</p>
<div style="color: red">and this</div>
</div>
DATA;
$dom = new DOMDOcument();
$dom->loadHTML($data, LIBXML_HTML_NOIMPLIED);
$xpath = new DOMXPath($dom);
$lines_to_be_removed = $xpath->query("//*[count(#*)>0]");
foreach ($lines_to_be_removed as $line) {
$line->parentNode->removeChild($line);
}
// just to check
echo $dom->saveHtml($dom->documentElement);
?>
I'm not sure code above is the best, but as you see (in the fiddle I've linked) it works as expected. I mean it removes nodes that are at least one attribute. Now I need to do that by JS (or jQuery) (I need this for that textarea preview simulator). Anyway how can I do that? Do I need regex?
You could do something like this:
$('.myTextArea *').each(function(){
if (this.attributes.length)
$(this).remove();
});
JSFIDDLE
It's not the most efficient, but if it's just a textarea preview it should be fine. I'd recommend running it as little as possible though. As far as I know there is no selector (jQuery or otherwise) that would otherwise do this...so you have to make the JS do the work.
Edit based on comment:
To not remove the element, just the surrounding tag, do something like this:
$('.myTextArea *').each(function(){
if (this.attributes.length)
this.outerHTML = this.textContent;
});
JSFIDDLE
The JavaScript element.attributes property returns a live NamedNodeMap of a tags attributes and their values. For example...
HTML
<div class=".cls" id="id" title="divtitle">
<!-- content ... -->
</div>
JavaScript
var div = document.getElementById('id');
var attr = div.attributes;
console.log(attr);
/* =>
NamedNodeMap [class="cls", id="id", title="divtitle"]
*/
This can be used to filter selected items - something like this for your example...
/* return an array from querySelectorAll */
var paras = Array.prototype.slice.call(
document.querySelectorAll('div p')
);
/* loop through paras */
paras.forEach(function(p) {
/* 'p' = each element in 'paras' */
/* get attributes of 'p' */
var attr = p.attributes;
/* only check elements with attributes */
if (attr.length != 0) {
/* loop through attributes */
Object.keys(attr).forEach(function(a) {
/* apply conditional */
if (attr[a].name === 'class' && attr[a].value === 'myclass' ||
attr[a].name === 'style' && attr[a].value === 'color: red;') {
/* remove element ('p') */
p.parentElement.removeChild(p);
}
});
}
});
Because a NamedNodeMap is a type of Object I used Object.keys(obj) to return an array of keys, and then looped over them to determine the attribute's .name and .value properties.
EDIT: In light of comment above
If you just want to remove the attributes then you can drop the condition above, like so...
paras.forEach(function(p) {
var attr = p.attributes;
if (attr.length != 0) {
Object.keys(attr).forEach(function(a) {
p.removeAttribute(a);
});
}
});
See:
NamedNodeMap
Element.attributes
Array.prototype.slice
Array.prototype.forEach
Object.keys
Element.removeAttribute

javascript: how to replace all the tag with something different

I want to replace all the tags with different tag using javascript.
Lets say I want to replace "asdf" tag with "xyz"
<asdf>
<asdf> First
<asdf> First nested </asdf>
</asdf>
<asdf> Second</asdf
</asdf>
This is what I am expecting:
<xyz>
<xyz> First
<xyz> First nested </xyz>
</xyz>
<xyz> Second </xyz>
</xyz>
i tried with using jquery
$('asdf').each(function() {
$(this).replaceWith("<xyz>"+$(this).html()+"</xyz>")
});
but it replaces only the first not all.
I'd do it in reverse document order so that we process descendant elements before ancestor ones, and I'd avoid making a round-trip through markup since that's unnecessary overhead and will wipe out event handlers we could avoid wiping out. (Not much we can do about the ones on the actual elements we're changing, at least not ones attached with modern techniques.)
See comments:
// Get all target elements, and then get a raw array for them
// and reverse it. Then loop through the reversed copy.
$("div").get().reverse().forEach(function(src) {
// Get a jQuery wrapper for this element
var $src = $(src);
// Create the replacement
var $dest = $("<span>");
// Move all its contents over
$dest.append($src.contents());
// Copy its attributes
Array.prototype.forEach.call(src.attributes, function(attr) {
$dest[0].setAttribute(attr.nodeName, attr.nodeValue);
});
// Replace it
$src.replaceWith($dest);
});
Live Example:
setTimeout(function() {
// Get all target elements, and then get a raw array for them
// and reverse it. Then loop through the reversed copy.
$("div").get().reverse().forEach(function(src) {
// Get a jQuery wrapper for this element
var $src = $(src);
// Create the replacement
var $dest = $("<span>");
// Move all its contents over
$dest.append($src.contents());
// Copy its attributes
Array.prototype.forEach.call(src.attributes, function(attr) {
$dest[0].setAttribute(attr.nodeName, attr.nodeValue);
});
// Replace it
$src.replaceWith($dest);
});
}, 500);
div {
border: 1px solid red;
}
span {
border: 1px solid green;
}
.test {
color: blue;
font-weight: bold;
}
<p>Divs will change to spans in a moment. "Second" is blue because it has a class on it, to check that we copy attributes correctly.</p>
<div>
<div>First
<div>First nested</div>
</div>
<div class="test">Second</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Made simple jquery plugin
$.fn.renameTag = function(replaceWithTag){
this.each(function(){
var outerHtml = this.outerHTML;
var tagName = $(this).prop("tagName");
var regexStart = new RegExp("^<"+tagName,"i");
var regexEnd = new RegExp("</"+tagName+">$","i")
outerHtml = outerHtml.replace(regexStart,"<"+replaceWithTag)
outerHtml = outerHtml.replace(regexEnd,"</"+replaceWithTag+">");
$(this).replaceWith(outerHtml);
});
return this;
}
Usage:
$('asdf').renameTag('xyz')

Categories