What you have tried would not work because it would strip all HTML from parent elements (thus removing all child elements). If using .text()
, you have to only do replace operations only on textNodes
to avoid accidentally killing childNodes which will kill the structure of your document.
In your code, the very first parent element you do your replace on will get the .text()
from it (which returns only text, not child elements) and then set .text()
back on it. This ends up removing all child elements from the document which isn't what you want to do.
The only way I know of to do this is to iterate through all the individual text nodes in the document and do a replace on each one.
Since you asked for a jQuery method of doing this, here's one (that I don't think it is particularly efficient), but it appears to work:
$(document.body).find("*").contents().each(function() {
if (this.nodeType === 3) {
this.nodeValue = this.nodeValue.replace(/foo/g, "bar");
}
});
And, if you really want it on one line (which wrecks readability if you ask me), you could do this:
$(document.body).find("*").contents().filter(function() {return this.nodeType === 3}).each(function() {this.nodeValue = this.nodeValue.replace(/foo/g, "bar")});
This finds all elements in the body, then gets all the child nodes of each one of those elements, then iterates every one of those nodes looking only for text nodes to do a replace on.
Working demo: http://jsfiddle.net/jfriend00/qw8va10y/
A method that is likely much more efficient can be done in plain Javascript.
This code uses a tree walking function I've previously developed which lets you iterate through all the nodes in an parent node and knows to skip certain types of tags that it shouldn't iterate. It takes a callback which can then examine each node and decide what to do:
var treeWalkFast = (function() {
// create closure for constants
var skipTags = {"SCRIPT": true, "IFRAME": true, "OBJECT": true,
"EMBED": true, "STYLE": true, "LINK": true, "META": true};
return function(parent, fn, allNodes) {
var node = parent.firstChild, nextNode;
while (node && node != parent) {
if (allNodes || node.nodeType === 1) {
if (fn(node) === false) {
return(false);
}
}
// if it's an element &&
// has children &&
// has a tagname && is not in the skipTags list
// then, we can enumerate children
if (node.nodeType === 1 && node.firstChild && !(node.tagName && skipTags[node.tagName])) {
node = node.firstChild;
} else if (node.nextSibling) {
node = node.nextSibling;
} else {
// no child and no nextsibling
// find parent that has a nextSibling
while ((node = node.parentNode) != parent) {
if (node.nextSibling) {
node = node.nextSibling;
break;
}
}
}
}
}
})();
treeWalkFast(document.body, function(node) {
// process only text nodes
if (node.nodeType == 3) {
node.nodeValue = node.nodeValue.replace(/foo/g, "bar");
}
}, true);
Working demo: http://jsfiddle.net/jfriend00/Lvbr4a00/
The only cases that I know of where this would not work is if the text had HTML tags in the middle of it (and thus it is split across more than one text node). That is a fairly complicated problem to solve because you have to figure out how to handle the HTML formatting in the middle of the thing you're replacing the text of.
And, here's a jQuery plug-in that fetches textNodes (using the treeWalkFast()
function):
jQuery.fn.textNodes = function() {
var nodes = [];
this.each(function() {
treeWalkFast(this, function(node) {
if (node.nodeType === 3) {
nodes.push(node);
}
}, true);
});
return this.pushStack(nodes, "textNodes");
}
And, a jQuery plug-in that uses that to do a find/replace:
// find text may be regex or string
// if using regex, use the g flag so it will replace all occurrences
jQuery.fn.findReplace = function(find, replace) {
var fn;
if (typeof find === "string") {
fn = function(node) {
var lastVal;
// loop calling replace until the string doesn't change any more
do {
lastVal = node.nodeValue;
node.nodeValue = lastVal.replace(find, replace);
} while (node.nodeValue !== lastVal);
}
} else if (find instanceof RegExp) {
fn = function(node) {
node.nodeValue = node.nodeValue.replace(find, replace);
}
} else {
throw new Error("find argument must be string or RegExp");
}
return this.each(function() {
$(this).textNodes().each(function() {
fn(this);
});
});
}
So, once you have these plug-ins and the treeWalkFast()
supporting code installed, you can then just do this:
$(document.body).findReplace("foo", "bar");
Working demo: http://jsfiddle.net/jfriend00/7g1Lfvcm/