Remove All id Attributes from nodes in a Range of Fragment - javascript

Is there a way to remove the id attribute of every node in a range or fragment?
Update: I finally found out that the bug I'm struggling with is based on a <[script]> being included in a range, and therefore unexpectedly cloned, when a chrome user does a ctrl+a. My goal would be to remove any instance of <[script]> from the range (or doc fragment), such that it is not replicated when cloned.

You may be able to use a TreeWalker, which works in pretty much all the browers that Range works in.
function actOnElementsInRange(range, func) {
function isContainedInRange(el, range) {
var elRange = range.cloneRange();
elRange.selectNode(el);
return range.compareBoundaryPoints(Range.START_TO_START, elRange) <= 0
&& range.compareBoundaryPoints(Range.END_TO_END, elRange) >= 0;
}
var rangeStartElement = range.startContainer;
if (rangeStartElement.nodeType == 3) {
rangeStartElement = rangeStartElement.parentNode;
}
var rangeEndElement = range.endContainer;
if (rangeEndElement.nodeType == 3) {
rangeEndElement = rangeEndElement.parentNode;
}
var isInRange = function(el) {
return (el === rangeStartElement || el === rangeEndElement ||
isContainedInRange(el, range))
? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
};
var container = range.commonAncestorContainer;
if (container.nodeType != 1) {
container = container.parentNode;
}
var walker = document.createTreeWalker(document,
NodeFilter.SHOW_ELEMENT, isInRange, false);
while (walker.nextNode()) {
func(walker.currentNode);
}
}
actOnElementsInRange(range, function(el) {
el.removeAttribute("id");
});

yes: http://api.jquery.com/removeAttr/

Related

Highlight text fragments selected by user without nesting tags

