I've been searching through a lot of Range and Selection related questions (mostly answered by @tim-down), but I can't quite get what I need, although I come close.
I want to search the currently focused text node for the word foo
. If I find it - replace it with bar
and set the caret position at the end of the replaced word. For example:
"Lorem ipsum dolor foo amet, consectetur adipiscing elit."
Turns into:
"Lorem ipsum dolor bar amet, consectetur adipiscing elit."
// -------------------^--- caret position
What I currently have works only halfway - it removes the text, but doesn't add anything. I'm not sure it's the best approach, though:
function replacer(search, replace) {
var sel = window.getSelection();
if (!sel.focusNode) {
return;
}
var startIndex = sel.focusNode.nodeValue.indexOf(search);
var endIndex = startIndex + search.length;
if (startIndex === -1) {
return;
}
var range = document.createRange();
range.setStart(sel.focusNode, startIndex);
range.setEnd(sel.focusNode, endIndex);
range.insertNode(document.createTextNode("bar"));
sel.removeAllRanges();
sel.addRange(range);
}
document.addEventListener("keypress", function() {
replacer("foo", "bar");
});
<div contenteditable="true" style="width: 600px; height: 300px;">Lorem ipsum dolor foo amet, consectetur adipiscing elit.</div>
Note: I only care for compatibility with Chrome.
Explain in code comment and console log.
See about selection
function replacer(search, replace) {
var sel = window.getSelection();
if (!sel.focusNode) {
return;
}
var startIndex = sel.focusNode.nodeValue.indexOf(search);
var endIndex = startIndex + search.length;
if (startIndex === -1) {
return;
}
console.log("first focus node: ", sel.focusNode.nodeValue);
var range = document.createRange();
//Set the range to contain search text
range.setStart(sel.focusNode, startIndex);
range.setEnd(sel.focusNode, endIndex);
//Delete search text
range.deleteContents();
console.log("focus node after delete: ", sel.focusNode.nodeValue);
//Insert replace text
range.insertNode(document.createTextNode(replace));
console.log("focus node after insert: ", sel.focusNode.nodeValue);
//Move the caret to end of replace text
sel.collapse(sel.focusNode, 0);
}
document.addEventListener("keypress", function() {
replacer("foo", "bar");
});
<div contenteditable="true" style="width: 600px; height: 300px;" id='content'>Lorem ipsum dolor foo amet, consectetur adipiscing elit.</div>
Using createNodeIterator
to iterate all textNodes with NodeFilter.SHOW_TEXT
, it is easy to hunt down all occurrences of the string foo
and replace with anything desired, bar
textNode in this case.
The second stage, which should be decoupled from the find-and-replace function, is the caret placement request, and is implemented by the function setRangeAtEnd
, which receives a single argument, node
, which is available from the output of the first function, replaceTextWithNode
, which returns an Array of all the nodes replaced in the contentEditable.
const elm = document.querySelector('[contenteditable]');
function replaceTextWithNode( elm, text, replacerNode ){
var iter = document.createNodeIterator(elm, NodeFilter.SHOW_TEXT),
textnode, replacedNode, idx, newNode,
maxIterations = 100,
addedNodes = [];
while( textnode = iter.nextNode() ){
if( !maxIterations-- ) break;
// get the index of which the text is within the textNode (if at all)
idx = textnode.nodeValue.indexOf(text)
if( idx == -1 ) continue
replacedNode = textnode.splitText(idx)
newNode = replacerNode.cloneNode()
// clean up the tag's string and put tag element instead
replacedNode.nodeValue = replacedNode.nodeValue.replace(text, '')
textnode.parentNode.insertBefore(newNode, replacedNode)
addedNodes.push(newNode)
}
return addedNodes
}
function setRangeAtEnd( node ){
node = node.firstChild || node;
const sel = document.getSelection()
if( sel.rangeCount )
['Start', 'End'].forEach(pos =>
sel.getRangeAt(0)["set" + pos](node, node.length)
)
}
// Replace all 'foo' with 'bar' textNodes
var addedNodes = replaceTextWithNode(elm, 'foo', document.createTextNode('bar'))
// Place caret at last occurrence of 'bar'
elm.focus()
setRangeAtEnd(addedNodes[addedNodes.length-1])
<div contenteditable>Lorem ipsum foo dolor foo amet foo, consectetur adipiscing elit.</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With