[jquery] Using .text() to retrieve only text not nested in child tags

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.

This question is related to jquery text tags

The answer is


I propose to use the createTreeWalker to find all texts elements not attached to html elements (this function can be used to extend jQuery):

_x000D_
_x000D_
function textNodesOnlyUnder(el) {_x000D_
  var resultSet = [];_x000D_
  var n = null;_x000D_
  var treeWalker  = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, function (node) {_x000D_
    if (node.parentNode.id == el.id && node.textContent.trim().length != 0) {_x000D_
      return NodeFilter.FILTER_ACCEPT;_x000D_
    }_x000D_
    return NodeFilter.FILTER_SKIP;_x000D_
  }, false);_x000D_
  while (n = treeWalker.nextNode()) {_x000D_
    resultSet.push(n);_x000D_
  }_x000D_
  return resultSet;_x000D_
}_x000D_
_x000D_
_x000D_
_x000D_
window.onload = function() {_x000D_
  var ele = document.getElementById('listItem');_x000D_
  var textNodesOnly = textNodesOnlyUnder(ele);_x000D_
  var resultingText = textNodesOnly.map(function(val, index, arr) {_x000D_
    return 'Text element N. ' + index + ' --> ' + val.textContent.trim();_x000D_
  }).join('\n');_x000D_
  document.getElementById('txtArea').value = resultingText;_x000D_
}
_x000D_
<li id="listItem">_x000D_
    This is some text_x000D_
    <span id="firstSpan">First span text</span>_x000D_
    <span id="secondSpan">Second span text</span>_x000D_
</li>_x000D_
<textarea id="txtArea" style="width: 400px;height: 200px;"></textarea>
_x000D_
_x000D_
_x000D_


Try this:

$('#listItem').not($('#listItem').children()).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:

  1. You are getting only the text
  2. 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);
}

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

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().


Simple answer:

$("#listItem").contents().filter(function(){ 
  return this.nodeType == 3; 
})[0].nodeValue = "The text you want to replace with" 

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("");

This is a good way for me

   var text  =  $('#listItem').clone().children().remove().end().text();

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)

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();

jQuery.fn.ownText = function () {
    return $(this).contents().filter(function () {
        return this.nodeType === Node.TEXT_NODE;
    }).text();
};

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.


If the position index of the text node is fixed among its siblings, you can use

$('parentselector').contents().eq(index).text()

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

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>

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;

I am not a jquery expert, but how about,

$('#listItem').children().first().text()

You can try this

alert(document.getElementById('listItem').firstChild.data)

Using plain JavaScript in IE 9+ compatible syntax in just a few lines:

let children = document.querySelector('#listItem').childNodes;

if (children.length > 0) {
    childrenLoop:
    for (var i = 0; i < children.length; i++) {
        //only target text nodes (nodeType of 3)
        if (children[i].nodeType === 3) {
            //do not target any whitespace in the HTML
            if (children[i].nodeValue.trim().length > 0) {
                children[i].nodeValue = 'Replacement text';
                //optimized to break out of the loop once primary text node found
                break childrenLoop;
            }
        }
    }
}

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.


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/


$($('#listItem').contents()[0]).text()

Short variant of Stuart answer.

or with get()

$($('#listItem').contents().get(0)).text()

This untested, but I think you may be able to try something like this:

 $('#listItem').not('span').text();

http://api.jquery.com/not/


Easier and quicker:

$("#listItem").contents().get(0).nodeValue

Examples related to jquery

How to make a variable accessible outside a function? Jquery assiging class to th in a table Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Getting all files in directory with ajax Bootstrap 4 multiselect dropdown Cross-Origin Read Blocking (CORB) bootstrap 4 file input doesn't show the file name Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource how to remove json object key and value.?

Examples related to text

Difference between opening a file in binary vs text How do I center text vertically and horizontally in Flutter? How to `wget` a list of URLs in a text file? Convert txt to csv python script Reading local text file into a JavaScript array Python: How to increase/reduce the fontsize of x and y tick labels? How can I insert a line break into a <Text> component in React Native? How to split large text file in windows? Copy text from nano editor to shell Atom menu is missing. How do I re-enable

Examples related to tags

How to set image name in Dockerfile? What is initial scale, user-scalable, minimum-scale, maximum-scale attribute in meta tag? How to create named and latest tag in Docker? Excel data validation with suggestions/autocomplete How do you revert to a specific tag in Git? HTML tag <a> want to add both href and onclick working Instagram API to fetch pictures with specific hashtags How to apply Hovering on html area tag? How to get current formatted date dd/mm/yyyy in Javascript and append it to an input How to leave space in HTML