I have a <div>some test phrase<div> and I need to allow user to select different fragments of text and highlight them in different colors. Also I need to allow user to delete the the highlighting (but keep the text).
I use Angular, but the solution might be in pure JS.
I've got the partial solution received in response to my previous question:
function mark() {
var rng = document.getSelection().getRangeAt(0);
var cnt = rng.extractContents();
var node = document.createElement('MARK');
node.style.backgroundColor = "orange";
node.appendChild(cnt);
rng.insertNode(node);
}
document.addEventListener('keyup', mark);
document.addEventListener('mouseup', mark);
function unmark(e) {
var tgt = e.target;
if (tgt.tagName === 'MARK') {
if (e.ctrlKey) {
var txt = tgt.textContent;
tgt.parentNode.replaceChild(document.createTextNode(txt), tgt);
}
}
}
document.addEventListener('click', unmark);
::selection {
background: orange;
}
<p>some test phrase</p>
However, if user selects some test and test phrase after that, the selections will intersect and the mark tags will be nesting, while I need them to be like this: <mark>some</mark><mark>test phrase</mark>.
So, the general rule is: the last selection always wins, i.e. its color is always on top. How could I achieve this for any number of selections done?
Also deletion seems not to be working from time to time and I don't know why.
UPDATE:
Kind of implemented this, but I won't be surprised if there is a better way to do this.
Here is the code
I think you can start with this. You should thouroughly test it if it satisfy your case. Perhaps you should also refactor it to better fit you needs.
function mark() {
let selection = document.getSelection();
if(selection.type !== 'Range') { return;}
let pos = window.placeOfSelections;
let ranges = [];
let start = 0;
Array.prototype.forEach.call(pos.childNodes, function(chD)
{
ranges.push([start, start + chD.textContent.length, chD.nodeName === 'MARK']);
start += chD.textContent.length;
});
let text = pos.textContent;
let range = selection.getRangeAt(0);
let firstNode = range.startContainer;
let lastNode = range.endContainer;
selection.removeAllRanges();
let firstNodeIndex = Array.prototype.findIndex.call(pos.childNodes, node => node === firstNode || node.firstChild === firstNode);
let lastNodeIndex = Array.prototype.findIndex.call(pos.childNodes, node => node === lastNode || node.firstChild === lastNode);
let newSelectionStart = ranges[firstNodeIndex][0] + range.startOffset;
let newSelectionEnd = ranges[lastNodeIndex][0] + range.endOffset;
pos.innerHTML = text;
range.setStart(pos.childNodes[0], newSelectionStart);
range.setEnd(pos.childNodes[0], newSelectionEnd);
let node = document.createElement('MARK');
let cnt = range.extractContents();
node.appendChild(cnt);
range.insertNode(node);
let marks = ranges.filter(r => r[2]);
while(marks.length != 0)
{
let startEnd = marks.shift();
if(startEnd[0]>= newSelectionStart && startEnd[1] <= newSelectionEnd)
{
continue;
}
if(startEnd[0]>= newSelectionStart && startEnd[0] <= newSelectionEnd)
{
startEnd[0] = newSelectionEnd;
}
else
if(startEnd[1]>= newSelectionStart && startEnd[1] <= newSelectionEnd)
{
startEnd[1] = newSelectionStart;
}
else
if(startEnd[0] <=newSelectionStart && startEnd[1] >= newSelectionEnd)
{
marks.push([newSelectionEnd, startEnd[1]]);
startEnd[1] = newSelectionStart;
}
let tnStart = 0, tnEnd = 0;
let textNode = Array.prototype.find.call(pos.childNodes, function(tn)
{
tnEnd += tn.textContent.length;
if(tnStart <= startEnd[0] && startEnd[1] <= tnEnd )
{
return true;
}
tnStart += tn.textContent.length ;
});
range.setStart(textNode, startEnd[0] - tnStart);
range.setEnd(textNode, startEnd[1] - tnStart);
node = document.createElement('MARK');
node.appendChild(range.extractContents());
range.insertNode(node);
}
}
window.placeOfSelections.addEventListener('keyup', mark);
window.placeOfSelections.addEventListener('mouseup', mark);
function unmark(e) {
var tgt = e.target;
if ((tgt.tagName === 'MARK' || (e.parentNode && e.parentNode.tagName === "MARK")) && e.ctrlKey) {
let txt = tgt.textContent;
tgt.parentNode.replaceChild(document.createTextNode(txt), tgt);
}
}
window.placeOfSelections.addEventListener('mousedown', unmark);
mark {background-color: #BCE937 ;}
<p id="placeOfSelections">some test phrase</p>

JavaScript window.find() does not select search term

When I try to pass text which spreads throughout a few block elements the window.find method dosent work:
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
</head>
<body>
<p>search me</p><b> I could be the answer</b>
</body>
</html>
JavaScript:
window.find("meI could be");
Or:
str = "me";
str+= "\n";
str+="I could be t";
window.find(str);
This happens when the <p> element is present between the search term.
The end result should be a GUI selection on the text in the page, I do not want to search if it exists.
I would like to know how to achieve this in Firefox(at least) and internet explorer.
Note: I can't change the dom (e.g. change to display inline).
Edit:
Here is something I tried after #Alexey Lebedev's comment, but it also finds the script (tag [...] text):
Can I make it more simple? (better)?
function nativeTreeWalker(startNode) {
var walker = document.createTreeWalker(
startNode,
NodeFilter.SHOW_TEXT,
null,
false
);
var node;
var textNodesV = [];
var textNodes = [];
node = walker.nextNode();
while(node ) {
if(node.nodeValue.trim()){
textNodes.push(node);
textNodesV.push(node.nodeValue);
//console.log(node.nodeValue);
}
node = walker.nextNode();
}
return [textNodes,textNodesV];
}
var result = nativeTreeWalker(document.body);
var textNodes = result[0];
var textNodesV = result[1];
var param = " Praragraph.Test 3 Praragr";
paramArr = param.split(/(?=[\S])(?!\s)(?=[\W])(?=[\S])/g);
//Fix split PARAM
for(i=0;i<paramArr.length-1;i++){
paramArr[i]= paramArr[i]+paramArr[i+1].charAt(0);
paramArr[i+1] = paramArr[i+1].substring(1,paramArr[i+1].length);
}
//Fix last element PARAM
if(paramArr[paramArr.length-1] === ""){
paramArr.splice(paramArr.length-1,1);
}
//console.log(paramArr);
var startNode,startOffset,sFound=false,
endNode,endOffset;
for(i=0;i<paramArr.length;i++){
for(j=0;j<textNodesV.length;j++){
//Fully Equal
var pos = textNodesV[j].indexOf(paramArr[i]);
if(pos != -1){
if(!sFound){
startNode = textNodes[j];
startOffset = pos;
sFound=true;
}else{
endNode = textNodes[j];
endOffset = pos+paramArr[i].length;
break;
}
}
}
}
console.log(startNode);
console.log(startOffset);
console.log(endNode);
console.log(endOffset);
var range = document.createRange();
range.setStart(startNode,startOffset);
range.setEnd(endNode,endOffset);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
Note: No jQuery (only Raw JS).
JS Bin demo: http://jsbin.com/aqiciv/1/
If you want this to work in IE < 9 you'll need to add MS-specific selection code (nightmare), or use Rangy.js (pretty heavy).
function visibleTextNodes() {
var walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ALL,
function(node) {
if (node.nodeType == 3) {
return NodeFilter.FILTER_ACCEPT;
} else if (node.offsetWidth && node.offsetHeight && node.style.visibility != 'hidden') {
return NodeFilter.FILTER_SKIP;
} else {
return NodeFilter.FILTER_REJECT;
}
},
false
);
for (var nodes = []; walker.nextNode();) {
nodes.push(walker.currentNode);
}
return nodes;
}
// Find the first match, select and scroll to it.
// Case- and whitespace- insensitive.
// For better scrolling to selection see https://gist.github.com/3744577
function highlight(needle) {
needle = needle.replace(/\s/g, '').toLowerCase();
var textNodes = visibleTextNodes();
for (var i = 0, texts = []; i < textNodes.length; i++) {
texts.push(textNodes[i].nodeValue.replace(/\s/g, '').toLowerCase());
}
var matchStart = texts.join('').indexOf(needle);
if (matchStart < 0) {
return false;
}
var nodeAndOffsetAtPosition = function(position) {
for (var i = 0, consumed = 0; consumed + texts[i].length < position; i++) {
consumed += texts[i].length;
}
var whitespacePrefix = textNodes[i].nodeValue.match(/^\s*/)[0];
return [textNodes[i], position - consumed + whitespacePrefix.length];
};
var range = document.createRange();
range.setStart.apply(range, nodeAndOffsetAtPosition(matchStart));
range.setEnd.apply( range, nodeAndOffsetAtPosition(matchStart + needle.length));
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
range.startContainer.parentNode.scrollIntoView();
}
highlight('hello world');

