Contenteditable: move cursor outside inserted html node (webkit / ie) - javascript

When I'm inserting an html tag inside a contenteditable div I need the cursor to move outside (to the right) the new inserted element, so if I continue to type, the new text will be unformatted.
With Firefox I've found this solution is working just fine:
node = document.createElement("strong");
node.innerHTML = "test";
range.deleteContents();
range.insertNode(node);
range.collapse(false);
The variable range is set this way:
if (window.getSelection) {
var sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
}
}
Using the above code in webkit browsers (Chrome / Safari) put the cursor outside the new tag, but to the left.
Is there a solution for this (Chrome / Safari) and for IE support (mainly 9, optionally 8)?
Thanks
=============================================
Thanks to Tim for his advices, here's the working code:
var node = document.createElement("strong");
node.innerHTML = "test";
var space = document.createElement("span");
space.innerHTML = "\u200B";
range.insertNode(space);
range.insertNode(node);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);

You need to reselect the range in non-Mozilla browsers. This will work in all major browsers except IE <= 8:
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
For IE <= 8, you can use a different approach. Here's another answer of mine with a complete example:
https://stackoverflow.com/a/4836809/96100

Related

How to select and copy to clipboard text automatically with Javascript from a div? [duplicate]

In JavaScript, it's possible to programmatically select text in an input or textarea element. You can focus an input with ipt.focus(), and then select its contents with ipt.select(). You can even select a specific range with ipt.setSelectionRange(from,to).
My question is: is there any way to do this in a contenteditable element too?
I found that I can do elem.focus(), to put the caret in a contenteditable element, but subsequently running elem.select() doesn't work (and nor does setSelectionRange). I can't find anything on the web about it, but maybe I'm searching for the wrong thing...
By the way, if it makes any difference, I only need it to work in Google Chrome, as this is for a Chrome extension.
If you want to select all the content of an element (contenteditable or not) in Chrome, here's how. This will also work in Firefox, Safari 3+, Opera 9+ (possibly earlier versions too) and IE 9. You can also create selections down to the character level. The APIs you need are DOM Range (current spec is DOM Level 2, see also MDN) and Selection, which is being specified as part of a new Range spec (MDN docs).
function selectElementContents(el) {
var range = document.createRange();
range.selectNodeContents(el);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
var el = document.getElementById("foo");
selectElementContents(el);
In addition to Tim Downs answer, i made a solution that work even in oldIE:
var selectText = function() {
var range, selection;
if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(this);
range.select();
} else if (window.getSelection) {
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(this);
selection.removeAllRanges();
selection.addRange(range);
}
};
document.getElementById('foo').ondblclick = selectText;​
Tested in IE 8+, Firefox 3+, Opera 9+, & Chrome 2+. Even I've set it up into a jQuery plugin:
jQuery.fn.selectText = function() {
var range, selection;
return this.each(function() {
if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(this);
range.select();
} else if (window.getSelection) {
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(this);
selection.removeAllRanges();
selection.addRange(range);
}
});
};
$('#foo').on('dblclick', function() {
$(this).selectText();
});
...and who's intereseted in, here's the same for all coffee-junkies:
jQuery.fn.selectText = ->
#each ->
if document.body.createTextRange
range = document.body.createTextRange()
range.moveToElementText #
range.select()
else if window.getSelection
selection = window.getSelection()
range = document.createRange()
range.selectNodeContents #
selection.removeAllRanges()
selection.addRange range
return
Update:
If you want to select the entire page or contents of an editable region (flagged with contentEditable), you can do it much simpler by switching to designMode and using document.execCommand:
There's a good starting point at MDN and a littledocumentation.
var selectText = function () {
document.execCommand('selectAll', false, null);
};
(works well in IE6+, Opera 9+, Firefoy 3+, Chrome 2+) http://caniuse.com/#search=execCommand
The modern way of doing things is like this. More details on MDN
document.addEventListener('dblclick', (event) => {
window.getSelection().selectAllChildren(event.target)
})
<div contenteditable="true">Some text</div>
Since all of the existing answers deal with div elements, I'll explain how to do it with spans.
There is a subtle difference when selecting a text range in a span. In order to be able to pass the text start and end index, you have to use a Text node, as described here:
If the startNode is a Node of type Text, Comment, or CDATASection,
then startOffset is the number of characters from the start of
startNode. For other Node types, startOffset is the number of child
nodes between the start of the startNode.
var e = document.getElementById("id of the span element you want to select text in");
var textNode = e.childNodes[0]; //text node is the first child node of a span
var r = document.createRange();
var startIndex = 0;
var endIndex = textNode.textContent.length;
r.setStart(textNode, startIndex);
r.setEnd(textNode, endIndex);
var s = window.getSelection();
s.removeAllRanges();
s.addRange(r);
Rangy allows you to do this cross-browser with the same code. Rangy is a cross-browser implementation of the DOM methods for selections. It is well tested and makes this a lot less painful. I refuse to touch contenteditable without it.
You can find rangy here:
http://code.google.com/p/rangy/
With rangy in your project, you can always write this, even if the browser is IE 8 or earlier and has a completely different native API for selections:
var range = rangy.createRange();
range.selectNodeContents(contentEditableNode);
var sel = rangy.getSelection();
sel.removeAllRanges();
sel.addRange(range);
Where "contentEditableNode" is the DOM node that has the contenteditable attribute. You might fetch it like this:
var contentEditable = document.getElementById('my-editable-thing');
Or if jQuery is part of your project already and you find it convenient:
var contentEditable = $('.some-selector')[0];
[Updated to fix mistake]
Here is an example that is adapted from this answer that appears to work well in Chrome - Select range in contenteditable div
var elm = document.getElementById("myText"),
fc = elm.firstChild,
ec = elm.lastChild,
range = document.createRange(),
sel;
elm.focus();
range.setStart(fc,1);
range.setEnd(ec,3);
sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
HTML is:
<div id="myText" contenteditable>test</div>

