Js: Get Array of All Selected Nodes in Contenteditable Div

JS: Get array of all selected nodes in contentEditable div

Here's a version that gives you the actual selected and partially selected nodes rather than clones. Alternatively you could use my Rangy library, which has a getNodes() method of its Range objects and works in IE < 9.

function nextNode(node) {
if (node.hasChildNodes()) {
return node.firstChild;
} else {
while (node && !node.nextSibling) {
node = node.parentNode;
}
if (!node) {
return null;
}
return node.nextSibling;
}
}

function getRangeSelectedNodes(range) {
var node = range.startContainer;
var endNode = range.endContainer;

// Special case for a range that is contained within a single node
if (node == endNode) {
return [node];
}

// Iterate nodes until we hit the end container
var rangeNodes = [];
while (node && node != endNode) {
rangeNodes.push( node = nextNode(node) );
}

// Add partially selected nodes at the start of the range
node = range.startContainer;
while (node && node != range.commonAncestorContainer) {
rangeNodes.unshift(node);
node = node.parentNode;
}

return rangeNodes;
}

function getSelectedNodes() {
if (window.getSelection) {
var sel = window.getSelection();
if (!sel.isCollapsed) {
return getRangeSelectedNodes(sel.getRangeAt(0));
}
}
return [];
}

How to get the selected element inside a contenteditable element

You can use a Range to refer to the selected part of the document and than check the commonAncestorContainer for retriving the <ul>.

In your example if you select part of the list you can retrieve the <ul> with something like:

    var sel = window.getSelection();
var range = sel.getRangeAt(0);
var ulTag = range.commonAncestorContainer;

If you instead want to get the element pointed by the cursor (without there being a selection) you can refer to the startContainer property and than check the parentNode for retriving the <li>. For example:

    var sel = window.getSelection();
var range = sel.getRangeAt(0);
var pointedLiTag = range.startContainer.parentNode;

Please note that those are just simple use cases, in real documents there's lots of nested elements, so you must ensure that the element you retrieve is the one you expect before using it.

Extracting a DOM element into an array of lines

Live demo here (click).

var myElem = document.getElementById('myElem');
var myBtn = document.getElementById('myBtn');

myBtn.addEventListener('click', function() {
var results = [];
var children = myElem.childNodes;
for (var i=0; i<children.length; ++i) {
var child = children[i];
if (child.nodeName === '#text') {
results.push(child.textContent);
}
else {
var subChildren = child.childNodes;
for (var j=0; j<subChildren.length; ++j) {
var subChild = subChildren[j];
results.push(subChild.textContent);
}
}
}
console.log(results);
});

Old Answer

How about this? Live demo here (click).

var myElem = document.getElementById('myElem');
var myBtn = document.getElementById('myBtn');

myBtn.addEventListener('click', function() {
var results = [];
var children = myElem.childNodes;
for (var i=0; i<children.length; ++i) {
var text = children[i].textContent;
if (text) { //remove empty lines
results.push(text);
}
}
console.log(results);
});

You can remove that if (text) statement if you want to keep the empty lines.

How to select text range within a contenteditable div that has no child nodes?

But mine don't have any children. Just the text within the div.

The text within the div is a child – it's a text node. That's what you want to target.

You will also need to trim its nodeValue to get the proper offset. Otherwise, the leading spaces will be included.

This seems to do what you want:

function SelectText(obj, start, stop) {  var mainDiv = $(obj)[0],      startNode = mainDiv.childNodes[0],      endNode = mainDiv.childNodes[0];
startNode.nodeValue = startNode.nodeValue.trim(); var range = document.createRange(); range.setStart(startNode, start); range.setEnd(endNode, stop + 1); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range);} //SelectText
$('#main').focus();
SelectText('#main', 6, 10);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><div id="main" contenteditable="true">  Hello World</div>

Building a :selection selector

This will get you all the elements that are completely selected:

var currentSelection = window.getSelection();
var firstRangeInSelection = currentSelection.getRangeAt(0);
var commonAncestor = firstRangeInSelection.commonAncestorContainer;

var nodesInSelection = $(commonAncestor).find("*").filter(function() {
return currentSelection.containsNode(this, false);
});

For more info on DOM Selections, check out this page.

How to get selected textnode in contentEditable div in IE?

Here's my version of the function you need from IERange, with my comments:

function getChildIndex(node) {
var i = 0;
while( (node = node.previousSibling) ) {
i++;
}
return i;
}

function getTextRangeBoundaryPosition(textRange, isStart) {
var workingRange = textRange.duplicate();
workingRange.collapse(isStart);
var containerElement = workingRange.parentElement();
var workingNode = document.createElement("span");
var comparison, workingComparisonType = isStart ?
"StartToStart" : "StartToEnd";

var boundaryPosition, boundaryNode;

// Move the working range through the container's children, starting at
// the end and working backwards, until the working range reaches or goes
// past the boundary we're interested in
do {
containerElement.insertBefore(workingNode, workingNode.previousSibling);
workingRange.moveToElementText(workingNode);
} while ( (comparison = workingRange.compareEndPoints(
workingComparisonType, textRange)) > 0 && workingNode.previousSibling);

// We've now reached or gone past the boundary of the text range we're
// interested in so have identified the node we want
boundaryNode = workingNode.nextSibling;
if (comparison == -1 && boundaryNode) {
// This must be a data node (text, comment, cdata) since we've overshot.
// The working range is collapsed at the start of the node containing
// the text range's boundary, so we move the end of the working range
// to the boundary point and measure the length of its text to get
// the boundary's offset within the node
workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);

boundaryPosition = {
node: boundaryNode,
offset: workingRange.text.length
};
} else {
// We've hit the boundary exactly, so this must be an element
boundaryPosition = {
node: containerElement,
offset: getChildIndex(workingNode)
};
}

// Clean up
workingNode.parentNode.removeChild(workingNode);

return boundaryPosition;
}

var textRange = document.selection.createRange();
var selectionStart = getTextRangeBoundaryPosition(textRange, true);
// selectionStart has properties 'node' and 'offset'

How to getSelection() within a specific div?

You are able to get the node element from the selection using .baseNode. From there you can get the parent node and use that for comparison.

function red(){
// If it's not the element with an id of "foo" stop the function and return
if(window.getSelection().baseNode.parentNode.id != "foo") return;
...
// Highlight if it is our div.
}

In the example below I made the div have an id that you can check to make sure it's that element:

Demo


As @z0mBi3 noted, this will work the first time. But may not work for many highlights (if they happen to get cleared). The <span> elements inside the div create a hierarchy where the div is the parent elements of many span elements. The solution to this would be to take traverse up through the ancestors of the node until you find one with the id of "foo".

Luckily you can use jQuery to do that for you by using their .closest() method:

if($(window.getSelection().baseNode).closest("#foo").attr("id") != "foo") return;

Here is an answer with a native JS implemented method of .closest().



Related Topics



Leave a reply



Submit