Search code examples
javascriptjqueryckeditorcursor-position

Add an HTML element only once in CKEditor


In my ckeditor instance, I want to let my users insert a horizontal rule but only once. If there is an existing line, it should be replaced by the new one (same as the Read-More functionality on Wordpress). I can get it to work if I want only the last HR tag to remain, but what if a user wants to insert his HR tag higher up in the document than an existing one?

I used a variable (say, savedContent) to store the default content (without any HR) on instantiation. Following the steps in Set cursor to specific position in CKEditor , my idea was to use the savedContent variable to revert the CKEditor contents to the default HR-less content before (using the ranges or bookmarks) to insert a new HR at the cursor position.

This is the code:

CKEDITOR.on('instanceReady', function() {
    $(".cke_button__horizontalrule").attr("title","End Preview here");
    var savedContent = CKEDITOR.instances['pro_story'].getData();

    $(".cke_button__horizontalrule").on("click",function(){
        var ranges = editor.getSelection().getRanges();
        CKEDITOR.instances['pro_story'].setData(savedContent);
        editor.getSelection().selectRanges( ranges );
        editor.insertHtml("<hr />");
    });
});

Now the problem with this code is that, for some reason, it gives me the following error: The given range isn't in document.

What do I do?


Solution

  • Get an array of existing <hr /> tags before inserting a new one. Then, remove all these tags after inserting the new one.

    let hrArray;
    
    CKEDITOR.instances['pro_story'].on('beforeCommandExec', function(evt) {
        if (evt.data.name == 'horizontalrule') {
            hrArray = evt.editor.document.getElementsByTag('hr').toArray();
        }
    });
    
    CKEDITOR.instances['pro_story'].on('afterCommandExec', function(evt) {
        if (evt.data.name == 'horizontalrule') {
            hrArray.forEach(el => el.remove());
        }
    });