10

I am trying to create a few draggable elements (tokens) that can be dragged around inside a contenteditable div. It seems that everything is working except that... after I drag one element and drop it, I cannot drag it again. It seems that I cannot bind to it's dragstart event again.

Any idea why this happens and how can i fix it?

Here's a link to my fiddle: http://jsfiddle.net/gXScu/1/

HTML:

<div id="editor" contenteditable="true">
    Testime siinkohal seda, et kuidas<br />
    on võimalik asja testida.
    <span class="draggable" draggable="true" contenteditable="false">Token</span>
</div>
<span class="draggable" draggable="true" contenteditable="false">Token 2</span>

Javascript (jQuery)

var bindDraggables = function() {
    console.log('binding draggables', $('.draggable').length);
    $('.draggable').off('dragstart').on('dragstart', function(e) {
        if (!e.target.id)
            e.target.id = (new Date()).getTime();
        e.originalEvent.dataTransfer.setData('text/html', e.target.outerHTML);
        console.log('started dragging');
        $(e.target).addClass('dragged');
    }).on('click', function() {
        console.log('there was a click');
    });
}

$('#editor').on('dragover', function (e) {
    e.preventDefault();
    return false;
});

$('#editor').on('drop', function(e) {
    e.preventDefault();
    var e = e.originalEvent;
    var content = e.dataTransfer.getData('text/html');
    var range = null;
  if (document.caretRangeFromPoint) { // Chrome
    range = document.caretRangeFromPoint(e.clientX, e.clientY);
  }
  else if (e.rangeParent) { // Firefox
    range = document.createRange();
        range.setStart(e.rangeParent, e.rangeOffset);
  }
    console.log('range', range)
  var sel = window.getSelection();
  sel.removeAllRanges(); sel.addRange(range);

  $('#editor').get(0).focus(); // essential
  document.execCommand('insertHTML',false, content);
    //$('#editor').append(content);
  sel.removeAllRanges();
    bindDraggables();
    console.log($('[dragged="dragged"]').length);
    $('.dragged').remove();
});

bindDraggables();

CSS:

#editor {
    border: 2px solid red;
    padding: 5px;
}
.draggable {
    display: inline-block;
    padding: 3px;
    background: yellow;
    cursor: move !important;
}
ragulka
  • 4,312
  • 7
  • 48
  • 73
  • It works perfectly fine on Firefox, therefore most likely it is a Chrome's bug. And it occurs only if you use `insertHTML` command. When I replaced it with `$('#editor').append(content)` everything works fine... – Reinmar Jun 03 '13 at 17:53

2 Answers2

9

I also tried a few hacks but none of them seems to work. I was inspecting HTML and I found that in your example the newly inserted content don't have any contenteditable attribute assigned and when I assigned to it manually then it started working fine.

Here is my code http://jsfiddle.net/gXScu/8/

I just added this line

$('.draggable').attr("contenteditable", false); in bindDraggables method.

I agree with Reinmar explanation over contenteditable.

Imran Latif
  • 985
  • 8
  • 21
  • 1
    @Reinmar: :-). So it seems that if we do 'insertHTML' via 'document.execCommand' then Chrome is removing 'contenteditable' attribute silently :-). – Imran Latif Jun 03 '13 at 19:55
  • Both Firefox and Chrome might give inconsistent results with using 'document.execCommand('insertHTML',false, content);'. To fix that, change it to 'pasteHtmlAtCaret(content)'. Function can be found here: http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div/6691294#6691294 – Sergey Tsibel Mar 08 '15 at 10:58
2

Apparently Chrome has a bug (not a surprise - contenteditable is the most buggy feature in all browsers). Your code works perfectly fine on Firefox, but for some reason element inserted into editable with insertHTML command cannot be dragged any more.

I tried few hacks, but only one worked - inserting other element and then replacing it.

So instead:

document.execCommand('insertHTML', false, content);

Use something like this:

var spanId = 'temp-' + (new Date()).getTime();

// Insert span with zero-width space (so it isn't visible and it isn't empty).
document.execCommand('insertHTML', false, '<span id="' + spanId + '">\u200b</span>');
// Replace that span with our dragged content.
$('#' + spanId).replaceWith(content);

You can try this here: http://jsfiddle.net/gXScu/5/. I also corrected wrong order of this:

$('.dragged').remove();
bindDraggables();
Reinmar
  • 21,729
  • 4
  • 67
  • 78
  • 1
    This is a perfectly valid answer and a good explanation, thanks! I have chosen Imran's answer though because it is a bit more straightforward and doesn't require creating an extra element. – ragulka Jun 03 '13 at 19:28
  • The only thing that would be missing imho is the caret indicator to show where the item is going to be placed after mouseup. Is that somehow possible? – Stefan Falk Mar 13 '16 at 15:19