I encountered a situation recently in which I needed to apply some function to every text node within a given container element. Turns out this is pretty simple to accomplish with a few of JavaScript’s built-in DOM properties and some recursion. Here is an example:
First, let’s define a function to apply to our text nodes. For this demonstration, I have chosen to implement a simple Rot13 algorithm:
function rot13(str) {
var chars = 'abcdefghijklmnopqrstuvwxyz';
return String(str).replace(/[a-z]/gi, function(ch) {
var base = (ch < 'a') ? 65 : 97;
var idx = ((ch.charCodeAt() - base) + 13) % 26;
if (ch < 'a') {
return chars.charAt(idx).toUpperCase();
} else {
return chars.charAt(idx);
}
});
}
This will take our text and return a trivially encrypted string we can display for kicks and grins, or obfuscate from prying eyes. Not very useful, but it demonstrates our transform nicely.
Next, we need a way to find all the text nodes we want to transform. Unfortunately, JavaScript does not have a function that says “give me all the text nodes contained within this node”. We must resort to walking the DOM tree. This is not very difficult if we leverage the power of recursive functions to do the work. Since only element nodes can have child nodes, the recursion should happen there:
function obfuscateNode(node) {
switch (node.nodeType) {
case Node.ELEMENT_NODE:
var next = node.firstChild;
while (next) {
obfuscateNode(next); // recurse
next = next.nextSibling;
}
break;
case Node.TEXT_NODE:
node.data = rot13(node.data);
break;
}
}
Along with our recursive function, the only properties required to traverse the DOM are firstChild, and nextSibling. This a useful pattern for any time you need to traverse every node beneath a given root node. You can make it even more flexible by allowing for arbitrary transform functions — an exercise I leave to the reader.
One last item of note relates to the node type constants (Node.ELEMENT_NODE, and Node.TEXT_NODE above). Most modern browsers support these natively, the notable exception being IE6. To ensure that all browsers recognize these values, you can fake it:
if (!window.Node) {
var Node = {
ELEMENT_NODE: 1,
TEXT_NODE: 3
};
}
We could have defined all 12 values, but since the script only requires two, and for the sake of brevity, the rest were omitted.
Finally, here is a button which demonstrates the effect. Since Rot13 is its own inverse, successive clicks of the button will alternately encode and decode the text.
Naq gurer lbh unir vg!