Getting the contents of an element WITHOUT its children [duplicate] - javascript

This question already has answers here:
How to get the text node of an element?
(11 answers)
Closed 6 months ago.
I have a mild preference in solving this in pure JS, but if the jQuery version is simpler, then jQuery is fine too. Effectively the situation is like this
<span id="thisone">
The info I want
<span id="notthisone">
I don't want any of this nonsense
</span>
</span>
I effectively want to get
The info I want
but not
The info I want I don't want any of this nonsense
and I especially don't want
The info I want <span id="notthisone"> I don't want any of this nonsense </span>
which is unfortunately what I am getting right now...
How would I do this?

With js only:
Try it out: http://jsfiddle.net/g4tRn/
var result = document.getElementById('thisone').firstChild.nodeValue;
​alert(result);​
With jQuery:
Try it out: http://jsfiddle.net/g4tRn/1
var result = $('#thisone').contents().first().text();
alert(result);​
Bonus:
If there are other text nodes in the outer <span> that you want to get, you could do something like this:
Try it out: http://jsfiddle.net/g4tRn/4
var nodes = document.getElementById('thisone').childNodes;
var result = '';
for(var i = 0; i < nodes.length; i++) {
if(nodes[i].nodeType == 3) { // If it is a text node,
result += nodes[i].nodeValue; // add its text to the result
}
}
alert(result);
​

If you just want the first child then it's rather simple. If you are looking for the first text-only element then this code will need some modification.
var text = document.getElementById('thisone').firstChild.nodeValue;
alert(text);

Have you tried something like this?
var thisone = $("#thisone").clone();
thisone.children().remove();
var mytext = thisone.html();

FROM: http://viralpatel.net/blogs/2011/02/jquery-get-text-element-without-child-element.html
$("#foo")
.clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.text(); //get the text of element

Pure JavaScript
In this pure JavaScript example, I account for the possibility of multiple text nodes that could be interleaved with other kinds of nodes. Pass a containing NodeList in from calling / client code.
function getText (nodeList, target)
{
var trueTarget = target - 1;
var length = nodeList.length; // Because you may have many child nodes.
for (var i = 0; i < length; i++) {
if ((nodeList[i].nodeType === Node.TEXT_NODE) && (i === trueTarget)) {
return nodeList.childNodes[i].nodeValue;
}
}
return null;
}
You might use this function to create a wrapper function that uses this one to accumulate multiple text values.

To get a string of the child text nodes and not the element or other child nodes from a given element:
function getTextNodesText(el) {
return Array.from(el.childNodes)
.filter((child) => child.nodeType === Node.TEXT_NODE)
.map((child) => child.textContent)
.join("");
}

Related

Remove Text from Element Without Removing Reference

I would like to replace all the text in some element (including text in children) with some other text. For example, the html
<div id="myText">
This is some text.
This is some other text.
<p id="toHide">
This is even more text.
Click this text to hide it.
</p>
</div>
should become
<div id="myText">
That is some text.
That is some other text.
<p id="toHide">
That is even more text.
Click That text to hide it.
</p>
</div>
Essentially, I've replaced all of /this/gi with "That". However, I cannot use the following:
$("#myText").innerHTML = $("#myText").innerHTML.replace(/this/gi, "");
This is because I keep a lot of references to the children of myText. This references will be erased. I realize that in simple cases, I can just update these references, but I have a fairly large file, and many references (and it would be troublesome and error prone to have to update every reference every time this function is called).
I also store some data not visible to innerHTML. For example, I use
$("#toHide").test = "test";
This is lost when writing to innerHTML.
How can I replace text in a div without innerHTML (preferably without jquery)?
Jsfiddle http://jsfiddle.net/prankol57/ZEfM7/
Here's a solution:
var n, walker = document.createTreeWalker(document.getElementById("myText"), NodeFilter.SHOW_TEXT);
while (n = walker.nextNode()) {
n.nodeValue = n.nodeValue.replace(/this/ig, "that");
}
Basically, walk all the text nodes, and substitute their values.
For better compatibility, here's some reusable code:
function visitTextNodes(el, callback) {
if (el.nodeType === 3) {
callback(el);
}
for (var i=0; i < el.childNodes.length; ++i) {
visitTextNodes(el.childNodes[i], callback);
}
}
Then you can do:
visitTextNodes(document.getElementById("myText"), function(el) {
el.nodeValue = el.nodeValue.replace(/this/ig, "that");
});
You can use DOM methods (a.k.a. the old and safe way)
function replaceText(el, pattern, txt) {
for(var i=0; i<el.childNodes.length; ++i) {
var node = el.childNodes[i];
switch(node.nodeType){
case 1: // Element
replaceText(node, pattern, txt); continue;
case 3: // Text node
node.nodeValue = node.nodeValue.replace(/this/gi, "that"); continue;
}
}
}
Demo
Here my version of replaceText:
function replaceText(elem) {
if(elem.nodeType === Node.TEXT_NODE) {
elem.nodeValue = elem.nodeValue.replace(/this/gi, 'that')
return
}
var children = elem.childNodes
for(var i = 0, len = children.length; i < len; ++i)
replaceText(children[i]);
}
NB this take an element as the first parameter and traverse all children, hence it works even with complex elements.
Here the updated fiddle: http://jsfiddle.net/ZEfM7/6/

