Split an element containing TextNodes and elements using jQuery
contenteditable, html, javascript, jquery, selection
Solution
This seems to work:
Demo
function start () {
var range = window.getSelection().getRangeAt(0);
split(
range.startContainer,
range.startOffset,
range.commonAncestorContainer,
false
);
split(
range.endContainer,
range.endOffset,
range.commonAncestorContainer,
true
);
}
function split(node, offset, ancestor, backwards) {
var clone;
if(backwards) {
clone = node;
node = node.splitText(offset);
}else{
clone = node.splitText(offset);
}
if(node == ancestor) return;
var parent;
while((parent = node.parentNode) && parent != ancestor) {
var parentClone = parent.cloneNode(false);
appendUntil(parentClone, parent, node, !backwards);
parentClone.insertBefore(clone, backwards ? null : parentClone.firstChild);
node = parent;
clone = parentClone;
}
insertAdjacent(ancestor, clone, node, backwards);
}
function appendUntil(target, parent, until, fromEnd) {
var from, to, sibling;
if(fromEnd) {
from = until.nextSibling;
to = null;
} else {
from = parent.firstChild;
to = until;
}
while(from && from != to) {
sibling = from.nextSibling;
target.appendChild(from);
from = sibling;
}
}
function insertAdjacent(parent, newEl, refEl, before) {
parent.insertBefore(newEl, before ? refEl : refEl.nextSibling);
}
Problem
I need to split an HTML element based on a users selection using jQuery. In the following example square brackets indicate the selection: ``` Lor[em <a>ips]um <span>dolor</span></a> ``` should become ``` Lor [ em <a>ips</a> ] <a>um <span>dolor</span></a> ``` To do this I create a range, find the TextNodes containing the selection boundaries and split them using `splitText(index)`. Next I check whether the parent element must also be split. If yes, I clone and empty them, move the second parts of the original elements into the clones and insert them after the original like so: ``` var tail = textNode.splitText( offset ); var $parent = $(textNode).parent(); if ($parent.is("span")) { var $tail = $parent.clone(); $tail.contents().remove(); $tail = $tail.append(tail).insertAfter($parent); if ($parent.parent().is("a")) { $tail = $parent.parent().clone(); $tail.contents().remove(); $tail = $tail.append($tail).insertAfter($parent.parent()); } return $tail[0]; } else if ($parent.is("a")) { var $tail = $parent.clone(); $tail.contents().remove(); $tail = $tail.append(tail).insertAfter($parent); return $tail[0]; } return tail; ``` Problem is, though, `tail` only contains the second part of the TextNode. The following `<span />` is not moved, so the HTML is messed up like so (selection is lost underway, but not important): ``` Lor em <a>ips <span>dolor</span></a> <a>um</a> ``` I also tried $(tail).nextAll() but it seems to return an empty set. Does anybody have an idea how I can achieve this? If anything is not clear, please ask for more detail. EDIT: Like suggested I created the following http://jsfiddle.net/7PdLd/4/.