How to get the image element after insert using execCommand?

Is there any way to get the image element after we insert the image using execCommand? for example
e.execCommand('insertimage',0,'ronaldo.png')
Don't use insertimage, use plain old insertHTML and give the element you are inserting an ID so that you can reference it later.
i.e.,
function insertHTML(img) {
var id = "rand" + Math.random();
var doc = document.getElementById("editor");
doc = doc.document ? doc.document : doc.contentWindow.document;
img = "<img src='" + img + "' id=" + id + ">";
if(document.all) {
var range = doc.selection.createRange();
range.pasteHTML(img);
range.collapse(false);
range.select();
} else {
doc.execCommand("insertHTML", false, img);
}
return doc.getElementById(id);
};
You can use the fact that browsers place the caret immediately after the inserted image and work back from there. The following requires DOM Range and selection support, which rules out IE <= 8, but if that's important then you can use a library such as my own Rangy to fill that gap.
Demo: http://jsfiddle.net/xJkuj/
Code:
function previousNode(node) {
var previous = node.previousSibling;
if (previous) {
node = previous;
while (node.hasChildNodes()) {
node = node.lastChild;
}
return node;
}
var parent = node.parentNode;
if (parent && parent.nodeType.hasChildNodes()) {
return parent;
}
return null;
}
document.execCommand("InsertImage", false, "http://placekitten.com/200/300");
// Get the current selection
var sel = window.getSelection();
if (sel.rangeCount > 0) {
var range = sel.getRangeAt(0);
var node = range.startContainer;
if (node.hasChildNodes() && range.startOffset > 0) {
node = node.childNodes[range.startOffset - 1];
}
// Walk backwards through the DOM until we find an image
while (node) {
if (node.nodeType == 1 && node.tagName.toLowerCase() == "img") {
alert("Found inserted image with src " + node.src);
break;
}
node = previousNode(node);
}
}
This is my way:
e.execCommand('insertimage', 0, URI) // image's URI
image=$('img[src="'+URI+'"]').not('.handled').addClass('.handled');
//.not('.handled').addClass('.handled') is needed if there are many images with the same URI