How to set the caret position in a contenteditable div in Firefox and old IE versions

I am trying to set the caret position in a contenteditable div. Here is code that works in modern IE, Chrome and Safari, but does not work in Firefox 36.0.4 or old IE versions (9 or less). For this answer it is safe to assume that the contenteditable div contains only text (i.e no child elements other than a single text node).
Please tell me how to fix this, especially for Firefox.
Thank you.
HTML:
<div contenteditable id="d">12345</div>
<div contenteditable id="log"></div>
JavaScript:
function moveCaret(d, position) {
d.focus(); // This is included because in my application the div is already focused by the time the caret must be moved.
if (window.getSelection && document.createRange) {
var sel = window.getSelection();
sel.removeAllRanges();
var range = document.createRange();
range.setStart(d.childNodes[0], position);
range.setEnd(d.childNodes[0], position);
sel.addRange(range);
d.focus();
} else if (document.selection && document.body.createTextRange) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(d);
textRange.select();
d.focus();
}
}
try {
var position = 0;
var d = document.getElementById("d");
moveCaret(d, 3);
}catch(e) {
document.getElementById("log").innerHTML += e.toString();
}
http://jsfiddle.net/bk7ade2r/
The issue seems to be that firefox can not focus() on a contenteditable div.

Set caret position at a specific position in contenteditable div

SEE BEFORE MARKING DUPLICATE/DOWNVOTING
The contenteditable div will not have child elements
I do not want to set the position at the end of the div
I do not want a cross-browser solution, only Chrome support required
Only vanilla JS, no libraries.
I have seen many many solutions. Many by Tim Down, and others. But none does work. I have seen window.getSelection, .addRange etc. but don't see how they apply here.
Here's a jsfiddle.
(Tried) Code:
var node = document.querySelector("div");
node.focus();
var caret = 10; // insert caret after the 10th character say
var range = document.createRange();
range.setStart(node, caret);
range.setEnd(node, caret);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
You need to position the caret within the text node inside your element, not the element itself. Assuming your HTML looks something like <div contenteditable="true">Some text</div>, using the firstChild property of the element will get the text node.
Updated jsFiddle:
http://jsfiddle.net/xgz6L/8/
Code:
var node = document.querySelector("div");
node.focus();
var textNode = node.firstChild;
var caret = 10; // insert caret after the 10th character say
var range = document.createRange();
range.setStart(textNode, caret);
range.setEnd(textNode, caret);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);

Javascript: Pasting text at caret in WYSIWYG editor

