jQuery .not() function not working within .parent() function [duplicate] - javascript
If I have html like this:
<li id="listItem">
This is some text
<span id="firstSpan">First span text</span>
<span id="secondSpan">Second span text</span>
</li>
I'm trying to use .text() to retrieve just the string "This is some text", but if I were to say $('#list-item').text(), I get "This is some textFirst span textSecond span text".
Is there a way to get (and possibly remove, via something like .text("")) just the free text within a tag, and not the text within its child tags?
The HTML was not written by me, so this is what I have to work with. I know that it would be simple to just wrap the text in tags when writing the html, but again, the html is pre-written.
I liked this reusable implementation based on the clone() method found here to get only the text inside the parent element.
Code provided for easy reference:
$("#foo")
.clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.text();
Simple answer:
$("#listItem").contents().filter(function(){
return this.nodeType == 3;
})[0].nodeValue = "The text you want to replace with"
This seems like a case of overusing jquery to me. The following will grab the text ignoring the other nodes:
document.getElementById("listItem").childNodes[0];
You'll need to trim that but it gets you what you want in one, easy line.
EDIT
The above will get the text node. To get the actual text, use this:
document.getElementById("listItem").childNodes[0].nodeValue;
Easier and quicker:
$("#listItem").contents().get(0).nodeValue
Similar to the accepted answer, but without cloning:
$("#foo").contents().not($("#foo").children()).text();
And here is a jQuery plugin for this purpose:
$.fn.immediateText = function() {
return this.contents().not(this.children()).text();
};
Here is how to use this plugin:
$("#foo").immediateText(); // get the text without children
isn't the code:
var text = $('#listItem').clone().children().remove().end().text();
just becoming jQuery for jQuery's sake? When simple operations involve that many chained commands & that much (unnecessary) processing, perhaps it is time to write a jQuery extension:
(function ($) {
function elementText(el, separator) {
var textContents = [];
for(var chld = el.firstChild; chld; chld = chld.nextSibling) {
if (chld.nodeType == 3) {
textContents.push(chld.nodeValue);
}
}
return textContents.join(separator);
}
$.fn.textNotChild = function(elementSeparator, nodeSeparator) {
if (arguments.length<2){nodeSeparator="";}
if (arguments.length<1){elementSeparator="";}
return $.map(this, function(el){
return elementText(el,nodeSeparator);
}).join(elementSeparator);
}
} (jQuery));
to call:
var text = $('#listItem').textNotChild();
the arguments are in case a different scenario is encountered, such as
<li>some text<a>more text</a>again more</li>
<li>second text<a>more text</a>again more</li>
var text = $("li").textNotChild(".....","<break>");
text will have value:
some text<break>again more.....second text<break>again more
Try this:
$('#listItem').not($('#listItem').children()).text()
It'll need to be something tailored to the needs, which are dependent on the structure you're presented with. For the example you've provided, this works:
$(document).ready(function(){
var $tmp = $('#listItem').children().remove();
$('#listItem').text('').append($tmp);
});
Demo: http://jquery.nodnod.net/cases/2385/run
But it's fairly dependent on the markup being similar to what you posted.
$($('#listItem').contents()[0]).text()
Short variant of Stuart answer.
or with get()
$($('#listItem').contents().get(0)).text()
I presume this would be a fine solution also - if you want to get contents of all text nodes that are direct children of selected element.
$(selector).contents().filter(function(){ return this.nodeType == 3; }).text();
Note: jQuery documentation uses similar code to explain contents function: https://api.jquery.com/contents/
P.S. There's also a bit uglier way to do that, but this shows more in depth how things work, and allows for custom separator between text nodes (maybe you want a line break there)
$(selector).contents().filter(function(){ return this.nodeType == 3; }).map(function() { return this.nodeValue; }).toArray().join("");
jQuery.fn.ownText = function () {
return $(this).contents().filter(function () {
return this.nodeType === Node.TEXT_NODE;
}).text();
};
If the position index of the text node is fixed among its siblings, you can use
$('parentselector').contents().eq(index).text()
This is an old question but the top answer is very inefficient. Here's a better solution:
$.fn.myText = function() {
var str = '';
this.contents().each(function() {
if (this.nodeType == 3) {
str += this.textContent || this.innerText || '';
}
});
return str;
};
And just do this:
$("#foo").myText();
I propose to use the createTreeWalker to find all texts elements not attached to html elements (this function can be used to extend jQuery):
function textNodesOnlyUnder(el) {
var resultSet = [];
var n = null;
var treeWalker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, function (node) {
if (node.parentNode.id == el.id && node.textContent.trim().length != 0) {
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_SKIP;
}, false);
while (n = treeWalker.nextNode()) {
resultSet.push(n);
}
return resultSet;
}
window.onload = function() {
var ele = document.getElementById('listItem');
var textNodesOnly = textNodesOnlyUnder(ele);
var resultingText = textNodesOnly.map(function(val, index, arr) {
return 'Text element N. ' + index + ' --> ' + val.textContent.trim();
}).join('\n');
document.getElementById('txtArea').value = resultingText;
}
<li id="listItem">
This is some text
<span id="firstSpan">First span text</span>
<span id="secondSpan">Second span text</span>
</li>
<textarea id="txtArea" style="width: 400px;height: 200px;"></textarea>
I wouldn't bother with jQuery for this, especially not the solutions that make unnecessary clones of the elements. A simple loop grabbing text nodes is all you need. In modern JavaScript (as of this writing — "modern" is a moving target!) and trimming whitespace from the beginning and end of the result:
const { childNodes } = document.getElementById("listItem");
let text = "";
for (const node of childNodes) {
if (node.nodeType === Node.TEXT_NODE) {
text += node.nodeValue;
}
}
text = text.trim();
Live Example:
const { childNodes } = document.getElementById("listItem");
let text = "";
for (const node of childNodes) {
if (node.nodeType === Node.TEXT_NODE) {
text += node.nodeValue;
}
}
console.log(text);
<li id="listItem">
This is some text
<span id="firstSpan">First span text</span>
<span id="secondSpan">Second span text</span>
</li>
Some people would use reduce for this. I'm not a fan, I think a simple loop is clearer, but this usage does update the accumulator on each iteration, so it's not actually abusing reduce:
const { childNodes } = document.getElementById("listItem");
const text = [...childNodes].reduce((text, node) =>
node.nodeType === Node.TEXT_NODE ? text + node.nodeValue : text
, "").trim();
const { childNodes } = document.getElementById("listItem");
const text = [...childNodes].reduce((text, node) =>
node.nodeType === Node.TEXT_NODE ? text + node.nodeValue : text
, "").trim();
console.log(text);
<li id="listItem">
This is some text
<span id="firstSpan">First span text</span>
<span id="secondSpan">Second span text</span>
</li>
Or without creating a temporary array:
const { childNodes } = document.getElementById("listItem");
const text = Array.prototype.reduce.call(childNodes, (text, node) =>
node.nodeType === Node.TEXT_NODE ? text + node.nodeValue : text
, "").trim();
const { childNodes } = document.getElementById("listItem");
const text = Array.prototype.reduce.call(childNodes, (text, node) =>
node.nodeType === Node.TEXT_NODE ? text + node.nodeValue : text
, "").trim();
console.log(text);
<li id="listItem">
This is some text
<span id="firstSpan">First span text</span>
<span id="secondSpan">Second span text</span>
</li>
Using plain JavaScript in IE 9+ compatible syntax in just a few lines:
const childNodes = document.querySelector('#listItem').childNodes;
if (childNodes.length > 0) {
childNodesLoop:
for (let i = 0; i < childNodes.length; i++) {
//only target text nodes (nodeType of 3)
if (childNodes[i].nodeType === 3) {
//do not target any whitespace in the HTML
if (childNodes[i].nodeValue.trim().length > 0) {
childNodes[i].nodeValue = 'Replacement text';
//optimized to break out of the loop once primary text node found
break childNodesLoop;
}
}
}
}
Get all text in an element without text in any child elements still seems non trivial to do in 2022.
No jQuery needed though.
To get all raw textNode(s) content:
const getElementTextWithoutChildElements = (el) =>
Array.from(el.childNodes) // iterator to array
.filter(node => node.nodeType === 3) // only text nodes
.map(node => node.textContent) // get text
.join('') // stick together
;
Or similar, using reduce:
const getElementTextWithoutChildElements = (el) =>
[].reduce.call(
el.childNodes,
(a, b) => a + (b.nodeType === 3 ? b.textContent : ''),
''
);
Should work with this:
<div>
you get this
<b>not this</b>
you get this too
</div>
will return:
you get this
you get this too
Whitespace between elements could be tricky, suggest using with .trim() and/or normalize all whitespace, e.g.
For debugging and logging to quickly identify elements I find this is usually enough:
getElementTextWithoutChildElements(...).replace(/\s+/g, ' ').trim();
// 'you get this you get this too'
Though you might want to tweak whitespace differently, perhaps within the reduce() function itself to handle whitespace per node.
e.g. whitespace handling per node:
const getElementTextWithoutChildElements_2 = (el) =>
Array.from(el.childNodes)
.filter(node => node.nodeType === 3)
.map(node => node.textContent.trim()) // added .trim()
.join(',') // added ','
;
Quick tests for things above:
document.body.innerHTML = `
you get this
<b>not this</b>
you get this too
`;
// '\n you get this\n <b>not this</b>\n you get this too\n'
getElementTextWithoutChildElements(document.body);
// '\n you get this\n \n you get this too\n'
getElementTextWithoutChildElements(document.body).replace(/\s+/g, ' ').trim();
// 'you get this you get this too'
getElementTextWithoutChildElements_2(document.body);
// 'you get this,you get this too'
This is a good way for me
var text = $('#listItem').clone().children().remove().end().text();
I came up with a specific solution that should be much more efficient than the cloning and modifying of the clone. This solution only works with the following two reservations, but should be more efficient than the currently accepted solution:
You are getting only the text
The text you want to extract is before the child elements
With that said, here is the code:
// 'element' is a jQuery element
function getText(element) {
var text = element.text();
var childLength = element.children().text().length;
return text.slice(0, text.length - childLength);
}
Live demo
<li id="listItem">
This is some text
<span id="firstSpan">First span text</span>
<span id="secondSpan">Second span text</span>
</li>
<input id="input" style="width: 300px; margin-top: 10px;">
<script type="text/javascript">
$("#input").val($("#listItem").clone().find("span").remove().end().text().trim());
//use .trim() to remove any white space
</script>
For beginners:
I preferred #DUzun's answer because it's simple to understand and more efficient than the accepted answer. But it only partially worked for me as you can't directly pass the element with a class selector like this
$(".landing-center .articlelanding_detail").get(0).immediateText() //gives .immediateText is not a function error
or this
$(".landing-center .articlelanding_detail")[0].immediateText() //gives .immediateText is not a function error
because once you extract the native Element by using [index] or .get(index) out of the $() function you loose jQuery Object methods chainability as mentioned here. And most of the solutions are only in context to ids, not so elegant to use multiple times for the elements with a class selectors.
So, I wrote jQuery plugin:
$.fn.mainText = function(x=0) {
return $.trim(this.eq(x).contents().not(this.eq(x).children()).text().replace(/[\t\n]+/g,' '));
};
This will return the text of the element irrespective of if ids or class are used as selectors excluding child elements. Also will remove any \t or \n to get a clean string.
Use it like this:
Case 1
$("#example").mainText(); // get the text of element with example id
Case 2
$(".example").mainText(); // get the text of first element with example class
Case 3
$(".example").mainText(1); // get the text of second element with example class and so on..
Alternative version of the answere without JQuery
[...document.getElementById("listItem").childNodes].find(c => c.nodeType === Node.TEXT_NODE).nodeValue
Just like the question, I was trying to extract text in order to do some regex substitution of the text but was getting problems where my inner elements (ie: <i>, <div>, <span>, etc.) were getting also removed.
The following code seems to work well and solved all my problems.
It uses some of the answers provided here but in particular, will only substitute the text when the element is of nodeType === 3.
$(el).contents().each(function() {
console.log(" > Content: %s [%s]", this, (this.nodeType === 3));
if (this.nodeType === 3) {
var text = this.textContent;
console.log(" > Old : '%s'", text);
regex = new RegExp("\\[\\[" + rule + "\\.val\\]\\]", "g");
text = text.replace(regex, value);
regex = new RegExp("\\[\\[" + rule + "\\.act\\]\\]", "g");
text = text.replace(regex, actual);
console.log(" > New : '%s'", text);
this.textContent = text;
}
});
What the above does is loop through all the elements of the given el (which was simply obtained with $("div.my-class[name='some-name']");. For each inner element, it basically ignores them. For each portion of text (as determined by if (this.nodeType === 3)) it will apply the regex substitution only to those elements.
The this.textContent = text portion simply replaces the substituted text, which in my case, I was looking for tokens like [[min.val]], [[max.val]], etc.
This short code excerpt will help anyone trying to do what the question was asking ... and a bit more.
Not sure how flexible or how many cases you need it to cover, but for your example, if the text always comes before the first HTML tags – why not just split the inner html at the first tag and take the former:
$('#listItem').html().split('<span')[0];
and if you need it wider maybe just
$('#listItem').html().split('<')[0];
and if you need the text between two markers, like after one thing but before another, you can do something like (untested) and use if statements to make it flexible enough to have a start or end marker or both, while avoiding null ref errors:
var startMarker = '';// put any starting marker here
var endMarker = '<';// put the end marker here
var myText = String( $('#listItem').html() );
// if the start marker is found, take the string after it
myText = myText.split(startMarker)[1];
// if the end marker is found, take the string before it
myText = myText.split(endMarker)[0];
console.log(myText); // output text between the first occurrence of the markers, assuming both markers exist. If they don't this will throw an error, so some if statements to check params is probably in order...
I generally make utility functions for useful things like this, make them error free, and then rely on them frequently once solid, rather than always rewriting this type of string manipulation and risking null references etc. That way, you can re-use the function in lots of projects and never have to waste time on it again debugging why a string reference has an undefined reference error. Might not be the shortest 1 line code ever, but after you have the utility function, it is one line from then on. Note most of the code is just handling parameters being there or not to avoid errors :)
For example:
/**
* Get the text between two string markers.
**/
function textBetween(__string,__startMark,__endMark){
var hasText = typeof __string !== 'undefined' && __string.length > 0;
if(!hasText) return __string;
var myText = String( __string );
var hasStartMarker = typeof __startMark !== 'undefined' && __startMark.length > 0 && __string.indexOf(__startMark)>=0;
var hasEndMarker = typeof __endMark !== 'undefined' && __endMark.length > 0 && __string.indexOf(__endMark) > 0;
if( hasStartMarker ) myText = myText.split(__startMark)[1];
if( hasEndMarker ) myText = myText.split(__endMark)[0];
return myText;
}
// now with 1 line from now on, and no jquery needed really, but to use your example:
var textWithNoHTML = textBetween( $('#listItem').html(), '', '<'); // should return text before first child HTML tag if the text is on page (use document ready etc)
Use an extra condition to check if innerHTML and innerText are the same. Only in those cases, replace the text.
$(function() {
$('body *').each(function () {
console.log($(this).html());
console.log($(this).text());
if($(this).text() === "Search" && $(this).html()===$(this).text()) {
$(this).html("Find");
}
})
})
http://jsfiddle.net/7RSGh/
To be able to trim the result, use DotNetWala's like so:
$("#foo")
.clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.text()
.trim();
I found out that using the shorter version like document.getElementById("listItem").childNodes[0] won't work with jQuery's trim().
just put it in a <p> or <font> and grab that $('#listItem font').text()
First thing that came to mind
<li id="listItem">
<font>This is some text</font>
<span id="firstSpan">First span text</span>
<span id="secondSpan">Second span text</span>
</li>
You can try this
alert(document.getElementById('listItem').firstChild.data)
I am not a jquery expert, but how about,
$('#listItem').children().first().text()
This untested, but I think you may be able to try something like this:
$('#listItem').not('span').text();
http://api.jquery.com/not/
Related
How to traverse just the first level of <LI> elements `with no class tag` [duplicate]
If I have html like this: <li id="listItem"> This is some text <span id="firstSpan">First span text</span> <span id="secondSpan">Second span text</span> </li> I'm trying to use .text() to retrieve just the string "This is some text", but if I were to say $('#list-item').text(), I get "This is some textFirst span textSecond span text". Is there a way to get (and possibly remove, via something like .text("")) just the free text within a tag, and not the text within its child tags? The HTML was not written by me, so this is what I have to work with. I know that it would be simple to just wrap the text in tags when writing the html, but again, the html is pre-written.
I liked this reusable implementation based on the clone() method found here to get only the text inside the parent element. Code provided for easy reference: $("#foo") .clone() //clone the element .children() //select all the children .remove() //remove all the children .end() //again go back to selected element .text();
Simple answer: $("#listItem").contents().filter(function(){ return this.nodeType == 3; })[0].nodeValue = "The text you want to replace with"
This seems like a case of overusing jquery to me. The following will grab the text ignoring the other nodes: document.getElementById("listItem").childNodes[0]; You'll need to trim that but it gets you what you want in one, easy line. EDIT The above will get the text node. To get the actual text, use this: document.getElementById("listItem").childNodes[0].nodeValue;
Easier and quicker: $("#listItem").contents().get(0).nodeValue
Similar to the accepted answer, but without cloning: $("#foo").contents().not($("#foo").children()).text(); And here is a jQuery plugin for this purpose: $.fn.immediateText = function() { return this.contents().not(this.children()).text(); }; Here is how to use this plugin: $("#foo").immediateText(); // get the text without children
isn't the code: var text = $('#listItem').clone().children().remove().end().text(); just becoming jQuery for jQuery's sake? When simple operations involve that many chained commands & that much (unnecessary) processing, perhaps it is time to write a jQuery extension: (function ($) { function elementText(el, separator) { var textContents = []; for(var chld = el.firstChild; chld; chld = chld.nextSibling) { if (chld.nodeType == 3) { textContents.push(chld.nodeValue); } } return textContents.join(separator); } $.fn.textNotChild = function(elementSeparator, nodeSeparator) { if (arguments.length<2){nodeSeparator="";} if (arguments.length<1){elementSeparator="";} return $.map(this, function(el){ return elementText(el,nodeSeparator); }).join(elementSeparator); } } (jQuery)); to call: var text = $('#listItem').textNotChild(); the arguments are in case a different scenario is encountered, such as <li>some text<a>more text</a>again more</li> <li>second text<a>more text</a>again more</li> var text = $("li").textNotChild(".....","<break>"); text will have value: some text<break>again more.....second text<break>again more
Try this: $('#listItem').not($('#listItem').children()).text()
It'll need to be something tailored to the needs, which are dependent on the structure you're presented with. For the example you've provided, this works: $(document).ready(function(){ var $tmp = $('#listItem').children().remove(); $('#listItem').text('').append($tmp); }); Demo: http://jquery.nodnod.net/cases/2385/run But it's fairly dependent on the markup being similar to what you posted.
$($('#listItem').contents()[0]).text() Short variant of Stuart answer. or with get() $($('#listItem').contents().get(0)).text()
I presume this would be a fine solution also - if you want to get contents of all text nodes that are direct children of selected element. $(selector).contents().filter(function(){ return this.nodeType == 3; }).text(); Note: jQuery documentation uses similar code to explain contents function: https://api.jquery.com/contents/ P.S. There's also a bit uglier way to do that, but this shows more in depth how things work, and allows for custom separator between text nodes (maybe you want a line break there) $(selector).contents().filter(function(){ return this.nodeType == 3; }).map(function() { return this.nodeValue; }).toArray().join("");
jQuery.fn.ownText = function () { return $(this).contents().filter(function () { return this.nodeType === Node.TEXT_NODE; }).text(); };
If the position index of the text node is fixed among its siblings, you can use $('parentselector').contents().eq(index).text()
This is an old question but the top answer is very inefficient. Here's a better solution: $.fn.myText = function() { var str = ''; this.contents().each(function() { if (this.nodeType == 3) { str += this.textContent || this.innerText || ''; } }); return str; }; And just do this: $("#foo").myText();
I propose to use the createTreeWalker to find all texts elements not attached to html elements (this function can be used to extend jQuery): function textNodesOnlyUnder(el) { var resultSet = []; var n = null; var treeWalker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, function (node) { if (node.parentNode.id == el.id && node.textContent.trim().length != 0) { return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_SKIP; }, false); while (n = treeWalker.nextNode()) { resultSet.push(n); } return resultSet; } window.onload = function() { var ele = document.getElementById('listItem'); var textNodesOnly = textNodesOnlyUnder(ele); var resultingText = textNodesOnly.map(function(val, index, arr) { return 'Text element N. ' + index + ' --> ' + val.textContent.trim(); }).join('\n'); document.getElementById('txtArea').value = resultingText; } <li id="listItem"> This is some text <span id="firstSpan">First span text</span> <span id="secondSpan">Second span text</span> </li> <textarea id="txtArea" style="width: 400px;height: 200px;"></textarea>
I wouldn't bother with jQuery for this, especially not the solutions that make unnecessary clones of the elements. A simple loop grabbing text nodes is all you need. In modern JavaScript (as of this writing — "modern" is a moving target!) and trimming whitespace from the beginning and end of the result: const { childNodes } = document.getElementById("listItem"); let text = ""; for (const node of childNodes) { if (node.nodeType === Node.TEXT_NODE) { text += node.nodeValue; } } text = text.trim(); Live Example: const { childNodes } = document.getElementById("listItem"); let text = ""; for (const node of childNodes) { if (node.nodeType === Node.TEXT_NODE) { text += node.nodeValue; } } console.log(text); <li id="listItem"> This is some text <span id="firstSpan">First span text</span> <span id="secondSpan">Second span text</span> </li> Some people would use reduce for this. I'm not a fan, I think a simple loop is clearer, but this usage does update the accumulator on each iteration, so it's not actually abusing reduce: const { childNodes } = document.getElementById("listItem"); const text = [...childNodes].reduce((text, node) => node.nodeType === Node.TEXT_NODE ? text + node.nodeValue : text , "").trim(); const { childNodes } = document.getElementById("listItem"); const text = [...childNodes].reduce((text, node) => node.nodeType === Node.TEXT_NODE ? text + node.nodeValue : text , "").trim(); console.log(text); <li id="listItem"> This is some text <span id="firstSpan">First span text</span> <span id="secondSpan">Second span text</span> </li> Or without creating a temporary array: const { childNodes } = document.getElementById("listItem"); const text = Array.prototype.reduce.call(childNodes, (text, node) => node.nodeType === Node.TEXT_NODE ? text + node.nodeValue : text , "").trim(); const { childNodes } = document.getElementById("listItem"); const text = Array.prototype.reduce.call(childNodes, (text, node) => node.nodeType === Node.TEXT_NODE ? text + node.nodeValue : text , "").trim(); console.log(text); <li id="listItem"> This is some text <span id="firstSpan">First span text</span> <span id="secondSpan">Second span text</span> </li>
Using plain JavaScript in IE 9+ compatible syntax in just a few lines: const childNodes = document.querySelector('#listItem').childNodes; if (childNodes.length > 0) { childNodesLoop: for (let i = 0; i < childNodes.length; i++) { //only target text nodes (nodeType of 3) if (childNodes[i].nodeType === 3) { //do not target any whitespace in the HTML if (childNodes[i].nodeValue.trim().length > 0) { childNodes[i].nodeValue = 'Replacement text'; //optimized to break out of the loop once primary text node found break childNodesLoop; } } } }
Get all text in an element without text in any child elements still seems non trivial to do in 2022. No jQuery needed though. To get all raw textNode(s) content: const getElementTextWithoutChildElements = (el) => Array.from(el.childNodes) // iterator to array .filter(node => node.nodeType === 3) // only text nodes .map(node => node.textContent) // get text .join('') // stick together ; Or similar, using reduce: const getElementTextWithoutChildElements = (el) => [].reduce.call( el.childNodes, (a, b) => a + (b.nodeType === 3 ? b.textContent : ''), '' ); Should work with this: <div> you get this <b>not this</b> you get this too </div> will return: you get this you get this too Whitespace between elements could be tricky, suggest using with .trim() and/or normalize all whitespace, e.g. For debugging and logging to quickly identify elements I find this is usually enough: getElementTextWithoutChildElements(...).replace(/\s+/g, ' ').trim(); // 'you get this you get this too' Though you might want to tweak whitespace differently, perhaps within the reduce() function itself to handle whitespace per node. e.g. whitespace handling per node: const getElementTextWithoutChildElements_2 = (el) => Array.from(el.childNodes) .filter(node => node.nodeType === 3) .map(node => node.textContent.trim()) // added .trim() .join(',') // added ',' ; Quick tests for things above: document.body.innerHTML = ` you get this <b>not this</b> you get this too `; // '\n you get this\n <b>not this</b>\n you get this too\n' getElementTextWithoutChildElements(document.body); // '\n you get this\n \n you get this too\n' getElementTextWithoutChildElements(document.body).replace(/\s+/g, ' ').trim(); // 'you get this you get this too' getElementTextWithoutChildElements_2(document.body); // 'you get this,you get this too'
This is a good way for me var text = $('#listItem').clone().children().remove().end().text();
I came up with a specific solution that should be much more efficient than the cloning and modifying of the clone. This solution only works with the following two reservations, but should be more efficient than the currently accepted solution: You are getting only the text The text you want to extract is before the child elements With that said, here is the code: // 'element' is a jQuery element function getText(element) { var text = element.text(); var childLength = element.children().text().length; return text.slice(0, text.length - childLength); }
Live demo <li id="listItem"> This is some text <span id="firstSpan">First span text</span> <span id="secondSpan">Second span text</span> </li> <input id="input" style="width: 300px; margin-top: 10px;"> <script type="text/javascript"> $("#input").val($("#listItem").clone().find("span").remove().end().text().trim()); //use .trim() to remove any white space </script>
For beginners: I preferred #DUzun's answer because it's simple to understand and more efficient than the accepted answer. But it only partially worked for me as you can't directly pass the element with a class selector like this $(".landing-center .articlelanding_detail").get(0).immediateText() //gives .immediateText is not a function error or this $(".landing-center .articlelanding_detail")[0].immediateText() //gives .immediateText is not a function error because once you extract the native Element by using [index] or .get(index) out of the $() function you loose jQuery Object methods chainability as mentioned here. And most of the solutions are only in context to ids, not so elegant to use multiple times for the elements with a class selectors. So, I wrote jQuery plugin: $.fn.mainText = function(x=0) { return $.trim(this.eq(x).contents().not(this.eq(x).children()).text().replace(/[\t\n]+/g,' ')); }; This will return the text of the element irrespective of if ids or class are used as selectors excluding child elements. Also will remove any \t or \n to get a clean string. Use it like this: Case 1 $("#example").mainText(); // get the text of element with example id Case 2 $(".example").mainText(); // get the text of first element with example class Case 3 $(".example").mainText(1); // get the text of second element with example class and so on..
Alternative version of the answere without JQuery [...document.getElementById("listItem").childNodes].find(c => c.nodeType === Node.TEXT_NODE).nodeValue
Just like the question, I was trying to extract text in order to do some regex substitution of the text but was getting problems where my inner elements (ie: <i>, <div>, <span>, etc.) were getting also removed. The following code seems to work well and solved all my problems. It uses some of the answers provided here but in particular, will only substitute the text when the element is of nodeType === 3. $(el).contents().each(function() { console.log(" > Content: %s [%s]", this, (this.nodeType === 3)); if (this.nodeType === 3) { var text = this.textContent; console.log(" > Old : '%s'", text); regex = new RegExp("\\[\\[" + rule + "\\.val\\]\\]", "g"); text = text.replace(regex, value); regex = new RegExp("\\[\\[" + rule + "\\.act\\]\\]", "g"); text = text.replace(regex, actual); console.log(" > New : '%s'", text); this.textContent = text; } }); What the above does is loop through all the elements of the given el (which was simply obtained with $("div.my-class[name='some-name']");. For each inner element, it basically ignores them. For each portion of text (as determined by if (this.nodeType === 3)) it will apply the regex substitution only to those elements. The this.textContent = text portion simply replaces the substituted text, which in my case, I was looking for tokens like [[min.val]], [[max.val]], etc. This short code excerpt will help anyone trying to do what the question was asking ... and a bit more.
Not sure how flexible or how many cases you need it to cover, but for your example, if the text always comes before the first HTML tags – why not just split the inner html at the first tag and take the former: $('#listItem').html().split('<span')[0]; and if you need it wider maybe just $('#listItem').html().split('<')[0]; and if you need the text between two markers, like after one thing but before another, you can do something like (untested) and use if statements to make it flexible enough to have a start or end marker or both, while avoiding null ref errors: var startMarker = '';// put any starting marker here var endMarker = '<';// put the end marker here var myText = String( $('#listItem').html() ); // if the start marker is found, take the string after it myText = myText.split(startMarker)[1]; // if the end marker is found, take the string before it myText = myText.split(endMarker)[0]; console.log(myText); // output text between the first occurrence of the markers, assuming both markers exist. If they don't this will throw an error, so some if statements to check params is probably in order... I generally make utility functions for useful things like this, make them error free, and then rely on them frequently once solid, rather than always rewriting this type of string manipulation and risking null references etc. That way, you can re-use the function in lots of projects and never have to waste time on it again debugging why a string reference has an undefined reference error. Might not be the shortest 1 line code ever, but after you have the utility function, it is one line from then on. Note most of the code is just handling parameters being there or not to avoid errors :) For example: /** * Get the text between two string markers. **/ function textBetween(__string,__startMark,__endMark){ var hasText = typeof __string !== 'undefined' && __string.length > 0; if(!hasText) return __string; var myText = String( __string ); var hasStartMarker = typeof __startMark !== 'undefined' && __startMark.length > 0 && __string.indexOf(__startMark)>=0; var hasEndMarker = typeof __endMark !== 'undefined' && __endMark.length > 0 && __string.indexOf(__endMark) > 0; if( hasStartMarker ) myText = myText.split(__startMark)[1]; if( hasEndMarker ) myText = myText.split(__endMark)[0]; return myText; } // now with 1 line from now on, and no jquery needed really, but to use your example: var textWithNoHTML = textBetween( $('#listItem').html(), '', '<'); // should return text before first child HTML tag if the text is on page (use document ready etc)
Use an extra condition to check if innerHTML and innerText are the same. Only in those cases, replace the text. $(function() { $('body *').each(function () { console.log($(this).html()); console.log($(this).text()); if($(this).text() === "Search" && $(this).html()===$(this).text()) { $(this).html("Find"); } }) }) http://jsfiddle.net/7RSGh/
To be able to trim the result, use DotNetWala's like so: $("#foo") .clone() //clone the element .children() //select all the children .remove() //remove all the children .end() //again go back to selected element .text() .trim(); I found out that using the shorter version like document.getElementById("listItem").childNodes[0] won't work with jQuery's trim().
just put it in a <p> or <font> and grab that $('#listItem font').text() First thing that came to mind <li id="listItem"> <font>This is some text</font> <span id="firstSpan">First span text</span> <span id="secondSpan">Second span text</span> </li>
You can try this alert(document.getElementById('listItem').firstChild.data)
I am not a jquery expert, but how about, $('#listItem').children().first().text()
This untested, but I think you may be able to try something like this: $('#listItem').not('span').text(); http://api.jquery.com/not/
Selecting parts of an element with jQuery?
I have the following HTML: <div id="foo"> <span> X 1 </span> <span> X 2 </span> <span> X Hello </span> </div> I want to use jQuery to get only the following 1, 2 and hello in JS (that means no <a> tags or ). Please note that I do not want to affect the DOM itself. I just want to retrieve the text in an object such as an array. This is what I have so far: $('#foo span'); However I can't seem to remove the "a" tag. The following does not seem to work either: $('#foo span').remove('a'); I'm also aware that .remove() affects the DOM it self, and does not just retrieve the text.
Solution creates array by looping over each child and cloning it to do manipulation so dom stays intact var values = $('#foo').children().map(function () { var $clone = $(this).clone(); $clone.children().remove(); return $.trim($clone.text()); }).get(); console.log(values) /// ["1","2","Hello"] DEMO
This piece of code will get that value for you: $('#foo span').each(function(){ console.log($.trim($(this).html().substring($(this).html().lastIndexOf(" ") + 6))); }); Working jsFiddle: http://jsfiddle.net/mrwqs2nb/1/ Open the console, you will see: 1 2 Hello
Since your problem is not detailed , on the basis of your requirements , this can be done as below var res=[]; $('#foo span').map(function(i){ var htm=$(this).text(); var htm_d=$(this).find('a').html(); htm=$.trim(htm.split("").reverse().join("")); htm_d=$.trim(htm_d.split("").reverse().join("")); res[i] = htm.substring(0,htm_d.length); } ); alert(res); LIVE http://jsfiddle.net/mailmerohit5/5bvavx0p/
Not sure what the rules of this game are, but if it can be asumed that what you want is on the right of the then this should do it: $('#foo span').map(function() { //Split at the space. var parts = $(this).html().split(' '); //Remove the first element from the list. parts.shift(); //Join and return. return parts.join(''); }).get();
Use jquery's contents - the api page has an example on how to extra text nodes while ignoring other elements. var result = $("#foo span") .contents() .filter(function() { // #text nodes return this.nodeType === 3; }).map(function() { // get the value and trim it (also removes  ) return $(this).text().trim(); }).filter(function() { // remove blanks return this != ""; }).toArray(); $("#result").text(result.join()); Working fiddle
Try using selector $("foo span") , $.map() , String.prototype.match() with RegExp [0-9]+|[a-z]+[^\s+|\n+|" + el.querySelector(".no-want").textContent + "]", "ig" to match digit or characters a-z case insensitive , negating space character , or newline character , or ".no-want" element .textContent at index of parent span element var res = $.map($("#foo span"), function(el, index) { return el.textContent.match(new RegExp("[0-9]+|[a-z]+[^\s+|\n+|" + el.querySelector(".no-want").textContent.trim() + "]", "ig")) }); console.log(res) <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <div id="foo"> <span> X 1 </span> <span> X 2 </span> <span> X Hello </span> </div>
Try using RegEx to match what you want $('#foo span').each(function () { alert($(this).html().match(/[0-9]/)); }); JSFIDDLE
jQuery: How to wrap RegEx matched plain text in an anchor tag?
Suppose I have an HTML page that looks something like this: <html><body> 00123 <input value="00123"> 00456 </body></html> And I want to use javascript/jQuery to make it look like this: <html><body> 00123 <input value="00123"> 00456 </body></html> Essentially I want to wrap regular expression matched plain strings in the document with HTML anchor tags. In this example, I want to do something like: $('body').html($('body').html().replace(/(00\d+)/, '$1')); See the jsFiddle with this example: http://jsfiddle.net/NATnr/2/ The problem with this solution is that the text inside the input element is matched and replaced. Does anyone know how to only match and replace plain text in a document in this manner using javascript/jQuery?
Try filtering the body's contents() by nodeType to get only the Text Nodes, then replace them with jQuery-generated anchor elements (any extra text in these nodes will be kept as Text Node): $('body').contents().filter(function() { return this.nodeType === 3; }).each(function() { $(this).replaceWith($(this).text().replace(/(00\d+)/g, '$1')); }); Fiddle As you know, most often it's not a good idea to parse HTML with Regex (look out for the ponies, they are evil), but if you isolate a part of the HTML you want to parse and it follows a relatively simple pattern, it is a viable option. edit: Included the g flag (global modifier) in your Regex to allow for matching multiple anchors inside a single Text Node.
The final solution ended up looking like this: jQuery.fn.linker = function () { $(this).contents() .filter(function() { return this.nodeType != Node.TEXT_NODE; }) .each(function () { $(this).linker(); }); $(this).contents() .filter(function() { return this.nodeType == Node.TEXT_NODE; }) .each(function () { $(this).replaceWith( $(this).text().replace(/(00\d+)/g, '$1') ); }); } $(document).ready(function () { $('body').linker(); }); See the jsFiddle at work here: http://jsfiddle.net/fr4AL/4/ Thanks to: Fabricio's answer How do I select text nodes with jQuery? http://api.jquery.com/wrap/ http://api.jquery.com/contents/ Converting HTML string into DOM elements?
This from a related answer to a question by bobince: You're right to not want to be processing HTML with regex. It's also bad news to be assigning huge chunks of .html(); apart from the performance drawbacks of serialising and reparsing a large amount of HTML, you'll also lose unserialisable data like event listeners, form data and JS properties/references. Here's a plain JavaScript/DOM one that allows a RegExp pattern to match. jQuery doesn't really give you much help here since selectors can only select elements, and the ‘:contains’ selector is recursive so not too useful to us. // Find text in descendents of an element, in reverse document order // pattern must be a regexp with global flag // function findText(element, pattern, callback) { for (var childi= element.childNodes.length; childi-->0;) { var child= element.childNodes[childi]; if (child.nodeType==1) { findText(child, pattern, callback); } else if (child.nodeType==3) { var matches= []; var match; while (match= pattern.exec(child.data)) matches.push(match); for (var i= matches.length; i-->0;) callback.call(window, child, matches[i]); } } } findText(document.body, /\bBuyNow\b/g, function(node, match) { var span= document.createElement('span'); span.className= 'highlight'; node.splitText(match.index+6); span.appendChild(node.splitText(match.index+3)); node.parentNode.insertBefore(span, node.nextSibling); });
Give this a whirl.... Much cleaner!! ;) $('input').each(function() { var temp; temp = $(this).val(); $(this).before('' +temp+ ''); }); $('body').contents().filter(function() {return this.nodeType == 3;}).remove();
How to change text inside span with jQuery, leaving other span contained nodes intact?
I have the following HTML snippet: <span class="target">Change me <a class="changeme" href="#">now</a></span> I'd like to change the text node (i.e. "Change me ") inside the span from jQuery, while leaving the nested <a> tag with all attributes etc. intact. My initial huch was to use .text(...) on the span node, but as it turns out this will replace the whole inner part with the passed textual content. I solved this with first cloning the <a> tag, then setting the new text content of <span> (which will remove the original <a> tag), and finally appending the cloned <a> tag to my <span>. This works, but feels such an overkill for a simple task like this. Btw. I can't guarantee that there will be an initial text node inside the span - it might be empty, just like: <span class="target"><a class="changeme" href="#">now</a></span> I did a jsfiddle too. So, what would be the neat way to do this?
Try something like: $('a.changeme').on('click', function() { $(this).closest('.target').contents().not(this).eq(0).replaceWith('Do it again '); }); demo: http://jsfiddle.net/eEMGz/ ref: http://api.jquery.com/contents/ Update: I guess I read your question wrong, and you're trying to replace the text if it's already there and inject it otherwise. For this, try: $('a.changeme').on('click', function() { var $tmp = $(this).closest('.target').contents().not(this).eq(0), dia = document.createTextNode('Do it again '); $tmp.length > 0 ? $tmp.replaceWith(dia) : $(dia).insertBefore(this); }); Demo: http://jsfiddle.net/eEMGz/3/
You can use .contents(): //set the new text to replace the old text var newText = 'New Text'; //bind `click` event handler to the `.changeme` elements $('.changeme').on('click', function () { //iterate over the nodes in this `<span>` element $.each($(this).parent().contents(), function () { //if the type of this node is undefined then it's a text node and we want to replace it if (typeof this.tagName == 'undefined') { //to replace the node we can use `.replaceWith()` $(this).replaceWith(newText); } }); }); Here is a demo: http://jsfiddle.net/jasper/PURHA/1/ Some docs for ya: .contents(): http://api.jquery.com/contents .replaceWith(): http://api.jquery.com/replacewith typeof: https://developer.mozilla.org/en/JavaScript/Reference/Operators/typeof Update var newText = 'New Text'; $('a').on('click', function () { $.each($(this).parent().contents(), function () { if (typeof this.tagName == 'undefined') { //instead of replacing this node with the replacement string, just replace it with a blank string $(this).replaceWith(''); } }); //then add the replacement string to the `<span>` element regardless of it's initial state $(this).parent().prepend(newText); }); Demo: http://jsfiddle.net/jasper/PURHA/2/
You can try this. var $textNode, $parent; $('.changeme').on('click', function(){ $parent = $(this).parent(); $textNode= $parent.contents().filter(function() { return this.nodeType == 3; }); if($textNode.length){ $textNode.replaceWith('Content changed') } else{ $parent.prepend('New content'); } }); Working demo - http://jsfiddle.net/ShankarSangoli/yx5Ju/8/
You step out of jQuery because it doesn't help you to deal with text nodes. The following will remove the first child of every <span> element with class "target" if and only if it exists and is a text node. Demo: http://jsfiddle.net/yx5Ju/11/ Code: $('span.target').each(function() { var firstChild = this.firstChild; if (firstChild && firstChild.nodeType == 3) { firstChild.data = "Do it again"; } });
This is not a perfect example I guess, but you could use contents function. console.log($("span.target").contents()[0].data);
You could wrap the text into a span ... but ... try this. http://jsfiddle.net/Y8tMk/ $(function(){ var txt = ''; $('.target').contents().each(function(){ if(this.nodeType==3){ this.textContent = 'done '; } }); });
You can change the native (non-jquery) data property of the object. Updated jsfiddle here: http://jsfiddle.net/elgreg/yx5Ju/2/ Something like: $('a.changeme3').click(function(){ $('span.target3').contents().get(0).data = 'Do it again'; }); The contents() gets the innards and the get(0) gets us back to the original element and the .data is now a reference to the native js textnode. (I haven't tested this cross browser.) This jsfiddle and answer are really just an expanded explanation of the answer to this question: Change text-nodes text
$('a.changeme').click(function() { var firstNode= $(this).parent().contents()[0]; if( firstNode.nodeType==3){ firstNode.nodeValue='New text'; } }) EDIT: not sure what layout rules you need, update to test only first node, otherwise adapt as needed
In jquery, how can I detect if a paragraph contains one link and only one link, nothing else
I want to apply a class 'solo' to a link in a paragraph where that link is the ONLY element in the paragraph. So this would get the 'solo' class: <p><a>I am alone</a></p> But this would not: <p><a>I am not alone</a> because there is more text!</p>
You could do: $('p').filter(function() { var $childNodes = $(this).contents(); return $childNodes .not($childNodes.filter('a').first()) .not(function() { return this.nodeType === 3 && $.trim(this.nodeValue) === ''; }).length === 0; }); This gets all child nodes (including text nodes) and removes the first a element it finds and all text nodes only containing whitespaces from that set. If the resulting set is empty, the link was the only child (alone). Reference: filter, not, contents, Node.nodeType, trim Update: Maybe this could be done easier, but I wanted it to work also for the case your HTML contains line breaks, which would result in text nodes containing only whitespaces: <p> <a>I am alone</a> </p> <p> <a>I am not alone</a> because there is more text! </p> DEMO
Try this $('p').filter(function(){ var $childrens = $(this).children(); return ($childrens.length == 1 && $childrens.is('a') && $(this).text() == $childrens.text()); }).addClass('solo'); Demo
There is 1 more way to do this: if($("p").find('a').length === 1){ //perform operation }
Not very performant, but this is the most concise and nice (and working) solution I came out with! $('p').filter( function() { return !$('<p>'+$.trim($(this).html())+'</p>').contents().not('a:first').length; }).addClass('solo'); http://jsfiddle.net/DYxgu/