How to get all elements that are highlighted

I want to get all of the elements in a user highlighted area. The problem is that I don't know how to traverse to different parts of the DOM, when the elements are on a different part of the tree. My code so far can only get the elements off the startContainer of the selection range. Do I need some sort of recursion? Here is my code:
getSelectedElementTags:function()
{
var range, sel, container;
sel = content.window.getSelection();
if (sel.getRangeAt)
{
if (sel.rangeCount > 0)
{
range = sel.getRangeAt(0);
}
}
else
{
range = content.window.createRange();
range.setStart(sel.anchorNode, sel.anchorOffset);
range.setEnd(sel.focusNode, sel.focusOffset);
alert("range created");
}
if (range)
{
container = range["startContainer"];
var elms = container.parentNode.getElementsByTagName("*");
elmlist = "parent: "+container.parentNode.tagName + " (" + elms.length + ")\n";
for (i in elms)
{
if (elms[i].tagName != null)
{
elmlist += elms[i].tagName+"\n";
}
}
alert(elmlist);
}
},
You could use a TreeWalker using document.createTreeWalker. An example is below. It lists all elements that are partially or fully selected. You can easily change the behaviour by modifying the parameters passed to document.createTreeWalker.
Note that in Firefox you don't need to check for the existence of the getRangeAt method of a selection. This check is only required for older versions of WebKit. Also, IE < 9 does not support TreeWalker or Range, so the following won't work in those browsers.
Edit Fixed as per comments below.
function rangeIntersectsNode(range, node) {
var nodeRange;
if (range.intersectsNode) {
return range.intersectsNode(node);
} else {
nodeRange = node.ownerDocument.createRange();
try {
nodeRange.selectNode(node);
} catch (e) {
nodeRange.selectNodeContents(node);
}
return range.compareBoundaryPoints(Range.END_TO_START, nodeRange) == -1 &&
range.compareBoundaryPoints(Range.START_TO_END, nodeRange) == 1;
}
}
function getSelectedElementTags(win) {
var range, sel, elmlist, treeWalker, containerElement;
sel = win.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0);
}
if (range) {
containerElement = range.commonAncestorContainer;
if (containerElement.nodeType != 1) {
containerElement = containerElement.parentNode;
}
treeWalker = win.document.createTreeWalker(
containerElement,
NodeFilter.SHOW_ELEMENT,
function(node) { return rangeIntersectsNode(range, node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; },
false
);
elmlist = [treeWalker.currentNode];
while (treeWalker.nextNode()) {
elmlist.push(treeWalker.currentNode);
}
console.log(elmlist);
}
}
<input type="button" onclick="getSelectedElementTags(window)" value="Get selected elements">

How to get nodes lying inside a range with javascript?

