Reputation: 16994
I've discovered that I have a need for selectors with full support for DOM textNode
s that jQuery doesn't provide.
jQuery ignores text nodes, probably because most pages have tons of irrelevant blank ones between tags that the various browsers can treat differently.
Most answers to jQuery questions about text nodes come down to using the .contents()
function which returns all the child nodes for the selected items, including text nodes - all other jQuery APIs ignore text nodes.
Often you don't need something that can't easily be built upon .contents()
but I have found myself in such a situation.
My use case is that I want to locate and then wrap arbitrary runs of text in 3rd-party web pages over which I have no control. (Think browser extension or userscript.)
So far I've been happy to walk the DOM looking for all text nodes or find a wrapper element that contains all the text nodes I'm interested in and use .contents()
to iterate through them.
But now I have found that I sometimes need the full power of jQuery/sizzle selectors to narrow my focus down to certain possibilities of classes within classes etc.
I considered ways to extend jQuery with a textNode
selector but that seems to be impossible due to a pervasive rule of ignoring text nodes which would filter many of them out before my extension gets called.
Thus I'm looking for some other JavaScript tool which offers something like selectors but allows selecting text nodes arbitrarily mixed in its selector expression syntax.
Here's an example of what I might need to do:
$('.ii:even > div > TXT, .ii:even > div > div.im > TXT')
Here's an example I personally haven't needed yet but can easily imagine:
$('#something .somethingElse TXT')
When you can address (select) the immediate parent(s) of the textNodes, iterating over their .contents()
is easy, not so when you can only identify some arbitrary ancestor but want all the text nodes below this, which is of course trivial for element nodes.
Upvotes: 2
Views: 934
Reputation: 167
Here is something you could do:
jQuery.fn.getTextNodes = function(val,_case) {
var nodes = [],
noVal = typeof val === "undefined",
regExp = !noVal && jQuery.type(val) === "regexp",
nodeType, nodeValue;
if (!noVal && _case && !regExp) val = val.toLowerCase();
this.each(function() {
if ((nodeType = this.nodeType) !== 3 && nodeType !== 8) {
jQuery.each(this.childNodes, function() {
if (this.nodeType === 3) {
nodeValue = _case ? this.nodeValue.toLowerCase() : this.nodeValue;
if (noVal || (regExp ? val.test(nodeValue) : nodeValue === val)) nodes.push(this);
}
});
}
});
return this.pushStack(nodes, "getTextNodes", val || "");
};
Then you could use the following:
$("selector").getTextNodes("selector");
Here is a JSFiddle.
How .getTextNodes()
works is very simple. If you don't pass an argument, it returns all text nodes. If you pass it a string, it returns text nodes with that exact same nodeValue
. If you are passing it a string, set the second argument to a truthy value for a case-insensitive check. The first argument can also be a regular expression against which the nodeValue
is matched.
Hope this helps.
Edit: Note that you can also use $("selector").getNodes("selector").end()
, since it uses .pushStack()
.
Upvotes: 4