Ckeditor html content is empty after using jQuery-ui Sortable in your parent div

Asked

Viewed 1,285 times

13

I want to leave "sortable" 2 or more div’s with a Ckeditor populated with html, but when doing this the html of Ckeditor loses the content and the editing space is blocked.

Fiddle next: http://jsfiddle.net/B4yGJ/256/

HTML

<div id='e'>
    <div id='e1'>
        <textarea id="editor1"></textarea>
    </div>
    <div id='e2'>
        <textarea id="editor2"></textarea>
    </div>
</div>

JS, inside the ready

CKEDITOR.replace('editor1');

CKEDITOR.replace('editor2');

$("#e").sortable();

2 answers

12

Solution B

2014-11-11

Indeed, what happens is that the iframe is automatically unloaded when we are dragging it because during dragging it is detached from the DOM.

Therefore, except as previously suggested, we have to make use of a solution that does not use iframe.

Ckeditor Plugin: Div Editing Area

With the plugin "Div Editing Area" we managed to get around the problem because it does not use a iframe but editing inline, which does not make it possible to dispense with the manipulation of iframe before and after being attached to the GIFT by the act of being dragged:

This plugin uses an element <div> (instead of the element <iframe> traditional) as the editable area in themedui creator. Very similar to editing inline, benefits by allowing the content of the editor to inherit the host page styles.

$(document).ready(function(){

    $("#e").sortable();

    CKEDITOR.replace( 'editor1', {
        extraPlugins: 'divarea'
    });

    CKEDITOR.replace( 'editor2', {
        extraPlugins: 'divarea'
    });
});

Interestingly, your problem is reported as a Ckeditor bug Ticket #11857 which has since been closed as "invalid" given the problem being in the way the iframe is managed in the DOM by the navigators and not in the Ckeditor. The official recommendation is the form that had already suggested, but instead this plugin on which I have now written also serves as a solution.


Solution A

2014-11-06

It gives me the idea that some change is made in the DOM in the course of the element dragging and the instance of the Ckeditor is somehow corrupted.

Deal with the problem

This doesn’t explain the problem you have, but it presents a way to deal with it:

View example in Jsfiddle

CKEDITOR.replace('editor1');
CKEDITOR.replace('editor2');

var $textareaTemplate = $('<textarea/>');

var textareaId = '',
    CKvalue    = '';

$("#e").sortable({
    activate: function( event, ui ) {
        var $textarea = $(ui.item).find('textarea'),
            $userMsg  = $(ui.item).find('.while-drag');

        textareaId = $textarea.attr("id");

        CKvalue = CKEDITOR.instances[textareaId].getData();
        CKEDITOR.instances[textareaId].destroy();
        $textarea.remove();
        $userMsg.show();
    },
    update: function( event, ui ) {

        $(ui.item).find('.while-drag').hide();
        $(ui.item).append($textareaTemplate.attr("id",textareaId));

        CKEDITOR.replace(textareaId);
        CKEDITOR.instances[textareaId].setData(CKvalue);
    }
});

Explaining

  • Instantiate the Ckeditor(s) as you already did:

    CKEDITOR.replace('editor1');
    
    CKEDITOR.replace('editor2');
    
  • Global variables for storing data and the text area template:

    // Template da textarea e seus atributos (se aplicavel)
    var $textareaTemplate = $('<textarea/>');
    
    
    var textareaId = '', // para guardar a referencia à instancia do CKEditor
        CKvalue    = ''; // para guardar o valor que a instancia do CKEditor contém
    
  • Instantiate the sortable with two methods:

    The method activate( event, ui ) is called when dragging starts on a particular element of the milking list.

    The method update( event, ui ) is called when the list is updated, something that happens after the drag has ended and the DOM positions have been changed.

    $("#e").sortable({
      activate: function( event, ui ) { /* ao iniciar */ },
      update: function( event, ui ) { /* ao terminar */ }
    });
    
  • Save what we need when starting drag:

    activate: function( event, ui ) {
    
        var $textarea = $(ui.item).find('textarea'),     // localizar a textarea
            $userMsg  = $(ui.item).find('.while-drag');  // localizar mensagem ao utilizador
    
        // Guardar ID para identificar a instância do CKEditor
        textareaId = $textarea.attr("id"); 
    
        /* Recolher o valor atualmente presente na instância do CKEditor
         * que corresponde ao ID recolhido em cima.
         */
        CKvalue = CKEDITOR.instances[textareaId].getData();
    
        // Destruir a instancia do CKEditor neste elemento.
        CKEDITOR.instances[textareaId].destroy();
    
        // Remover do DOM a textarea
        $textarea.remove();
    
        // Apresentar mensagem ao utilizador
        $userMsg.show();
    }
    
  • Reset what was saved after dragging:

    update: function( event, ui ) {
    
        // Esconder mensagem
        $(ui.item).find('.while-drag').hide();
    
        // Colocar template da textarea com o ID recolhido
        $(ui.item).append($textareaTemplate.attr("id",textareaId));
    
        // Nova instancia do CKEditor para o ID recolhido 
        CKEDITOR.replace(textareaId);
    
        // Passar valor que o CKEditor tinha de volta para a área de edição
        CKEDITOR.instances[textareaId].setData(CKvalue);
    }
    

  • This already gives me a very good direction in solving the problem. Your example still has a bug, when I change places other times the Ckeditor ends up getting lost. I will try to investigate it here. If you have any idea what might be going on please update here!

  • Hmmm... With Firefox running on Linux I can run Ckeditor up and down multiple times without losing content... In due course I will further analyze this problem and update the answer!

  • @Joaopaulo Most likely the problem happens when you click on something inside the editor itself and drag. See at the end of my answer for an explanation and possible solution. By the way, my suggestion is quite similar to this, it just changes the way you do it. And in both cases, moving the element loses the edit history (undo and redo). Maybe more things get lost too, I don’t know... If the solution B of this answer works (i.e. without having to destroy and recreate the editor), it should be the best option.

