I would like to get all descendant text nodes of an element, as a jQuery collection. What is the best way to do that?
This question is related to
javascript
jquery
dom
Jauco posted a good solution in a comment, so I'm copying it here:
$(elem)
.contents()
.filter(function() {
return this.nodeType === 3; //Node.TEXT_NODE
});
Jauco posted a good solution in a comment, so I'm copying it here:
$(elem)
.contents()
.filter(function() {
return this.nodeType === 3; //Node.TEXT_NODE
});
For me, plain old .contents()
appeared to work to return the text nodes, just have to be careful with your selectors so that you know they will be text nodes.
For example, this wrapped all the text content of the TDs in my table with pre
tags and had no problems.
jQuery("#resultTable td").content().wrap("<pre/>")
Can also be done like this:
var textContents = $(document.getElementById("ElementId").childNodes).filter(function(){
return this.nodeType == 3;
});
The above code filters the textNodes from direct children child nodes of a given element.
For some reason contents()
didn't work for me, so if it didn't work for you, here's a solution I made, I created jQuery.fn.descendants
with the option to include text nodes or not
Usage
Get all descendants including text nodes and element nodes
jQuery('body').descendants('all');
Get all descendants returning only text nodes
jQuery('body').descendants(true);
Get all descendants returning only element nodes
jQuery('body').descendants();
Coffeescript Original:
jQuery.fn.descendants = ( textNodes ) ->
# if textNodes is 'all' then textNodes and elementNodes are allowed
# if textNodes if true then only textNodes will be returned
# if textNodes is not provided as an argument then only element nodes
# will be returned
allowedTypes = if textNodes is 'all' then [1,3] else if textNodes then [3] else [1]
# nodes we find
nodes = []
dig = (node) ->
# loop through children
for child in node.childNodes
# push child to collection if has allowed type
nodes.push(child) if child.nodeType in allowedTypes
# dig through child if has children
dig child if child.childNodes.length
# loop and dig through nodes in the current
# jQuery object
dig node for node in this
# wrap with jQuery
return jQuery(nodes)
Drop In Javascript Version
var __indexOf=[].indexOf||function(e){for(var t=0,n=this.length;t<n;t++){if(t in this&&this[t]===e)return t}return-1}; /* indexOf polyfill ends here*/ jQuery.fn.descendants=function(e){var t,n,r,i,s,o;t=e==="all"?[1,3]:e?[3]:[1];i=[];n=function(e){var r,s,o,u,a,f;u=e.childNodes;f=[];for(s=0,o=u.length;s<o;s++){r=u[s];if(a=r.nodeType,__indexOf.call(t,a)>=0){i.push(r)}if(r.childNodes.length){f.push(n(r))}else{f.push(void 0)}}return f};for(s=0,o=this.length;s<o;s++){r=this[s];n(r)}return jQuery(i)}
Unminified Javascript version: http://pastebin.com/cX3jMfuD
This is cross browser, a small Array.indexOf
polyfill is included in the code.
Jauco posted a good solution in a comment, so I'm copying it here:
$(elem)
.contents()
.filter(function() {
return this.nodeType === 3; //Node.TEXT_NODE
});
$('body').find('*').contents().filter(function () { return this.nodeType === 3; });
For me, plain old .contents()
appeared to work to return the text nodes, just have to be careful with your selectors so that you know they will be text nodes.
For example, this wrapped all the text content of the TDs in my table with pre
tags and had no problems.
jQuery("#resultTable td").content().wrap("<pre/>")
jQuery.contents()
can be used with jQuery.filter
to find all child text nodes. With a little twist, you can find grandchildren text nodes as well. No recursion required:
$(function() {_x000D_
var $textNodes = $("#test, #test *").contents().filter(function() {_x000D_
return this.nodeType === Node.TEXT_NODE;_x000D_
});_x000D_
/*_x000D_
* for testing_x000D_
*/_x000D_
$textNodes.each(function() {_x000D_
console.log(this);_x000D_
});_x000D_
});
_x000D_
div { margin-left: 1em; }
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>_x000D_
_x000D_
<div id="test">_x000D_
child text 1<br>_x000D_
child text 2_x000D_
<div>_x000D_
grandchild text 1_x000D_
<div>grand-grandchild text 1</div>_x000D_
grandchild text 2_x000D_
</div>_x000D_
child text 3<br>_x000D_
child text 4_x000D_
</div>
_x000D_
if you want to strip all tags, then try this
function:
String.prototype.stripTags=function(){
var rtag=/<.*?[^>]>/g;
return this.replace(rtag,'');
}
usage:
var newText=$('selector').html().stripTags();
jQuery.contents()
can be used with jQuery.filter
to find all child text nodes. With a little twist, you can find grandchildren text nodes as well. No recursion required:
$(function() {_x000D_
var $textNodes = $("#test, #test *").contents().filter(function() {_x000D_
return this.nodeType === Node.TEXT_NODE;_x000D_
});_x000D_
/*_x000D_
* for testing_x000D_
*/_x000D_
$textNodes.each(function() {_x000D_
console.log(this);_x000D_
});_x000D_
});
_x000D_
div { margin-left: 1em; }
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>_x000D_
_x000D_
<div id="test">_x000D_
child text 1<br>_x000D_
child text 2_x000D_
<div>_x000D_
grandchild text 1_x000D_
<div>grand-grandchild text 1</div>_x000D_
grandchild text 2_x000D_
</div>_x000D_
child text 3<br>_x000D_
child text 4_x000D_
</div>
_x000D_
If you can make the assumption that all children are either Element Nodes or Text Nodes, then this is one solution.
To get all child text nodes as a jquery collection:
$('selector').clone().children().remove().end().contents();
To get a copy of the original element with non-text children removed:
$('selector').clone().children().remove().end();
$('body').find('*').contents().filter(function () { return this.nodeType === 3; });
I had the same problem and solved it with:
Code:
$.fn.nextNode = function(){
var contents = $(this).parent().contents();
return contents.get(contents.index(this)+1);
}
Usage:
$('#my_id').nextNode();
Is like next()
but also returns the text nodes.
Can also be done like this:
var textContents = $(document.getElementById("ElementId").childNodes).filter(function(){
return this.nodeType == 3;
});
The above code filters the textNodes from direct children child nodes of a given element.
For some reason contents()
didn't work for me, so if it didn't work for you, here's a solution I made, I created jQuery.fn.descendants
with the option to include text nodes or not
Usage
Get all descendants including text nodes and element nodes
jQuery('body').descendants('all');
Get all descendants returning only text nodes
jQuery('body').descendants(true);
Get all descendants returning only element nodes
jQuery('body').descendants();
Coffeescript Original:
jQuery.fn.descendants = ( textNodes ) ->
# if textNodes is 'all' then textNodes and elementNodes are allowed
# if textNodes if true then only textNodes will be returned
# if textNodes is not provided as an argument then only element nodes
# will be returned
allowedTypes = if textNodes is 'all' then [1,3] else if textNodes then [3] else [1]
# nodes we find
nodes = []
dig = (node) ->
# loop through children
for child in node.childNodes
# push child to collection if has allowed type
nodes.push(child) if child.nodeType in allowedTypes
# dig through child if has children
dig child if child.childNodes.length
# loop and dig through nodes in the current
# jQuery object
dig node for node in this
# wrap with jQuery
return jQuery(nodes)
Drop In Javascript Version
var __indexOf=[].indexOf||function(e){for(var t=0,n=this.length;t<n;t++){if(t in this&&this[t]===e)return t}return-1}; /* indexOf polyfill ends here*/ jQuery.fn.descendants=function(e){var t,n,r,i,s,o;t=e==="all"?[1,3]:e?[3]:[1];i=[];n=function(e){var r,s,o,u,a,f;u=e.childNodes;f=[];for(s=0,o=u.length;s<o;s++){r=u[s];if(a=r.nodeType,__indexOf.call(t,a)>=0){i.push(r)}if(r.childNodes.length){f.push(n(r))}else{f.push(void 0)}}return f};for(s=0,o=this.length;s<o;s++){r=this[s];n(r)}return jQuery(i)}
Unminified Javascript version: http://pastebin.com/cX3jMfuD
This is cross browser, a small Array.indexOf
polyfill is included in the code.
If you can make the assumption that all children are either Element Nodes or Text Nodes, then this is one solution.
To get all child text nodes as a jquery collection:
$('selector').clone().children().remove().end().contents();
To get a copy of the original element with non-text children removed:
$('selector').clone().children().remove().end();
I was getting a lot of empty text nodes with the accepted filter function. If you're only interested in selecting text nodes that contain non-whitespace, try adding a nodeValue
conditional to your filter
function, like a simple $.trim(this.nodevalue) !== ''
:
$('element')
.contents()
.filter(function(){
return this.nodeType === 3 && $.trim(this.nodeValue) !== '';
});
Or to avoid strange situations where the content looks like whitespace, but is not (e.g. the soft hyphen ­
character, newlines \n
, tabs, etc.), you can try using a Regular Expression. For example, \S
will match any non-whitespace characters:
$('element')
.contents()
.filter(function(){
return this.nodeType === 3 && /\S/.test(this.nodeValue);
});
if you want to strip all tags, then try this
function:
String.prototype.stripTags=function(){
var rtag=/<.*?[^>]>/g;
return this.replace(rtag,'');
}
usage:
var newText=$('selector').html().stripTags();
I was getting a lot of empty text nodes with the accepted filter function. If you're only interested in selecting text nodes that contain non-whitespace, try adding a nodeValue
conditional to your filter
function, like a simple $.trim(this.nodevalue) !== ''
:
$('element')
.contents()
.filter(function(){
return this.nodeType === 3 && $.trim(this.nodeValue) !== '';
});
Or to avoid strange situations where the content looks like whitespace, but is not (e.g. the soft hyphen ­
character, newlines \n
, tabs, etc.), you can try using a Regular Expression. For example, \S
will match any non-whitespace characters:
$('element')
.contents()
.filter(function(){
return this.nodeType === 3 && /\S/.test(this.nodeValue);
});
Source: Stackoverflow.com