I am trying to create simple WYSIWYG editor and I have faced with following problem.
When user copies or drag text inside of contenteditable
div.
According to information from the Google I have added following code for copy/paste:
var $mainInput = $('#main-input');
$mainInput.on('paste', function(e) {
e.preventDefault();
var text = e.originalEvent.clipboardData.getData("text/plain");
lastCaretIndex.insertNode(document.createTextNode(text));
});
And it works like a charm:
But with drag&drop it doesn't work as expected. I found similar posts on Stackoverflow:
1) This one uses jQuery UI which is irrelevant for my case
2) Event described here doesn't provide ability to customize draggable text, only target
And some others pieces, so I got following code:
$mainInput.on('dragend', function(e) {
window.getSelection().getRangeAt(0).insertNode(document.createTextNode(strip(t)));
});
And the result:
Also, I tried to do this according to documentation, but this fully removes old element content. Code:
$mainInput.bind('drop', function (e) {
event.target.textContent = e.originalEvent.dataTransfer.getData("text/plain");
event.preventDefault();
});
Example:
Can somebody please help me, or give some advice how this could be handled.
Current state is available in this fiddle
================= PS =============== This is solved. Version which works for most browsers - fiddle
When the text is dropped, the span
at the drop point is split in two and a new span
is inserted in between. I haven't seen an option of the drag-and-drop to prevent that behavior. One way to avoid it is to replace the default transfer mechanism.
In the drop
event handler, you can move the text manually if you find that it will end up in a different span
. The destination range can be found with createCollapsedRangeFromPoint
, a function provided by Tim Down. The range can then be compared with the original selection range.
The following code can be tested in this jsfiddle. It works in Chrome and Firefox but not in IE11. The default drag-and-drop behavior is used when the customized one is not supported.
var $mainInput = $('#main-input');
var draggedText;
var dragStartContainer;
var dragEndContainer;
var dragStartOffset;
var dragEndOffset;
$mainInput.on('dragstart', function(e) {
var dragRange = getSelectionRange();
dragStartContainer = dragRange.startContainer;
dragEndContainer = dragRange.endContainer;
dragStartOffset = dragRange.startOffset;
dragEndOffset = dragRange.endOffset;
draggedText = e.originalEvent.dataTransfer.getData("text");
});
$mainInput.on('drop', function(e) {
// Get caret at mouse cursor position
var range = createCollapsedRangeFromPoint(e.clientX, e.clientY);
var container = range.startContainer;
// If text is moved to another container, do it manually
if (container && (container !== dragStartContainer || container !== dragEndContainer)) {
e.preventDefault();
// Insert dragged text at caret position
var startOffset = range.startOffset;
var str = container.textContent;
var strPrepend = str.substr(0, range.startOffset);
var strAppend = str.substr(range.startOffset);
container.textContent = strPrepend + draggedText + strAppend;
// Delete text at original position
var spaceCount = getSpaceCountAtStart(container);
setSelectionRange(dragStartContainer, dragEndContainer, dragStartOffset, dragEndOffset);
document.execCommand("delete", false);
var spaceCountCorrection = getSpaceCountAtStart(container) - spaceCount;
// Select text at new position
var startRemoved = (dragEndContainer === container ? dragEndOffset - 1 : 0);
var startOffset = Math.max(0, startOffset + spaceCountCorrection - startRemoved);
var endOffset = startOffset + draggedText.length;
setSelectionRange(container, container, startOffset, endOffset);
}
});
// Get the number of spaces at start of container
// After moving text, that number may change
// and screw up range position calculations
function getSpaceCountAtStart(container) {
var innerHTML = container.parentNode.innerHTML;
var count = 0;
for (var i = 0; i < innerHTML.length; i++) {
if (innerHTML[i] === ' ') {
count += 1;
} else {
break;
}
}
return count;
}
// Function provided by Tim Down at https://stackoverflow.com/a/28275304/1009922
function createCollapsedRangeFromPoint(x, y) {
var doc = document;
var position, range = null;
if (typeof doc.caretPositionFromPoint != "undefined") {
position = doc.caretPositionFromPoint(x, y);
range = doc.createRange();
range.setStart(position.offsetNode, position.offset);
range.collapse(true);
} else if (typeof doc.caretRangeFromPoint != "undefined") {
range = doc.caretRangeFromPoint(x, y);
} else if (typeof doc.body.createTextRange != "undefined") {
range = doc.body.createTextRange();
range.moveToPoint(x, y);
}
return range;
}
function getSelectionRange() {
var sel;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
return sel.getRangeAt(0);
}
} else if (document.selection) {
return document.createRange();
}
return null;
}
function setSelectionRange(startContainer, endContainer, startOffset, endOffset) {
var sel = window.getSelection();
sel.removeAllRanges();
var range = document.createRange();
range.setStart(startContainer, startOffset);
range.setEnd(endContainer, endOffset);
sel.addRange(range);
}