CKEDITOR -- 在 DOM 修改后无法恢复光标位置
CKEDITOR -- cannnot restore cursor location after DOM modification
我读过这篇 excellent answer to pretty much the same question. However, I have tried every technique that @Reinmar 推荐的文章,其中 none 似乎有效。
情况是我从编辑器中获取当前 HTML 并将某些片段包装在 span 标签中。然后我将现在修改的 HTML 设置回来并尝试恢复用户的光标位置。技术不行。
这里是重现问题的一个非常简单的例子:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="//cdn.ckeditor.com/4.4.7/standard/ckeditor.js"></script>
</head>
<body>
<textarea id="cktest"><p>Sometimes Lorem. Sometime Ipsum. Always dolor.</p></textarea>
<script type="text/javascript">
(function () {
var checkTimeout;
var bookmark;
var storeCursorLocation = function(editor) {
bookmark = editor.getSelection().createBookmarks();
};
var restoreCursorLocation = function(editor) {
editor.getSelection().selectBookmarks(bookmark);
};
var validateText = function(editor) {
storeCursorLocation(editor);
var data = editor.document.getBody().getHtml();
data = data.replace("Lorem", "<span class='err-item'>Lorem</span>");
editor.document.getBody().setHtml(data);
restoreCursorLocation(editor);
};
CKEDITOR.replace('cktest', {
on: {
'instanceReady': function(evt) {
},
'key' : function(evt) {
clearTimeout(checkTimeout);
checkTimeout = setTimeout(function () {
validateText(evt.editor);
}, 1000);
}
}
});
})();
</script>
</body>
</html>
此代码会在用户按键时启动计时器,然后在用户停止按键后等待 1 秒进行检查。
将其复制到一个新的 .html 文件中,然后在您最喜欢的浏览器中 运行(我使用的是 Chrome)。
加载 CKEditor 时,使用鼠标将光标放在文本中间的某个位置。然后按 CTRL 键并等待 1 秒钟。您会看到光标跳回到文本的开头。
此代码示例使用
editor.getSelection().createBookmarks();
创建书签。不过我也试过:
editor.getSelection().createBookmarks(true);
和
editor.getSelection().createBookmarks2();
我也试过使用
保存范围
var ranges = editor.getSelection().getRanges();
和
editor.getSelection().selectRanges(ranges);
在 restoreCursorLocation 函数中。
检查在 https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
中设置 innerHtml 时的默认行为
Removes all of element's children, parses the content string and assigns the resulting nodes as children of the element
CKEDITOR 中的书签是隐藏的 span 元素,设置 innerHtml 将删除所有这些元素。
反正解决方法很简单。
将您的 storeCursorLocation 函数更改为此
var storeCursorLocation = function(editor) {
bookmark = editor.getSelection().createBookmarks(true);
};
当您将 true 作为参数传递时,它将使用 id 作为参考,而不是存储 DOM 元素,因此您可以在 innerHtml 更改后恢复。
{编辑}
阅读 解决方案 2 来自 @Reinmar 他说
If you can avoid uncontrolled innerHTML changes and instead append/remove/move some nodes, then just remember that you have to preserve these elements and this method will work perfectly. You can also move bookmarks' elements if your modifications should change the selection as well.
如果您无法替换元素 innerHtml 的内容,这就是您的做法。
此解决方案效率较低,但在某些情况下可能有效
将 validateText 函数更改为此。
var validateText = function(editor) {
storeCursorLocation(editor);
var parent = editor.document.getBody().$.firstChild,
nodes = parent.childNodes,
nodeText,
words,
index = 0,
current,
newElement;
while (index < nodes.length) {
current = nodes[index];
nodeText = current.nodeValue;
if (current.nodeType === Node.TEXT_NODE && nodeText.indexOf('Lorem') !== -1) {
words = nodeText.split('Lorem');
newElement = document.createTextNode(words[0]);
parent.insertBefore(newElement, current);
newElement = document.createTextNode(words[1]);
parent.insertBefore(newElement, current.nextSibling);
newElement = document.createElement('span')
newElement.className = 'err-item';
newElement.innerHTML = 'Lorem';
parent.replaceChild(newElement, current);
break;
}
index++;
}
restoreCursorLocation(editor);
};
基本上,我正在遍历 chkeditor 正文中第一个 p 的节点,并仅将包含 Lorem 的文本类型的节点替换为跨度,并将其余文本作为文本元素添加到前后。如果您像以前一样替换整个文本,它会从 DOM 中删除书签,因此当您尝试恢复它们时它们不存在。
(function () {
var checkTimeout;
var bookmark;
var storeCursorLocation = function( editor ) {
bookmark = editor.getSelection().createBookmarks( true );
};
var restoreCursorLocation = function( editor ) {
//editor.focus();
editor.getSelection().selectBookmarks( bookmark );
};
var validateText = function( editor ) {
storeCursorLocation( editor );
var data = editor.document.getBody().getHtml();
data = data.replace( "spaceflight", "<span class='err-item'>spaceflight</span>" );
editor.document.getBody().setHtml( data );
restoreCursorLocation( editor );
//fire this event after DOM changes if working with widgets
//editor.fire( 'contentDomInvalidated' );
};
var editor = CKEDITOR.replace( 'editor1', {
extraAllowedContent : 'span(err-item)',
on: {
"pluginsLoaded" : function( event ){
editor.on( 'contentDom', function() {
var editable = editor.editable();
editable.attachListener( editable, 'keyup', function( e ) {
clearTimeout( checkTimeout );
checkTimeout = setTimeout(function () {
validateText( editor );
}, 100 );
});
});
}
}
});
})();
我检查了您的代码,进行了一些更正,上面的代码似乎工作正常。我知道你说过你已经尝试过了,但对我来说 createBookmarks(true)
已经成功了。
说明与注释:
- 您需要使用
createBookmarks(true)
将唯一跨度插入 HTML。这样的书签不受您在 DOM 中所做的更改的影响(当然有限制,例如您的自定义更改删除书签)。
- 使用
getBody().getHtml()
和 getBody().setHTML()
很聪明。如果您使用 editor.getData()
这将删除代表书签的空范围。
但请注意,这种方法可能会破坏小部件,因此需要在此类更改后触发 contentDomInvalidated
事件。
- 在恢复选择之前我也在关注编辑器,但这是“以防万一”的解决方案,因为我注意到编辑器在没有它的情况下选择了书签。但是,如果由于某种原因您失去了选择,这将是另一回事。
我读过这篇 excellent answer to pretty much the same question. However, I have tried every technique that @Reinmar 推荐的文章,其中 none 似乎有效。
情况是我从编辑器中获取当前 HTML 并将某些片段包装在 span 标签中。然后我将现在修改的 HTML 设置回来并尝试恢复用户的光标位置。技术不行。
这里是重现问题的一个非常简单的例子:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="//cdn.ckeditor.com/4.4.7/standard/ckeditor.js"></script>
</head>
<body>
<textarea id="cktest"><p>Sometimes Lorem. Sometime Ipsum. Always dolor.</p></textarea>
<script type="text/javascript">
(function () {
var checkTimeout;
var bookmark;
var storeCursorLocation = function(editor) {
bookmark = editor.getSelection().createBookmarks();
};
var restoreCursorLocation = function(editor) {
editor.getSelection().selectBookmarks(bookmark);
};
var validateText = function(editor) {
storeCursorLocation(editor);
var data = editor.document.getBody().getHtml();
data = data.replace("Lorem", "<span class='err-item'>Lorem</span>");
editor.document.getBody().setHtml(data);
restoreCursorLocation(editor);
};
CKEDITOR.replace('cktest', {
on: {
'instanceReady': function(evt) {
},
'key' : function(evt) {
clearTimeout(checkTimeout);
checkTimeout = setTimeout(function () {
validateText(evt.editor);
}, 1000);
}
}
});
})();
</script>
</body>
</html>
此代码会在用户按键时启动计时器,然后在用户停止按键后等待 1 秒进行检查。
将其复制到一个新的 .html 文件中,然后在您最喜欢的浏览器中 运行(我使用的是 Chrome)。
加载 CKEditor 时,使用鼠标将光标放在文本中间的某个位置。然后按 CTRL 键并等待 1 秒钟。您会看到光标跳回到文本的开头。
此代码示例使用
editor.getSelection().createBookmarks();
创建书签。不过我也试过:
editor.getSelection().createBookmarks(true);
和
editor.getSelection().createBookmarks2();
我也试过使用
保存范围var ranges = editor.getSelection().getRanges();
和
editor.getSelection().selectRanges(ranges);
在 restoreCursorLocation 函数中。
检查在 https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
中设置 innerHtml 时的默认行为Removes all of element's children, parses the content string and assigns the resulting nodes as children of the element
CKEDITOR 中的书签是隐藏的 span 元素,设置 innerHtml 将删除所有这些元素。
反正解决方法很简单。
将您的 storeCursorLocation 函数更改为此
var storeCursorLocation = function(editor) {
bookmark = editor.getSelection().createBookmarks(true);
};
当您将 true 作为参数传递时,它将使用 id 作为参考,而不是存储 DOM 元素,因此您可以在 innerHtml 更改后恢复。
{编辑}
阅读 解决方案 2 来自 @Reinmar 他说
If you can avoid uncontrolled innerHTML changes and instead append/remove/move some nodes, then just remember that you have to preserve these elements and this method will work perfectly. You can also move bookmarks' elements if your modifications should change the selection as well.
如果您无法替换元素 innerHtml 的内容,这就是您的做法。
此解决方案效率较低,但在某些情况下可能有效
将 validateText 函数更改为此。
var validateText = function(editor) {
storeCursorLocation(editor);
var parent = editor.document.getBody().$.firstChild,
nodes = parent.childNodes,
nodeText,
words,
index = 0,
current,
newElement;
while (index < nodes.length) {
current = nodes[index];
nodeText = current.nodeValue;
if (current.nodeType === Node.TEXT_NODE && nodeText.indexOf('Lorem') !== -1) {
words = nodeText.split('Lorem');
newElement = document.createTextNode(words[0]);
parent.insertBefore(newElement, current);
newElement = document.createTextNode(words[1]);
parent.insertBefore(newElement, current.nextSibling);
newElement = document.createElement('span')
newElement.className = 'err-item';
newElement.innerHTML = 'Lorem';
parent.replaceChild(newElement, current);
break;
}
index++;
}
restoreCursorLocation(editor);
};
基本上,我正在遍历 chkeditor 正文中第一个 p 的节点,并仅将包含 Lorem 的文本类型的节点替换为跨度,并将其余文本作为文本元素添加到前后。如果您像以前一样替换整个文本,它会从 DOM 中删除书签,因此当您尝试恢复它们时它们不存在。
(function () {
var checkTimeout;
var bookmark;
var storeCursorLocation = function( editor ) {
bookmark = editor.getSelection().createBookmarks( true );
};
var restoreCursorLocation = function( editor ) {
//editor.focus();
editor.getSelection().selectBookmarks( bookmark );
};
var validateText = function( editor ) {
storeCursorLocation( editor );
var data = editor.document.getBody().getHtml();
data = data.replace( "spaceflight", "<span class='err-item'>spaceflight</span>" );
editor.document.getBody().setHtml( data );
restoreCursorLocation( editor );
//fire this event after DOM changes if working with widgets
//editor.fire( 'contentDomInvalidated' );
};
var editor = CKEDITOR.replace( 'editor1', {
extraAllowedContent : 'span(err-item)',
on: {
"pluginsLoaded" : function( event ){
editor.on( 'contentDom', function() {
var editable = editor.editable();
editable.attachListener( editable, 'keyup', function( e ) {
clearTimeout( checkTimeout );
checkTimeout = setTimeout(function () {
validateText( editor );
}, 100 );
});
});
}
}
});
})();
我检查了您的代码,进行了一些更正,上面的代码似乎工作正常。我知道你说过你已经尝试过了,但对我来说 createBookmarks(true)
已经成功了。
说明与注释:
- 您需要使用
createBookmarks(true)
将唯一跨度插入 HTML。这样的书签不受您在 DOM 中所做的更改的影响(当然有限制,例如您的自定义更改删除书签)。 - 使用
getBody().getHtml()
和getBody().setHTML()
很聪明。如果您使用editor.getData()
这将删除代表书签的空范围。 但请注意,这种方法可能会破坏小部件,因此需要在此类更改后触发contentDomInvalidated
事件。 - 在恢复选择之前我也在关注编辑器,但这是“以防万一”的解决方案,因为我注意到编辑器在没有它的情况下选择了书签。但是,如果由于某种原因您失去了选择,这将是另一回事。