CSS styling a single character within a word

My client has asked for the letter 4 to appear in red, wherever it is used in his website navigation.
For instance, where he has 'bikes4kids' as a menu item.
Unfortunately, I am using a 'mega menu' style plugin for his Magento site that only allows for plain text menu items - I cannot use HTML code in the menu item title box, which takes away the chance of me using <span>.
Is there a way of achieving this with JS? I assume not with CSS alone.
EDIT: The mega menu I am working with can be seen here: http://www.magentech.com/extensions/commercial-extensions/item/246-sm-mega-menu-responsive-magento-module
I did it.
Please have a look at this Link
<div class="title">menu1</div>
<div class="title">bike4kids</div>
<div class="title">menu2</div>
var avno = $(".title:nth-child(2)").text();
var avn = avno.split('4');
var item = avn[0]+"<span style='color:red'>4</span>"+avn[1];
$(".title:nth-child(2)").html(item);
No, within “plain text menu items” (as described in the question) you cannot style one character differently from others (except in a few very special cases, which do not apply here: styling the first letter, and setting the font of some characters different from others). JavaScript won’t help, because you would still need to make the character an element, and anything containing an element is by definition not plain text.
So you need to consider other approaches, like menus with items that allow some markup.
If you can process the document after it's finished loading, or sometime after magento has finished doing its thing, you can try the following. It will wrap a provided character in a span with a supplied class. A root element can be provided to limit the scope of the replace. If no root is provided, it searches the entire document.
// Simple function to convert NodeList to Array
// Not suitable for general application
function toArray(obj) {
var a = [];
for (var i=0, iLen=obj.length; i<iLen; i++) {
a[i] = obj[i];
}
return a;
}
// Highlight character c by wrapping in a span with class className
// starting with element root. If root not provided, document.body is used
function highlightChar(c, className, root) {
if (!root) root = document.body;
var frag, idx, t;
var re = new RegExp(c);
// Add tag names to ignore
var ignoreTags = {'script':'script'};
// Child nodes is a live NodeList, convert to array
// so don't have to deal with changing as nodes are added
var node, nodes = toArray(root.childNodes);
var span = document.createElement('span');
span.appendChild(document.createTextNode(c));
span.className = 'highlightChar';
for (var i=0, iLen=nodes.length; i<iLen; i++) {
node = nodes[i];
// If node is a text node and contains the chacter, highlight it
if (node.nodeType == 3 && re.test(node.data)) {
t = node.data.split(re);
frag = document.createDocumentFragment();
// Insert higlight spans after first but not after last
for (var j=0, jLen = t.length-1; j<jLen; j++) {
frag.appendChild(document.createTextNode(t[j]));
frag.appendChild(span.cloneNode(true));
}
// Append last text node
if (j > 0 && t[j]) {
frag.appendChild(document.createTextNode(t[j]));
}
// Replace the original text node with higlighted fragment
node.parentNode.replaceChild(frag, node);
// Otherwise, if node is an element, process it
} else if (node.nodeType == 1 && !(node.tagName.toLowerCase() in ignoreTags)) {
highlightChar(c, className, node);
}
}
}
It can be used to process the entire document using:
window.onload = function() {
highlightChar('4','highlightChar');
};
Edit:
Modified to find menu-items in 'mega menu'... I hope. In the demo site the "$" variable isn't jQuery so I modified the answer as well to use the jQuery function.
Testing in the demo site I found that the letter I modified did color yellow, but there was a bullet added to the left of it - apparently their css adds a bullet to the left (ie. :before) every span...
After the plugin completes its DOM modifications - simply run over the menu items and search-and-replace "4" with a colored span
eg.
// loop over all dom elements with class 'menu-item'
// - I assume here below them exist only text
jQuery('.sm-megamenu-child span').each(function() {
var $item = jQuery(this);
var text = $item.text();
var modified = text.replace(/4/g, "<span style='color:yellow'>4</span>");
$item.html(modified);
})