I'm still learning the ropes when it comes to JavaScript and one of the issues which is causing me the most problems is understanding the caret position. Currently I'm writing a reference form for a wiki (http://t3chbox.wikia.com/wiki/MediaWiki:Wikia.js/referenceForm.js) and have it working for the source code editor for the wiki, but not the WYSIWYG editor (Visual Editor). I was wondering if anyone here knew how to get the caret position and then paste text for the iframe in which the edit content sits?
The WYSIWYG editor can be seen here: http://t3chbox.wikia.com/wiki/Test?action=edit (edit whilst not logged in for those with Wikia accounts and have Visual set to off). The method I've been using the get the content and attempt to paste at the caret is this:
$(document.getElementById('cke_contents_wpTextbox1').getElementsByTagName('iframe')[0].contentDocument.body).insertAtCaret('hello');
Thanks :)
The jquery insertAtCaret function that you are using only works with textareas and input fields. It does not work with contentEditable elements. You can take a look at this jsfiddle for inserting at caret for contentEditable elements.
function pasteHtmlAtCaret(html,windo) {
windo = windo || window;
var sel, range;
if (windo.getSelection) {
// IE9 and non-IE
sel = windo.getSelection();
console.log(sel);
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
console.log(range);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// non-standard and not supported in all browsers (IE9, for one)
var el = windo.document.createElement("div");
el.innerHTML = html;
var frag = windo.document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (windo.document.selection && windo.document.selection.type != "Control") {
// IE < 9
windo.document.selection.createRange().pasteHTML(html);
}
}
//usage
var iframeWindow = document.getElementById('cke_contents_wpTextbox1').getElementsByTagName('iframe')[0].contentWindow;
pasteHtmlAtCaret("Hello World!",iframeWindow);
http://jsfiddle.net/jwvha/534/

Programmatically select text in a contenteditable HTML element?

In JavaScript, it's possible to programmatically select text in an input or textarea element. You can focus an input with ipt.focus(), and then select its contents with ipt.select(). You can even select a specific range with ipt.setSelectionRange(from,to).
My question is: is there any way to do this in a contenteditable element too?
I found that I can do elem.focus(), to put the caret in a contenteditable element, but subsequently running elem.select() doesn't work (and nor does setSelectionRange). I can't find anything on the web about it, but maybe I'm searching for the wrong thing...
By the way, if it makes any difference, I only need it to work in Google Chrome, as this is for a Chrome extension.
If you want to select all the content of an element (contenteditable or not) in Chrome, here's how. This will also work in Firefox, Safari 3+, Opera 9+ (possibly earlier versions too) and IE 9. You can also create selections down to the character level. The APIs you need are DOM Range (current spec is DOM Level 2, see also MDN) and Selection, which is being specified as part of a new Range spec (MDN docs).
function selectElementContents(el) {
var range = document.createRange();
range.selectNodeContents(el);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
var el = document.getElementById("foo");
selectElementContents(el);
In addition to Tim Downs answer, i made a solution that work even in oldIE:
var selectText = function() {
var range, selection;
if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(this);
range.select();
} else if (window.getSelection) {
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(this);
selection.removeAllRanges();
selection.addRange(range);
}
};
document.getElementById('foo').ondblclick = selectText;​
Tested in IE 8+, Firefox 3+, Opera 9+, & Chrome 2+. Even I've set it up into a jQuery plugin:
jQuery.fn.selectText = function() {
var range, selection;
return this.each(function() {
if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(this);
range.select();
} else if (window.getSelection) {
selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(this);
selection.removeAllRanges();
selection.addRange(range);
}
});
};
$('#foo').on('dblclick', function() {
$(this).selectText();
});
...and who's intereseted in, here's the same for all coffee-junkies:
jQuery.fn.selectText = ->
#each ->
if document.body.createTextRange
range = document.body.createTextRange()
range.moveToElementText #
range.select()
else if window.getSelection
selection = window.getSelection()
range = document.createRange()
range.selectNodeContents #
selection.removeAllRanges()
selection.addRange range
return
Update:
If you want to select the entire page or contents of an editable region (flagged with contentEditable), you can do it much simpler by switching to designMode and using document.execCommand:
There's a good starting point at MDN and a littledocumentation.
var selectText = function () {
document.execCommand('selectAll', false, null);
};
(works well in IE6+, Opera 9+, Firefoy 3+, Chrome 2+) http://caniuse.com/#search=execCommand
The modern way of doing things is like this. More details on MDN
document.addEventListener('dblclick', (event) => {
window.getSelection().selectAllChildren(event.target)
})
<div contenteditable="true">Some text</div>
Since all of the existing answers deal with div elements, I'll explain how to do it with spans.
There is a subtle difference when selecting a text range in a span. In order to be able to pass the text start and end index, you have to use a Text node, as described here:
If the startNode is a Node of type Text, Comment, or CDATASection,
then startOffset is the number of characters from the start of
startNode. For other Node types, startOffset is the number of child
nodes between the start of the startNode.
var e = document.getElementById("id of the span element you want to select text in");
var textNode = e.childNodes[0]; //text node is the first child node of a span
var r = document.createRange();
var startIndex = 0;
var endIndex = textNode.textContent.length;
r.setStart(textNode, startIndex);
r.setEnd(textNode, endIndex);
var s = window.getSelection();
s.removeAllRanges();
s.addRange(r);
Rangy allows you to do this cross-browser with the same code. Rangy is a cross-browser implementation of the DOM methods for selections. It is well tested and makes this a lot less painful. I refuse to touch contenteditable without it.
You can find rangy here:
http://code.google.com/p/rangy/
With rangy in your project, you can always write this, even if the browser is IE 8 or earlier and has a completely different native API for selections:
var range = rangy.createRange();
range.selectNodeContents(contentEditableNode);
var sel = rangy.getSelection();
sel.removeAllRanges();
sel.addRange(range);
Where "contentEditableNode" is the DOM node that has the contenteditable attribute. You might fetch it like this:
var contentEditable = document.getElementById('my-editable-thing');
Or if jQuery is part of your project already and you find it convenient:
var contentEditable = $('.some-selector')[0];
[Updated to fix mistake]
Here is an example that is adapted from this answer that appears to work well in Chrome - Select range in contenteditable div
var elm = document.getElementById("myText"),
fc = elm.firstChild,
ec = elm.lastChild,
range = document.createRange(),
sel;
elm.focus();
range.setStart(fc,1);
range.setEnd(ec,3);
sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
HTML is:
<div id="myText" contenteditable>test</div>

Categories

Resources