CKEDITOR - cannnot restores cursor location after changing DOM

I read this excellent answer in almost the same question. However, I tried every technique recommended by @Reinmar and none of them seem to work.

The situation is that I take the current HTML from the editor and wrap certain fragments in the span tags. Then, I now install the changed HTML back and try to restore the location of the user cursor. No technique is working.

Here is a very simple example to reproduce the problem:

<!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> 

This code starts the timer when the user presses a key, and then waits 1 second after they stop pressing the keys to perform a check.

Copy this into a new .html file and run it in your favorite browser (I use Chrome).

When CKEditor loads, use the mouse to position the cursor somewhere in the middle of the text. Then press CTRL and wait 1 second. You will see that your cursor returns to the beginning of the text.

This code example uses

 editor.getSelection().createBookmarks(); 

to create a bookmark. But I also tried:

 editor.getSelection().createBookmarks(true); 

and

 editor.getSelection().createBookmarks2(); 

I also tried to just save the range using

 var ranges = editor.getSelection().getRanges(); 

and

 editor.getSelection().selectRanges(ranges); 

in the restoreCursorLocation function.

+6
source share
2 answers
  (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 ); }); }); } } }); })(); 

I checked your code, made some corrections, and the above seems to work fine. I know you said you tried, but for me createBookmarks(true) did the trick.

Explanations and notes:

  • You needed to use createBookmarks(true) , which embeds unique HTML code in HTML. Such a bookmark is not affected by the changes you make inside the DOM (there are restrictions, for example, your custom changes delete the bookmark).
  • It was wise to use getBody().getHtml() and getBody().setHTML() . If you used editor.getData() , this would eliminate the empty spaces representing the bookmarks. Please note, however, that this approach can break widgets, so contentDomInvalidated is required after these changes.
  • I also focused the editor before restoring the selection, but this is just in case, as I noticed that the editor selects a bookmark without it. If, however, for some reason you lose your choice, this will be another thing to use.

Here you have a working example: http://jsfiddle.net/j_swiderski/nwbsywnn/1/

+5
source

Check the default behavior when setting innerHtml in https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML

Deletes all children, parses the content string, and assigns the resulting nodes as children of the element

Bookmarks in CKEDITOR are hidden span elements, and setting innerHtml will remove all these elements.

In any case, the solution is very simple.

Change the storeCursorLocation function to

 var storeCursorLocation = function(editor) { bookmark = editor.getSelection().createBookmarks(true); }; 

When you pass true as parameters, it will use identifiers as a reference instead of storing DOM elements so that you can restore them after changing innerHtml.

{Edit}

Reading Solution 2 of @Reinmar says

If you can avoid uncontrolled innerHtml changes and instead add / remove / move some nodes, then just remember that you need to save these elements and this method will work just fine. You can also move bookmark items if your changes also need to change the selection.

So you will do this if you cannot replace the contents of the innerHtml element.

This solution is less efficient, but may work in some scenarios.

Change the validateText function to this.

 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); }; 

Basically, I cross the nodes of the first p in the chkeditor body and replace only the text node with a text of type that contains Lorem with a range and adds the remaining text before and after as text elements. If you replace all the text as you did, it will remove the bookmarks from the DOM, so when you try to restore them, they do not exist.

+1
source

Source: https://habr.com/ru/post/985704/


All Articles