Replacing content of html document

I am trying to replace a word in an html document with selected word using javascript.
JavaScript
var node=document.body;
var childs=node.childNodes;
var n=childs.length,i=0;
while (i < n) {
node=childs[i];
if (node.nodeType == 3) {
if (node.textContent) {
node.nodeValue=node.nodeValue.replace("injected","hai");
}
}
i++;
}
but string is not getting replaced...pls help
Add document.body=node; at the end. When you set node to equal body you are copying the value, not editing it by reference.
I'm not sure why you're trying to work with the text node directly. console.log on nodeValue shows that the textContent of displayed tags is neither retrieved nor set in your code.
This works great. Live demo here (click).
<p>something to be replaced.</p>
and the js:
var childs = document.body.childNodes;
var len = childs.length;
for (var i=0; i<len; ++i) {
var node=childs[i];
if (node.nodeName === 'P') {
node.textContent = node.textContent.replace("to be replaced","was replaced");
}
}
There is a much simpler method using the String replace method. For example, you can convert the body of the page into a string and use regular expressions to replace the word. This means that you can avoid having to traverse the entire DOM and node lists, which is unnecessarily slow for your task.
document.getElementByTagName("body")[0].innerHTML.replace("injected","hai")

How to get child element by class name?

I'm trying to get the child span that has a class = 4. Here is an example element:
<div id="test">
<span class="one"></span>
<span class="two"></span>
<span class="three"></span>
<span class="four"></span>
</div>
The tools I have available are JS and YUI2. I can do something like this:
doc = document.getElementById('test');
notes = doc.getElementsByClassName('four');
//or
doc = YAHOO.util.Dom.get('#test');
notes = doc.getElementsByClassName('four');
These do not work in IE. I get an error that the object (doc) doesn't support this method or property (getElementsByClassName). I've tried a few examples of cross browser implementations of getElementsByClassName but I could not get them to work and still got that error.
I think what I need is a cross browser getElementsByClassName or I need to use doc.getElementsByTagName('span') and loop through until I find class 4. I'm not sure how to do that though.
Use querySelector and querySelectorAll
var testContainer = document.querySelector('#test');
var fourChildNode = testContainer.querySelector('.four');
IE9 and upper
Use doc.childNodes to iterate through each span, and then filter the one whose className equals 4:
var doc = document.getElementById("test");
var notes = null;
for (var i = 0; i < doc.childNodes.length; i++) {
if (doc.childNodes[i].className == "4") {
notes = doc.childNodes[i];
break;
}
}
​
The accepted answer only checks immediate children. Often times we're looking for any descendants with that class name.
Also, sometimes we want any child that contains a className.
For example: <div class="img square"></div> should match a search on className "img", even though it's exact className is not "img".
Here's a solution for both of these issues:
Find the first instance of a descendant element with the class className
function findFirstChildByClass(element, className) {
var foundElement = null, found;
function recurse(element, className, found) {
for (var i = 0; i < element.childNodes.length && !found; i++) {
var el = element.childNodes[i];
var classes = el.className != undefined? el.className.split(" ") : [];
for (var j = 0, jl = classes.length; j < jl; j++) {
if (classes[j] == className) {
found = true;
foundElement = element.childNodes[i];
break;
}
}
if(found)
break;
recurse(element.childNodes[i], className, found);
}
}
recurse(element, className, false);
return foundElement;
}
Use element.querySelector().
Lets assume:
'myElement' is the parent element you already have.
'sonClassName' is the class of the child you are looking for.
let child = myElement.querySelector('.sonClassName');
For more info, visit: https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector
Modern solution
const context = document.getElementById('context');
const selected = context.querySelectorAll(':scope > div');
documentation
You could try:
notes = doc.querySelectorAll('.4');
or
notes = doc.getElementsByTagName('*');
for (var i = 0; i < notes.length; i++) {
if (notes[i].getAttribute('class') == '4') {
}
}
To me it seems like you want the fourth span. If so, you can just do this:
document.getElementById("test").childNodes[3]
or
document.getElementById("test").getElementsByTagName("span")[3]
This last one ensures that there are not any hidden nodes that could mess it up.
Use the name of the id with the getElementById, no # sign before it. Then you can get the span child nodes using getElementsByTagName, and loop through them to find the one with the right class:
var doc = document.getElementById('test');
var c = doc.getElementsByTagName('span');
var e = null;
for (var i = 0; i < c.length; i++) {
if (c[i].className == '4') {
e = c[i];
break;
}
}
if (e != null) {
alert(e.innerHTML);
}
Demo: http://jsfiddle.net/Guffa/xB62U/
But be aware that old browsers doesn't support getElementsByClassName.
Then, you can do
function getElementsByClassName(c,el){
if(typeof el=='string'){el=document.getElementById(el);}
if(!el){el=document;}
if(el.getElementsByClassName){return el.getElementsByClassName(c);}
var arr=[],
allEls=el.getElementsByTagName('*');
for(var i=0;i<allEls.length;i++){
if(allEls[i].className.split(' ').indexOf(c)>-1){arr.push(allEls[i])}
}
return arr;
}
getElementsByClassName('4','test')[0];
It seems it works, but be aware that an HTML class
Must begin with a letter: A-Z or a-z
Can be followed by letters (A-Za-z), digits (0-9), hyphens ("-"), and underscores ("_")
In my opinion, each time you can, you should use Array and its methods. They are much, much faster then looping over the whole DOM / wrapper, or pushing stuff into empty array. Majority of solutions presented here you can call Naive as described here (great article btw):
https://medium.com/#chuckdries/traversing-the-dom-with-filter-map-and-arrow-functions-1417d326d2bc
My solution: (live preview on Codepen: https://codepen.io/Nikolaus91/pen/wEGEYe)
const wrapper = document.getElementById('test') // take a wrapper by ID -> fastest
const itemsArray = Array.from(wrapper.children) // make Array from his children
const pickOne = itemsArray.map(item => { // loop over his children using .map() --> see MDN for more
if(item.classList.contains('four')) // we place a test where we determine our choice
item.classList.add('the-chosen-one') // your code here
})
using querySelector
var doc=document.getElementById("test");
console.log(doc.querySelector('.two').innerHTML)
<div id="test">
<span class="one"></span>
<span class="two">two</span>
<span class="three"></span>
<span class="four"></span>
</div>
Using querySelectorAll
var doc=document.getElementById("test");
console.log(doc.querySelectorAll('*')[1].innerHTML)
<div id="test">
<span class="one"></span>
<span class="two">two</span>
<span class="three"></span>
<span class="four"></span>
</div>
using getElementsByTagNames
var doc=document.getElementById("test");
console.log(doc.getElementsByTagName("SPAN")[1].innerHTML);
<div id="test">
<span class="one"></span>
<span class="two">two</span>
<span class="three"></span>
<span class="four"></span>
</div>
<span>ss</span>
Using getElementsByClassName
var doc=document.getElementById("test");
console.log(doc.getElementsByClassName('two')[0].innerHTML)
<div id="test">
<span class="one"></span>
<span class="two">two</span>
<span class="three"></span>
<span class="four"></span>
</div>
I believe this would answer your question best
document.querySelector('* > span.four')
This will match the first child element (of any parent) it finds that is a span and also has a class "four" set to it
However since in your example you also had a parent element which you are able to retrieve by id, you could also use this instead
document.querySelector('#test > span.four')
If you have a parent element saved in a variable like in your example, and you wish to search the subtree of that element, using :scope, as Billizzard has mentioned already, is probably your best choice
doc.querySelector(':scope > span.four');
Little extra: If the child element you are looking for isn't a direct child descendent, but somewhere further down the subtree, you can actually just omit the > like so
document.querySelector('#test span.four')
The way i will do this using jquery is something like this..
var targetedchild = $("#test").children().find("span.four");
You can fetch the parent class by adding the line below. If you had an id, it would be easier with getElementById. Nonetheless,
var parentNode = document.getElementsByClassName("progress__container")[0];
Then you can use querySelectorAll on the parent <div> to fetch all matching divs with class .progress__marker
var progressNodes = progressContainer.querySelectorAll('.progress__marker');
querySelectorAll will fetch every div with the class of progress__marker
Another way
const result = [...(parentElement.children)].find(child => {
return child.classList.contains('some-class-name');
});
First we spread the elements of the NodeList to turn it into an Array so we can make use of the find() method. Lastly, find() will return to us the first element whose classList property contains the given class name.
Here is a relatively simple recursive solution. I think a breadth-first search is appropriate here. This will return the first element matching the class that is found.
function getDescendantWithClass(element, clName) {
var children = element.childNodes;
for (var i = 0; i < children.length; i++) {
if (children[i].className &&
children[i].className.split(' ').indexOf(clName) >= 0) {
return children[i];
}
}
for (var i = 0; i < children.length; i++) {
var match = getDescendantWithClass(children[i], clName);
if (match !== null) {
return match;
}
}
return null;
}
I know this question is a few years old and there have been a few answers to this but I thought I would add my solution just in case it helps anyone. It's in the same vein as the answer given by user2795540 and involves an array iterator.
If you're just wanting to get the first child that has the four class then you could use the find array iterator. Your browser will need to be able to support ES6 or you can use Babel to compile your JS into something all browsers will support. IE will not support this without a polyfill.
Using the same details you provided in your question it could look something like this:
const parentNode = document.getElementById('test');
const childNode = Array.from(parentNode.childNodes).find(({ className }) => className === 'four');
The above solution will return the node you want to target and store it in the childNode variable.
You can find out more about the find array iterator at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
June 2018 update to ES6
const doc = document.getElementById('test');
let notes = null;
for (const value of doc) {
if (value.className === '4') {
notes = value;
break;
}
}
let notes = document.querySelector('#test .four')
YUI2 has a cross-browser implementation of getElementsByClassName.
Here is how I did it using the YUI selectors. Thanks to Hank Gay's suggestion.
notes = YAHOO.util.Dom.getElementsByClassName('four','span','test');
where four = classname, span = the element type/tag name, and test = the parent id.
Use YAHOO.util.Dom.getElementsByClassName() from here.

Why does `childNodes` return a number larger than I expect?

Could you please look at this jsFiddle example, and tell me why the number '11' is alerted rather than '5' (the number of <li> elements)?
From jsFiddle:
HTML
<ul id="list">
<li>milk</li>
<li>butter</li>
<li>eggs</li>
<li>orange juice</li>
<li>bananas</li>
</ul>
JavaScript
var list = document.getElementById('list');
var list_items = list.childNodes;
alert(list_items.length);
The childNodes, depending on the browser used, will return the text nodes, as well as the tags that are children of the parent node. So technically, the whitespace in between the <li> tags will also be counted among the childNodes.
To avoid processing them, you may check that nodeType != 3. Here is a list of node types.
var list = document.getElementById('list');
var list_items = list.childNodes;
var li_items = [];
for (var i=0; i<list_items.length; i++) {
console.log(list_items[i].nodeType);
// Add all the <li> nodes to an array, skip the text nodes
if (list_items[i].nodeType != 3) {
li_items.push(list_items[i]);
}
}
You have text nodes there.
You can skip them while iterating with...
for (var i = 0, length = list_items.length; i < length; i++) {
if (list_items[i].nodeType != 1) {
continue;
}
// Any code here that accesses list_items[i] will sure to be an element.
}
jsFiddle.
Alternatively, you could do it in a more functional way...
list_items = Array.prototype.filter.call(list_items, function(element) {
return element.nodeType == 1;
});
jsFiddle.
You must use convert it to a proper array to use the filter() method. childNodes property returns a NodeList object.
As others have pointed out, the childNode count inclues the text nodes, generated by the whitespace between the <li> elements.
<ul id="list"><li>milk</li><li>butter</li><li>eggs</li><li>orange juice</li><li>bananas</li></ul>
That will give you 5 childNodes because it omits the whitespace.
Text nodes are included in the child nodes count. To get the proper value, you'd need to strip out text nodes, or make sure they are not in your code. Any white space between code is considered a space and a text node, so your count is the total number of text nodes.
I cobbled together a solution for this that I like. (I got the idea from this blog post.)
1) First I get the number of child elements nodes by using:
nodeObject.childElementCount;
2) Then I wrote a function that will return any child element node by index number. I did this by using firstElementChild and nextElementSibling in a for loop.
function getElement(x, parentNode){
var item = parentNode.firstElementChild
for (i=0;i<x;i++){
item = item.nextElementSibling;
}
return item;
}
This returns the child element I need for anything I want to pull from it. It skips the problem with childNodes retuning all the different nodes that are not helpful when trying to parse just the elements. I am sure someone more experienced than me could clean this up. But I found this so helpful that I had to post it.
Use obj.children instead.
var list = document.getElementById('list');
var list_items = list.children;
alert(list_items.length);
The difference between this children and childNodes, is that childNodes contain all nodes, including text nodes and comment nodes, while children only contain element nodes.
from w3schools.

Categories