替换 contenteditable 中的特定单词
我有一个内容可编辑的 div
I have a contenteditable div
<div id="divTest" contenteditable="true">
我需要从插入符号位置获取最后一个单词,并且在某些情况下我必须测试并仅删除这个特定单词.以下是我的表现
I need to get the last word from caret position and on certain condition I have to test and remove this specific word only. Below is how am I doing
$('#divTest').on('keyup focus', function (e) {
if (e.keyCode == 32) {
var lastWord = getWordPrecedingCaret(this), spanLastWord = $('#lastWord');
}
});
function getWordPrecedingCaret(containerEl) {
var preceding = "",
sel,
range,
precedingRange;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
range.setStart(containerEl, 0);
preceding = range.toString();
}
} else if ((sel = document.selection) && sel.type != "Control") {
range = sel.createRange();
precedingRange = range.duplicate();
precedingRange.moveToElementText(containerEl);
precedingRange.setEndPoint("EndToStart", range);
preceding = precedingRange.text;
}
var words = range.toString().trim().split(' '),
lastWord = words[words.length - 1];
if (lastWord) {
var resultValue = 'some'; // this value is coming from some other function
if (resultValue == lastWord) {
alert('do nothing');
// do nothing
}
else
{
alert('replace word');
// delete That specific word and replace if with resultValue
}
return lastWord;
}
}
演示:http://codepen.io/anon/pen/ogzpXV
我试过 range.deleteContents();但这将删除 div 中的所有内容.如何仅替换特定单词?
I have tried range.deleteContents(); but that will delete all the content in the div. How can I replace specific word only?
推荐答案
使用 Ranges
我们需要记住,我们使用的是节点,而不仅仅是渲染的文本.您要操作的结构是:
To work with Ranges
we need to keep in mind that we are working with Nodes, not only the text that is rendered. The structure you want to manipulate is:
<div id="divTest" contenteditable="true"> <-- Element Node
"some text" <-- TextNode
</div>
但也可能是:
<div id="divTest" contenteditable="true"> <-- Element Node
"some text" <-- TextNode
"more text" <-- TextNode
"" <-- TextNode
</div>
为了解决您的问题,只处理一个 TextNode
更简单,我建议使用 normalize()
函数将它们全部连接成一个.
To solve your problem is simplier to handle only one TextNode
, I propose to use the normalize()
function to join all of them into a single one.
那么您只需在 Range 设置为单词的边界.deleteContents" rel="nofollow noreferrer">deleteContents()
.删除后,您可以使用 TextNode 替换="nofollow noreferrer">insertNode()
.
Then you only need to set the Range
to the word's bounds before deleteContents()
. Once deleted, you can insert a new TextNode
with the substitution using insertNode()
.
var wordStart = range.toString().lastIndexOf(lastWord);
var wordEnd = wordStart + lastWord.length;
/* containerEl.firstChild refers to the div's TextNode */
range.setStart(containerEl.firstChild, wordStart);
range.setEnd(containerEl.firstChild, wordEnd);
range.deleteContents();
range.insertNode(document.createTextNode(resultValue));
为此,您需要文本位于单个 TextNode
中.但在 ìnsertNode
之后,div
将包含多个文本节点.要解决这个问题,只需调用 normalize()
加入所有 TextNode
元素.
For this to work, you need that the text is in a single TextNode
. But after ìnsertNode
the div
will contain multiple text nodes. To fix this simply call normalize()
to join all TextNode
elements.
containerEl.normalize();
正如 Basj 指出的那样,多行的原始解决方案失败了.那是因为当点击 ENTER 时,结构会从:
As Basj points out, the original solution fails for multiline. That's because when hitting ENTER the structure changes from:
<div id="divTest" contenteditable="true"> <-- Element Node
"some text" <-- TextNode
</div>
类似于:
<div id="divTest" contenteditable="true"> <-- Element Node
<div>"some text"</div>
<div>"more text"</div>
</div>
我已经更新了这个答案,但在这个问题上也值得阅读 Basj 的答案:在 contenteditable 中有多行时替换光标前的单词
I've updated this answer, but it's also worth to read Basj's answer at this question: Replace word before cursor, when multiple lines in contenteditable
JSFiddle 演示或可运行代码片段:
document.getElementById('divTest').onkeyup = function (e) {
if (e.keyCode == 32) {
getWordPrecedingCaret(this);
}
};
function getWordPrecedingCaret(containerEl) {
var preceding = "",
sel,
range,
precedingRange;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
range.setStart(containerEl, 0);
preceding = range.toString();
}
} else if ((sel = document.selection) && sel.type != "Control") {
range = sel.createRange();
precedingRange = range.duplicate();
precedingRange.moveToElementText(containerEl);
precedingRange.setEndPoint("EndToStart", range);
preceding = precedingRange.text;
}
var words = range.toString().trim().split(' '),
lastWord = words[words.length - 1];
if (lastWord) {
var resultValue = 'some'; // this value is coming from some other function
if (resultValue == lastWord) {
console.log('do nothing: ' + lastWord);
// do nothing
} else {
console.log('replace word ' + lastWord);
/* Find word start and end */
var wordStart = range.endContainer.data.lastIndexOf(lastWord);
var wordEnd = wordStart + lastWord.length;
console.log("pos: (" + wordStart + ", " + wordEnd + ")");
range.setStart(range.endContainer, wordStart);
range.setEnd(range.endContainer, wordEnd);
range.deleteContents();
range.insertNode(document.createTextNode(resultValue));
// delete That specific word and replace if with resultValue
/* Merge multiple text nodes */
containerEl.normalize();
}
return lastWord;
}
}
<div id="divTest" contenteditable="true">Write words here and hit SPACE BAR</div>
相关文章