Ivar Hill
Ivar Hill

Reputation: 21

Issues with drag and drop in CKEditor

I'm writing a small notetaking webapp which uses CKEditor as a HTML editor. I've been able to integrate it well, with one exception - drag and drop functionality. It mostly works - but there are two quirks I haven't been able to solve despite trying a whole lot of different approaches.

My requirements are simple - I drag any file into CKEditor from outside the app, upon which it should insert a simple anchor tag in the editable text with a local link to the file. Currently, I am implementing drag and drop in my own CKEditor plugin called filemanager:

CKEDITOR.plugins.add( 'filemanager', {
init: function( editor ) {

    editor.on('contentDom', function(contentDom) { 

        //Runs upon dropping a file into the editor
        contentDom.editor.document.on('drop', function(e) {

            //prevents default behavior as long as it's an actual file drag and drop (if it's dragging text etc. inside the editor, keep default behavior)
            if(e.data.$.dataTransfer.files.length)
            {
                e.data.preventDefault();
            }

            //Goes through all the dropped files and insert HTML with links
            for (var i = 0; i < e.data.$.dataTransfer.files.length; ++i) {
                CopyData(e.data.$.dataTransfer.files[i].path);
            }
        });
    });

    //Creates the link in the editor
    function CopyData(path, range)
    {
        //Link HTML
        var fileHTML;

        /* Code to parse the path into HTML to be inserted */

        editor.insertHtml(fileHTML);

    }

}

});

I have omitted most of the CopyData function as the formatting isn't relevant, but it generates a div such as this:

fileHTML = "<a href=\""+path+"\" class=\"fileLink\" target=\"_blank\">"+fileName+"</a>";


This does work perfectly fine, so the basic implementation of the feature seems to work. However, here are my two issues:

  1. Dropping a file into the editor but outside the actual text area (such as below the final line of text in the editor) does not disable default behavior. How can I run a preventDefault() for this area of the editor? Been trying events in all sort of places without results.
  2. The HTML isn't inserted at the location of the mouse cursor, even though CKEditor previews a caret there. Rather, it is inserted where the caret currently is in the document, independently of where you drop. This disrupts core functionality of the feature - is there a way to insert HTML at the mouse position? I would assume so since CKEditor actually previews a caret where the mouse is, but I looked through the documentation without results.

Does anyone have ideas of how to solve the two above issues? If it helps, I am using nw.js so the webapp will always run in Chromium. Grateful for any responses!

Upvotes: 1

Views: 3093

Answers (1)

Ivar Hill
Ivar Hill

Reputation: 21

I eventually managed to solve these issues.

To prevent default behavior outside the DOM elements, I added this code to my CKEditor plugin:

        var iframeWin = window.document.getElementsByTagName('iframe')[0].contentWindow;

        iframeWin.addEventListener("dragover",function(e){
            e = e || iframeWin.event;
            if(e.dataTransfer.files.length)
            {
                e.preventDefault();

            }
        },false);

        iframeWin.addEventListener("drop",function(e){
            e = e || iframeWin.event;
            if(e.dataTransfer.files.length)
            {
                e.preventDefault();
            }
        },false);

This correctly identified the iframe I needed to disable default behavior in. The if(e.dataTransfer.files.lenght) condition makes sure this only applies to files dragged into the app, and not dragging things within the editor.

Inserting the HTML at the mouse cursor was trickier! This cannot be credited to me - I found a function doing most of the work on the CKEditor bug tracker, and edited it to work in this scenario:

    function moveSelectionToDropPosition( editor, dropEvt )
    {
    var $evt = dropEvt.data.$,
        $range,
        range = editor.createRange();

    // Make testing possible.
    if ( dropEvt.data.testRange ) {
        dropEvt.data.testRange.select();
        return;
    }

    // Webkits.
    if ( document.caretRangeFromPoint ) {
        $range = editor.document.$.caretRangeFromPoint( $evt.clientX, $evt.clientY );
        range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset );
        range.collapse( true );
    }
    // FF.
    else if ( $evt.rangeParent ) {
        range.setStart( CKEDITOR.dom.node( $evt.rangeParent ), $evt.rangeOffset );
        range.collapse( true );
    }
    // IEs.
    else if ( document.body.createTextRange ) {
        $range = editor.document.getBody().$.createTextRange();
        $range.moveToPoint( $evt.clientX, $evt.clientY );
        var id = 'cke-temp-' + ( new Date() ).getTime();
        $range.pasteHTML( '<span id="' + id + '">\u200b</span>' );

        var span = editor.document.getById( id );
        range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START );
        span.remove();
    }

    range.select();
}

In the drop event I then simply added

moveSelectionToDropPosition(editor, e);

And it all worked as intended!

Hopefully this may help anyone else having similar issues in the future :)

Upvotes: 1

Related Questions