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);
Related
I'm developing a basic html editor and I need to add a li to an ol when the return get pressed.
I've managed to do it but every time I update the innerhtml in my contenteditable div the caret reset to the start position. I'd like to position the caret inside the newly created li.
I've tried something like this but unfortunately it doesn't work:
setCursor() {
let el, el2, range, sel;
el = document.getElementById('editor');
range = document.createRange();
sel = window.getSelection();
if (el.childNodes.length > 0) {
el2 = el.childNodes[el.childNodes.length - 1];
range.setStartAfter(el2);
} else {
range.setStartAfter(el);
}
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
I've also tried replacing
range.setStartAfter(el2);
with
range.setStart(el2,0);
but still nothing happens.
Does anybody have any idea to make it working?
Thanks.
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>
I have tried everything I can think of, but for the life of me I can't programmatically select any text after the bold tag. I have tried setStart on all of the 3 nodes in the childNodes array, but it still won't work.
Can someone explain what I need to do to get it to work?
<div class = "tr" id = "data">
This text is <b>bold</b> as well it should be.
</div>
<button onclick="makeSelection(1,10);">Selection before bold</button>
<button onclick="makeSelection(35,38);">Selection after bold</button>
And then the JS:
function makeSelection(start, end) {
var parent = document.getElementById('data');
var range = document.createRange();
range.setStart(parent.childNodes[0], start);
range.setEnd(parent.childNodes[0], end);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
JSFiddle
Thanks!
Not entirely sure if this is what you wanted but you could simply pass the childNode index as part of the function call?
<div class = "tr" id = "data">
This text is <b>bold</b> as well it should be.
</div>
<button onclick="makeSelection(1,18, 0);">Selection before bold</button>
<button onclick="makeSelection(1,23, 2);">Selection after bold</button>
And then simply change the script to use this variable:
function makeSelection(start, end, child) {
var parent = document.getElementById('data');
var range = document.createRange();
range.setStart(parent.childNodes[child], start);
range.setEnd(parent.childNodes[child], end);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
As your current code stands it would never be able to reach the 35th - 38th position as you are still in the first child node. Calling this will search the node and receive an out of bounds exception as the first node is only 19 chars long.
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
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>