Javascript Contenteditable - Wrap Cross-tag Selections
I'm experimenting a bit with contentEditable and encountered this issue: I have the following js snippet var range = document.getSelection().getRangeAt(0); var newNode = document.c
Solution 1:
The basic approach if you're only interested in wrapping the text parts is:
- Get the selection range
- For each range boundary, if it lies in the middle of a text node, you need to split the text node in two at the boundary and update the range's boundary so that the range stays in place (example code from Rangy)
- Get all the text nodes within the range (example code)
- Surround each text node in a
<span>
element - Re-select the range
This is the approach taken by the class applier module of my Rangy library.
I've created an example, mostly using code adapted from Rangy:
functiongetNextNode(node) {
var next = node.firstChild;
if (next) {
return next;
}
while (node) {
if ( (next = node.nextSibling) ) {
return next;
}
node = node.parentNode;
}
}
functiongetNodesInRange(range) {
var start = range.startContainer;
var end = range.endContainer;
var commonAncestor = range.commonAncestorContainer;
var nodes = [];
var node;
// Walk parent nodes from start to common ancestorfor (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 foundfor (node = start; node; node = getNextNode(node)) {
nodes.push(node);
if (node == end) {
break;
}
}
return nodes;
}
functiongetNodeIndex(node) {
var i = 0;
while ( (node = node.previousSibling) ) {
++i;
}
return i;
}
functioninsertAfter(node, precedingNode) {
var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
if (nextNode) {
parent.insertBefore(node, nextNode);
} else {
parent.appendChild(node);
}
return node;
}
// Note that we cannot use splitText() because it is bugridden in IE 9.functionsplitDataNode(node, index) {
var newNode = node.cloneNode(false);
newNode.deleteData(0, index);
node.deleteData(index, node.length - index);
insertAfter(newNode, node);
return newNode;
}
functionisCharacterDataNode(node) {
var t = node.nodeType;
return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
}
functionsplitRangeBoundaries(range) {
var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
var startEndSame = (sc === ec);
// Split the end boundary if necessaryif (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
splitDataNode(ec, eo);
}
// Split the start boundary if necessaryif (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
sc = splitDataNode(sc, so);
if (startEndSame) {
eo -= so;
ec = sc;
} elseif (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
++eo;
}
so = 0;
}
range.setStart(sc, so);
range.setEnd(ec, eo);
}
functiongetTextNodesInRange(range) {
var textNodes = [];
var nodes = getNodesInRange(range);
for (var i = 0, node, el; node = nodes[i++]; ) {
if (node.nodeType == 3) {
textNodes.push(node);
}
}
return textNodes;
}
functionsurroundRangeContents(range, templateElement) {
splitRangeBoundaries(range);
var textNodes = getTextNodesInRange(range);
if (textNodes.length == 0) {
return;
}
for (var i = 0, node, el; node = textNodes[i++]; ) {
if (node.nodeType == 3) {
el = templateElement.cloneNode(false);
node.parentNode.insertBefore(el, node);
el.appendChild(node);
}
}
range.setStart(textNodes[0], 0);
var lastTextNode = textNodes[textNodes.length - 1];
range.setEnd(lastTextNode, lastTextNode.length);
}
document.onmouseup = function() {
if (window.getSelection) {
var templateElement = document.createElement("span");
templateElement.className = "highlight";
var sel = window.getSelection();
var ranges = [];
var range;
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
ranges.push( sel.getRangeAt(i) );
}
sel.removeAllRanges();
// Surround ranges in reverse document order to prevent surrounding subsequent ranges messing with already-surrounded ones
i = ranges.length;
while (i--) {
range = ranges[i];
surroundRangeContents(range, templateElement);
sel.addRange(range);
}
}
};
.highlight {
font-weight: bold;
color: red;
}
Select some of this text and it will be highlighted:
<ul><li>the <b>only entry</b> of the list</li></ul><p>Some text here in paragraph</p><ul><li>the <b>only entry</b> of the list</li></ul><p>Some text here in paragraph</p><ul><li>the <b>only entry</b> of the list</li></ul><p>Some text here in paragraph</p>
Post a Comment for "Javascript Contenteditable - Wrap Cross-tag Selections"