CKeditor html content gets empty after using jQuery-ui Sortable in your parent div

13

I want to "sortable" 2 or more div's with a CKeditor populated with html, however when doing this the html of CKeditor loses the content and the editing space gets blocked.

Follow fiddle: link

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();
    
asked by anonymous 06.11.2014 / 14:57

2 answers

5

As the Zuul response , the problem occurs when you remove the iframe from the DOM. For this reason, my suggestion is to destroy the editor before ordering (making it revert to a simple textarea ) and re-create it after it.

First, the option clone is used so that the element being dragged is not the div itself, 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",

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

As the helper is an exact copy of the element to be moved, it also has a iframe . It can be popular with a copy of the editor's own data so that it looks more like the original element. The code below is just an example, you can probably improve it more (the look looks pretty good except for some details in the drawing):

    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 change is perceived:

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

Finally, when you release, you recreate the editor:

    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 . A problem with this method is that after destroying and re-creating the editor, you lose all revision history (ie undo and redo ) as well as any "state" that the editor before the destruction. Unfortunately I have nothing to suggest in this regard ...

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

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

One way to avoid this would be to use a sortable to restrict which part (s) of handle you can drag the element - instead of the entire div . Example . Or maybe you get something by intercepting the capturing phase and preventing < in> drag if it starts when it occurs inside div (I do not know how to do this using iframe , there is no "beforeStart" method or something like that ...).

    
11.11.2014 / 09:57
12

Solution B

2014-11-11

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

So, except for the previously suggested, we have to make use of a solution that does not use iframe .

CKeditor Plugin: Div Editing Area

With the " Div Editing Area" plugin "we were able to get around the problem because it does not use a iframe in> inline , which does not allow exempting from the manipulation of iframe before and after it is attached to the DOM by the act of being dragged:

  

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

$(document).ready(function(){

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

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

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

Curiously, your problem is reported as a bug in the CKeditor Ticket # 11857 which has since been closed as " invalid "because the problem is in how iframe is managed in the DOM by browsers and not in CKEditor. The official recommendation is the form I have already suggested, but alternatively this plugin on which I have now written also serves as a solution.

Solution A

2014-11-06

It gives me idea that some change is made in the DOM during the drag of the element and the instance of CKEditor is somehow corrupted.

Dealing with the problem

This does not explain the problem you have, but it has a way of dealing with it:

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

To explain

  • Instantiate CKEditor (s) as you already did:

    CKEDITOR.replace('editor1');
    
    CKEDITOR.replace('editor2');
    
  • Global variables for storing data and 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 sortable with two methods:

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

    The method update( event, ui ) is called when the list is updated, something that happens after the drag has finished 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 the drag:

    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);
    }
    
06.11.2014 / 17:36