Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to iterate over every node in a selected range in javascript?

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?
like image 630
NeoWang Avatar asked Feb 18 '16 08:02

NeoWang


2 Answers

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.

like image 60
raphaelbs Avatar answered Oct 31 '22 09:10

raphaelbs


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.

like image 34
JaffaTheCake Avatar answered Oct 31 '22 10:10

JaffaTheCake