I have a div that is contenteditable but I don't want to allow users to paste or drop formatted content into the div as this will - very likely - break the layout of the page.
The question how to handle HTML content that is pasted into such a div has already been raised several times and I was able to solve this problem as described here:
Javascript trick for 'paste as plain text` in execCommand
In one of the comments of the above question the point was raised that - in order to avoid HTML formatted content in the div at all - one needs to handle dropped content as well.
I need to support FF, Chrome, Edge and IE11. For all but IE11 I was able to implement a custom drop handler:
$("div[contenteditable=true]").on('drop', function(e) {
e.preventDefault();
var text = e.originalEvent.dataTransfer.types.indexOf && e.originalEvent.dataTransfer.types.indexOf('text/plain') >= 0 ? e.originalEvent.dataTransfer.getData('text/plain') : e.originalEvent.dataTransfer.getData('Text');
var range;
if (!document.caretRangeFromPoint) { // IE11 doesn't support caretRangeFromPoint
range = document.createRange();
range.setStart(e.currentTarget, 0);
range.setEnd(e.currentTarget, 0);
} else {
range = document.caretRangeFromPoint(e.originalEvent.clientX, e.originalEvent.clientY);
}
_insertText(text, range);
});
function _insertText(text, range) {
range.deleteContents();
var textNode = document.createTextNode(text);
range.insertNode(textNode);
range.selectNodeContents(textNode);
range.collapse(false);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<h4 style="color:red">Drag Me</h4>
<div style="border: 1px solid;" contenteditable="true">Drop here</div>
</body>
https://jsfiddle.net/m25z6ch6/
As IE11 doesn't support document.caretRangeFromPoint I already tried to manually create a range as can be seen in the above snippet like this:
range = document.createRange();
range.setStart(e.currentTarget, 0);
range.setEnd(e.currentTarget, 0);
But This only works for dropping the content at the start of the already existing text. If I want to drop content into already existing text (e.g. between "Drop" and "here" in the above example), I somehow need to compute the offset to use. But I can't figure out how to do this for IE.
Is there a way to create a range object with correct offset given the information in a drop event (e.g. x,y coordinates and/or drop target element)?
Or is there any other way to customize the dropped content in order to allow plain text only for IE11?
Related
I'm using Froala 2 and the documentation doesn't seem to have anything that implies a simple way to set the location of the caret, let alone at the beginning or end. I'm trying to seed the editor instance with a little content in certain cases and when I do using html.set, the caret just stays where it is at the beginning and I want to move it to the end. The internet doesn't seem to have anything helpful around this for v2.
Froala support provided an answer for me that works:
var editor = $('#edit').data('froala.editor');
editor.selection.setAtEnd(editor.$el.get(0));
editor.selection.restore();
As far as I know, Froala 2 doesn't provide any API to do this, but you can use native JavaScript Selection API.
This code should do the job:
// Selects the contenteditable element. You may have to change the selector.
var element = document.querySelector("#froala-editor .fr-element");
// Selects the last and the deepest child of the element.
while (element.lastChild) {
element = element.lastChild;
}
// Gets length of the element's content.
var textLength = element.textContent.length;
var range = document.createRange();
var selection = window.getSelection();
// Sets selection position to the end of the element.
range.setStart(element, textLength);
range.setEnd(element, textLength);
// Removes other selection ranges.
selection.removeAllRanges();
// Adds the range to the selection.
selection.addRange(range);
See also:
How to set caret(cursor) position in contenteditable element (div)?
Set caret position at a specific position in contenteditable div
i'm trying to copy a text to the clipboard. But've already shown the text as selected in the modal window where it appears after an ajax call.The code is the following:
jQuery.fn.selectText = function(){
var doc = document
, element = this[0]
, range, selection
;
if (doc.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(element);
range.select();
} else if (window.getSelection) {
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
}
so after range = document.createRange(); i tryied inserting range.execCommand('copy'); cause i've read this tutorial about it but it doesn't mention any problem with this command. The error i'm getting is the following:
TypeError: range.execCommand is not a function
This is a mozilla tutorial about execCommand.
A range doesn't have an execCommand function, the execCommand function belongs to the document object.
Taken from the same tutorial:
When an HTML document has been switched to designMode, the document
object exposes the execCommand method which allows one to run commands
to manipulate the contents of the editable region. Most commands
affect the document's selection (bold, italics, etc), while others
insert new elements (adding a link) or affect an entire line
(indenting). When using contentEditable, calling execCommand will
affect the currently active editable element.
I'm currently building a Markdown editor for the web. Markdown tags are previewed in realtime by appending their HTML equivalents via the Range interface. Following code is used, which should be working according to MDN:
var range = document.createRange()
var selection = window.getSelection()
range.setStart(textNode, start)
range.setEnd(textNode, end + 2)
surroundingElement = document.createElement('strong')
range.surroundContents(surroundingElement)
var cursorRange = document.createRange()
cursorRange.setStartAfter(surroundingElement)
selection.removeAllRanges()
selection.addRange(cursorRange)
Firefox works: Some bold text
Chrome not: Some bold text
Any suggestions what could be wrong? Information on this subject are rare.
Answer
Thanks to #Tim Down, I fixed it using the invisible character workaround he describes in one of the links mentioned in his answer. This is the code I'm using now:
var range = document.createRange()
range.setStart(textNode, start)
range.setEnd(textNode, end + 2)
surroundingElement = document.createElement('strong')
range.surroundContents(surroundingElement)
var selection = window.getSelection()
var cursorRange = document.createRange()
var emptyElement = document.createTextNode('\u200B')
element[0].appendChild(emptyElement)
cursorRange.setStartAfter(emptyElement)
selection.removeAllRanges()
selection.addRange(cursorRange)
The problem is that WebKit has fixed ideas about where the caret (or a selection boundary) can go and is selecting an amended version of your range when you call the selection's addRange() method. I've written about this a few times on Stack Overflow; here are a couple of examples:
Set cursor after span element inside contenteditable div
How to set caret/cursor position in a contenteditable div between two divs.
Assume the following DOM tree:
<div id="edit" contenteditable="true">
this content <a id="link" href="http://www.google.com/">contains</a> a link
</div>
Then create a range right after the anchor:
var r = document.createRange();
var link = document.getElementById('link');
r.setStartAfter(link);
r.setEndAfter(link);
As expected, its commonAncestorContainer is the element with id edit:
console.log(r.commonAncestorContainer); /* => <div id="edit" contenteditable="true">…</div> */
Set the selection to this range:
var s = window.getSelection();
s.removeAllRanges();
s.addRange(r);
Now query the window for the current selection range and check its commonAncestorContainer:
var r2 = s.getRangeAt(0);
console.log(r2.commonAncestorContainer);
You will find that in Firefox the result is as expected; the same element with id edit.
In WebKit browsers though, the selection range ancestor container suddenly is the text node inside the anchor; "contains", yet when you start typing you will find that you really are not inside the anchor. WTF!?
Click here for a live demo.
Is there any potential rationale behind this behavior? Any reason to assume that it is not a WebKit bug??
Thanks for your $.02.
WebKit only allows certain positions within the DOM to be used as selection boundaries or caret positions. It therefore modifies a range that is selected using the selection's addRange() method to conform to this. See also https://stackoverflow.com/a/14104166/96100.
There is another issue at play, which is that WebKit has a special case for a caret position at the end of a link which places text typed at that position after the link rather than inside it. This is undeniably a bit of a nasty hack, given that the browser reports the selection as being inside the link. However, this does not happen for other inline elements, as you can see this in this modified version of your demo:
http://jsbin.com/EYoWuWe/7/edit
I have a div tag that is contenteditable so that users can type in the div. There is a function that adds a link into the div when the user presses a button. I would like the caret to be positioned after the link so that users can continue to type. The link can be inserted many times.
Example Code:
<div id="mydiv" contenteditable="true" tabindex="-1">before link after</div>
I require this code to work in: IE, Firefox, Opera, Safari and Chrome.
Can anyone offer any help?
Assuming you have a reference to the <a> element you've inserted in a variable called link:
function setCaretAfter(el) {
var sel, range;
if (window.getSelection && document.createRange) {
range = document.createRange();
range.setStartAfter(el);
range.collapse(true);
sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(el);
range.collapse(false);
range.select();
}
}
setCaretAfter(link);
I looked inside the CKEditor (http://ckeditor.com/) code and found that it has an appendBogus() function as well as inserts an extra <br><span> </span></br> that is then deleted.
The problem of course is that Gecko/IE browsers also have nuances about how <br> tags work too (i.e. whether to use \r or \n to insert into the range vs. adding an <br> element)
You can read through the _source/plugins/enterkey/plugin.js to see the different nuances with handling IE & Gecko. It seems like one big hack...