Reputation: 18563
When implementing a rich text editor in javascript, I need to apply some changes to every text node in selected range. Range object provides interface to get startContainer
, endContainer
, startOffset
, endOffset
for the selected range. How can I iterate over every DOM node in between?
var selection = window.getSelection();
var range = selection.getRange(0);
// How can I iterate over every node within the range?
Upvotes: 19
Views: 5010
Reputation: 469
As suggested, you can use NodeIterator
to walk inside range.commonAncestorContainer
.
Here's a snippet:
var _iterator = document.createNodeIterator(
range.commonAncestorContainer,
NodeFilter.SHOW_ALL, // pre-filter
{
// custom filter
acceptNode: function (node) {
return NodeFilter.FILTER_ACCEPT;
}
}
);
var _nodes = [];
while (_iterator.nextNode()) {
if (_nodes.length === 0 && _iterator.referenceNode !== range.startContainer) continue;
_nodes.push(_iterator.referenceNode);
if (_iterator.referenceNode === range.endContainer) break;
}
You should use NodeFilter.SHOW_ALL
because your range can contain multiple nodeTypes. If you know what you are selecting, you can check this reference to properly choose NodeFilter
.
Edit: I also want to point out document.createTreeWalker().
The key difference is that document.createTreeWalker()
allow your acceptNode
filter to return both NodeFilter.FILTER_REJECT
and NodeFilter.FILTER_SKIP
with real differences.
Quote from NodeFilter docs:
FILTER_REJECT:
Value to be returned by the NodeFilter.acceptNode() method when a node should be rejected. For TreeWalker, child nodes are also rejected. For NodeIterator, this flag is synonymous with FILTER_SKIP.
Ps: the NodeFilter.acceptNode() documentation for NodeFilter.FILTER_REJECT
is incorrect.
Upvotes: 18
Reputation: 14917
range.commonAncestorContainer
will give you the node that encompasses the range. If it gives you a text node, then that's the only node in your range.
If it gives you an element, you can use NodeIterator
, or el.querySelectorAll('*')
to get the nodes within.
Not all of these will be inside your range, so use range.intersectsNode(el)
to confirm.
Upvotes: 6