5


In accordance with the answer from Zuul, the problem occurs when removing the iframe of the GIFT. For this reason, my suggestion is to destroy the editor before ordering (making it a simple textarea) and recreate it after the same.

Firstly, the option is used clone so that the element being dragged is not itself div, but a clone of it (this prevents the element from being removed from the DOM when starting the sorting, but only hidden):

$("#e").sortable({
    helper:"clone",

Thus, the helper it will still look like the editor being moved - only empty. The original element will still be in the DOM, intact but invisible.

Like the helper is an exact copy of the element to be moved, it also has a iframe. One can popular this iframe with a copy of the editor’s own data, so it looks more like the original element. The code below is just an example, it is probably possible to improve more (the appearance is very similar, except for some details in fomatação):

    start:function(event, ui) {
        // Encontra o id do textarea original (melhor usar classes...)
        while ( event.originalEvent )
            event = event.originalEvent;
        var id = $(event.target).closest("#e1, #e2").find("textarea")[0].id;

        // Acha os dados do editor e os copia pro helper
        var copia = CKEDITOR.instances[id].getData();
        ui.helper.find("iframe").contents().find("body").html(copia);

Then the editor is destroyed at the beginning of the sorting process. As it is hidden, no visual changes are perceived:

        CKEDITOR.instances[id].destroy(false);
    },

Finally, when releasing, the editor is recreated:

    stop:function(event) {
        while ( event.originalEvent )
            event = event.originalEvent;
        var id = $(event.target).closest("#e1, #e2").find("textarea")[0].id;

        CKEDITOR.replace(id);
    }
});

Example in jsFiddle. One problem with this method is that after destroying and recreating the editor all revision history is lost (i.e. undo and redo), as well as any "state" that the editor kept before the destruction. Unfortunately I have nothing to suggest in this sense...

Another small inconvenience is the helper, that ideally should be an identical copy of the content being moved. I don’t think it’s simple to do this with the editor you use iframe (iframes are a bit "boring" to work with due to security issues), but it may be possible to improve the cloning method.

Warning: It should be noted that using the sortable this way is subject to a UX problem: if you click on the blue/red area and drag, everything ok, but if you click for example on one of the buttons of the editor and accidentally move the mouse, it starts an ordering (which can be kind of annoying for the user).

One way to avoid this would be to use a handle to restrict in which part(s) of the div can drag the element - instead of the div entire. Example. Or maybe you get something by intercepting the click on capture phase and preventing the drag start when it occurs within the iframe (I don’t know how to do this using the sortable, there is no "beforeStart" method or anything like that...).

  • It looks great in Chrome. But in IE it doesn’t find toElement/querySelector. The error is as follows: Unable to get Property 'querySelector' of Undefined or null Reference. With this in IE(10/9), so q try to drag the Ckeditor some.This toElement can be replaced by something else not to come null/Undefined?

  • Example: http://jsfiddle.net/B4yGJ/262/

  • 1

    @Joaopaulo I initially tried to do without using jQuery, but it can be replaced x.querySelector(y) for $(x).find(y)[0]. As to the toElement, I’ll investigate, I thought to simply use target but strangely didn’t work (the funny thing is that if you access the global variable event the target is right, but in the parameter event jQuery uses in callback the target is different...). When I have an answer I update.

  • @Joaopaulo I updated the answer to use jQuery all the time (instead of querySelector) and consulted the original event (a MouseEvent) instead of the jQuery event - to ensure that the target was even the element clicked (and not the entire area). It is clear to fix the bug when dragging the contents of the iframe. I have no way to test in IE(10/9), but the last example worked on IE11, FF, Chrome, Safari and Opera.

  • I’ll test today and give feedback!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.