I'm trying to get all the DOM nodes that are within a range object, what's the best way to do this?
var selection = window.getSelection(); //what the user has selected
var range = selection.getRangeAt(0); //the first range of the selection
var startNode = range.startContainer;
var endNode = range.endContainer;
var allNodes = /*insert magic*/;
I've been been thinking of a way for the last few hours and came up with this:
var getNextNode = function(node, skipChildren){
//if there are child nodes and we didn't come from a child node
if (node.firstChild && !skipChildren) {
return node.firstChild;
}
if (!node.parentNode){
return null;
}
return node.nextSibling
|| getNextNode(node.parentNode, true);
};
var getNodesInRange = function(range){
var startNode = range.startContainer.childNodes[range.startOffset]
|| range.startContainer;//it's a text node
var endNode = range.endContainer.childNodes[range.endOffset]
|| range.endContainer;
if (startNode == endNode && startNode.childNodes.length === 0) {
return [startNode];
};
var nodes = [];
do {
nodes.push(startNode);
}
while ((startNode = getNextNode(startNode))
&& (startNode != endNode));
return nodes;
};
However when the end node is the parent of the start node it returns everything on the page. I'm sure I'm overlooking something obvious? Or maybe going about it in totally the wrong way.
MDC/DOM/range
Here's an implementation I came up with to solve this:
function getNextNode(node)
{
if (node.firstChild)
return node.firstChild;
while (node)
{
if (node.nextSibling)
return node.nextSibling;
node = node.parentNode;
}
}
function getNodesInRange(range)
{
var start = range.startContainer;
var end = range.endContainer;
var commonAncestor = range.commonAncestorContainer;
var nodes = [];
var node;
// walk parent nodes from start to common ancestor
for (node = start.parentNode; node; node = node.parentNode)
{
nodes.push(node);
if (node == commonAncestor)
break;
}
nodes.reverse();
// walk children and siblings from start until end is found
for (node = start; node; node = getNextNode(node))
{
nodes.push(node);
if (node == end)
break;
}
return nodes;
}
The getNextNode will skip your desired endNode recursively if its a parent node.
Perform the conditional break check inside of the getNextNode instead:
var getNextNode = function(node, skipChildren, endNode){
//if there are child nodes and we didn't come from a child node
if (endNode == node) {
return null;
}
if (node.firstChild && !skipChildren) {
return node.firstChild;
}
if (!node.parentNode){
return null;
}
return node.nextSibling
|| getNextNode(node.parentNode, true, endNode);
};
and in while statement:
while (startNode = getNextNode(startNode, false , endNode));
The Rangy library has a Range.getNodes([Array nodeTypes[, Function filter]]) function.
I made 2 additional fixes based on MikeB's answer to improve the accuracy of the selected nodes.
I'm particularly testing this on select all operations, other than range selection made by dragging the cursor along text spanning across multiple elements.
In Firefox, hitting select all (CMD+A) returns a range where it's startContainer & endContainer is the contenteditable div, the difference is in the startOffset & endOffset where it's respectively the index of the first and the last child node.
In Chrome, hitting select all (CMD+A) returns a range where it's startContainer is the first child node of the contenteditable div, and the endContainer is the last child node of the contenteditable div.
The modifications I've added work around the discrepancies between the two. You can see the comments in the code for additional explanation.
function getNextNode(node) {
if (node.firstChild)
return node.firstChild;
while (node) {
if (node.nextSibling) return node.nextSibling;
node = node.parentNode;
}
}
function getNodesInRange(range) {
// MOD #1
// When the startContainer/endContainer is an element, its
// startOffset/endOffset basically points to the nth child node
// where the range starts/ends.
var start = range.startContainer.childNodes[range.startOffset] || range.startContainer;
var end = range.endContainer.childNodes[range.endOffset] || range.endContainer;
var commonAncestor = range.commonAncestorContainer;
var nodes = [];
var node;
// walk parent nodes from start to common ancestor
for (node = start.parentNode; node; node = node.parentNode)
{
nodes.push(node);
if (node == commonAncestor)
break;
}
nodes.reverse();
// walk children and siblings from start until end is found
for (node = start; node; node = getNextNode(node))
{
// MOD #2
// getNextNode might go outside of the range
// For a quick fix, I'm using jQuery's closest to determine
// when it goes out of range and exit the loop.
if (!$(node.parentNode).closest(commonAncestor)[0]) break;
nodes.push(node);
if (node == end)
break;
}
return nodes;
};
below code solve your problem
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>payam jabbari</title>
<script src="http://code.jquery.com/jquery-2.0.2.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function(){
var startNode = $('p.first').contents().get(0);
var endNode = $('span.second').contents().get(0);
var range = document.createRange();
range.setStart(startNode, 0);
range.setEnd(endNode, 5);
var selection = document.getSelection();
selection.addRange(range);
// below code return all nodes in selection range. this code work in all browser
var nodes = range.cloneContents().querySelectorAll("*");
for(var i=0;i<nodes.length;i++)
{
alert(nodes[i].innerHTML);
}
});
</script>
</head>
<body>
<div>
<p class="first">Even a week ago, the idea of a Russian military intervention in Ukraine seemed far-fetched if not totally alarmist. But the arrival of Russian troops in Crimea over the weekend has shown that he is not averse to reckless adventures, even ones that offer little gain. In the coming days and weeks</p>
<ol>
<li>China says military will respond to provocations.</li>
<li >This Man Has Served 20 <span class="second"> Years—and May Die—in </span> Prison for Marijuana.</li>
<li>At White House, Israel's Netanyahu pushes back against Obama diplomacy.</li>
</ol>
</div>
</body>
</html>
Annon, great work. I've modified the original plus included Stefan's modifications in the following.
In addition, I removed the reliance on Range, which converts the function into an generic algorithm to walk between two nodes. Plus, I've wrapped everything into a single function.
Thoughts on other solutions:
Not interested in relying on jquery
Using cloneNode lifts the results to a fragment, which prevents many operations one might want to conduct during filtering.
Using querySelectAll on a cloned fragment is wonky because the start or end nodes may be within a wrapping node, hence the parser may not have the closing tag?
Example:
<div>
<p>A</p>
<div>
<p>B</p>
<div>
<p>C</p>
</div>
</div>
</div>
Assume start node is the "A" paragraph, and the end node is the "C" paragraph
. The resulting cloned fragment would be:
<p>A</p>
<div>
<p>B</p>
<div>
<p>C</p>
and we're missing closing tags? resulting in funky DOM structure?
Anyway, here's the function, which includes a filter option, which should return TRUE or FALSE to include/exclude from results.
var getNodesBetween = function(startNode, endNode, includeStartAndEnd, filter){
if (startNode == endNode && startNode.childNodes.length === 0) {
return [startNode];
};
var getNextNode = function(node, finalNode, skipChildren){
//if there are child nodes and we didn't come from a child node
if (finalNode == node) {
return null;
}
if (node.firstChild && !skipChildren) {
return node.firstChild;
}
if (!node.parentNode){
return null;
}
return node.nextSibling || getNextNode(node.parentNode, endNode, true);
};
var nodes = [];
if(includeStartAndEnd){
nodes.push(startNode);
}
while ((startNode = getNextNode(startNode, endNode)) && (startNode != endNode)){
if(filter){
if(filter(startNode)){
nodes.push(startNode);
}
} else {
nodes.push(startNode);
}
}
if(includeStartAndEnd){
nodes.push(endNode);
}
return nodes;
};
I wrote the perfect code for this and it works 100% for every node :
function getNodesInSelection() {
var range = window.getSelection().getRangeAt(0);
var node = range.startContainer;
var ranges = []
var nodes = []
while (node != null) {
var r = document.createRange();
r.selectNode(node)
if(node == range.startContainer){
r.setStart(node, range.startOffset)
}
if(node == range.endContainer){
r.setEnd(node, range.endOffset)
}
ranges.push(r)
nodes.push(node)
node = getNextElementInRange(node, range)
}
// do what you want with ranges and nodes
}
here are some helper functions
function getClosestUncle(node) {
var parent = node.parentElement;
while (parent != null) {
var uncle = parent.nextSibling;
if (uncle != null) {
return uncle;
}
uncle = parent.nextElementSibling;
if (uncle != null) {
return uncle;
}
parent = parent.parentElement
}
return null
}
function getFirstChild(_node) {
var deep = _node
while (deep.firstChild != null) {
deep = deep.firstChild
}
return deep
}
function getNextElementInRange(currentNode, range) {
var sib = currentNode.nextSibling;
if (sib != null && range.intersectsNode(sib)) {
return getFirstChild(sib)
}
var sibEl = currentNode.nextSiblingElemnent;
if (sibEl != null && range.intersectsNode(sibEl)) {
return getFirstChild(sibEl)
}
var uncle = getClosestUncle(currentNode);
var nephew = getFirstChild(uncle)
if (nephew != null && range.intersectsNode(nephew)) {
return nephew
}
return null
}
bob. the function only returns the startNode and endNode. the nodes in between do not get pushed to the array.
seems the while loop returns null on getNextNode() hence that block never gets executed.
here is function return you array of sub-ranges
function getSafeRanges(range) {
var doc = document;
var commonAncestorContainer = range.commonAncestorContainer;
var startContainer = range.startContainer;
var endContainer = range.endContainer;
var startArray = new Array(0),
startRange = new Array(0);
var endArray = new Array(0),
endRange = new Array(0);
// ##### If start container and end container is same
if (startContainer == endContainer) {
return [range];
} else {
for (var i = startContainer; i != commonAncestorContainer; i = i.parentNode) {
startArray.push(i);
}
for (var i = endContainer; i != commonAncestorContainer; i = i.parentNode) {
endArray.push(i);
}
}
if (0 < startArray.length) {
for (var i = 0; i < startArray.length; i++) {
if (i) {
var node = startArray[i - 1];
while ((node = node.nextSibling) != null) {
startRange = startRange.concat(getRangeOfChildNodes(node));
}
} else {
var xs = doc.createRange();
var s = startArray[i];
var offset = range.startOffset;
var ea = (startArray[i].nodeType == Node.TEXT_NODE) ? startArray[i] : startArray[i].lastChild;
xs.setStart(s, offset);
xs.setEndAfter(ea);
startRange.push(xs);
}
}
}
if (0 < endArray.length) {
for (var i = 0; i < endArray.length; i++) {
if (i) {
var node = endArray[i - 1];
while ((node = node.previousSibling) != null) {
endRange = endRange.concat(getRangeOfChildNodes(node));
}
} else {
var xe = doc.createRange();
var sb = (endArray[i].nodeType == Node.TEXT_NODE) ? endArray[i] : endArray[i].firstChild;
var end = endArray[i];
var offset = range.endOffset;
xe.setStartBefore(sb);
xe.setEnd(end, offset);
endRange.unshift(xe);
}
}
}
var topStartNode = startArray[startArray.length - 1];
var topEndNode = endArray[endArray.length - 1];
var middleRange = getRangeOfMiddleElements(topStartNode, topEndNode);
startRange = startRange.concat(middleRange);
response = startRange.concat(endRange);
return response;
}
With generator and document.createTreeWalker:
function *getNodeInRange(range) {
let [start, end] = [range.startContainer, range.endContainer]
if (start.nodeType < Node.TEXT_NODE || Node.COMMENT_NODE < start.nodeType) {
start = start.childNodes[range.startOffset]
}
if (end.nodeType < Node.TEXT_NODE || Node.COMMENT_NODE < end.nodeType) {
end = end.childNodes[range.endOffset-1]
}
const relation = start.compareDocumentPosition(end)
if (relation & Node.DOCUMENT_POSITION_PRECEDING) {
[start, end] = [end, start]
}
const walker = document.createTreeWalker(
document, NodeFilter.SHOW_ALL
)
walker.currentNode = start
yield start
while (walker.parentNode()) yield walker.currentNode
if (!start.isSameNode(end)) {
walker.currentNode = start
while (walker.nextNode()) {
yield walker.currentNode
if (walker.currentNode.isSameNode(end)) break
}
}
const subWalker = document.createTreeWalker(
end, NodeFilter.SHOW_ALL
)
while (subWalker.nextNode()) yield subWalker.currentNode
}

Categories

Resources