I have a roll your own text editor that lets you change portions of a textarea
element. i want to adapt it to work with a span element. I have no particular attachment to span. The goal is simply to let someone edit html rather than a textarea. I have it working fine in IE but am encountering some problems with Mozilla.
Since I'm using a span instead of form input I am using innerHTML
instead of value. However, I can't seem to get the selectionStart
and selectionEnd
functions to work on innerHTML
as opposed to value
.
Here is the textarea
code that works fine....
html
<textarea id="textarea>Some text goes here</textarea><a href="javascript:void() onclick="editText">edit</a>
JS
function editText() {
var len = displaytext.value.length;
var start = displaytext.selectionStart;
var end = displaytext.selectionEnd;
var sel = displaytext.value.substring(start, end); returns selection ok
alert(sel);
}
However, the following adaption is not limiting the selection to start and end.
html
<span id="textarea>Some text goes here</span><a href="javascript:void() onclick="editText">edit</a>
JS
function editText() {
var len = displaytext.innerHTML.length; //works ok
var start = displaytext.selectionStart; //does not seem to work
var end = displaytext.selectionEnd; //does not seem to work
var sel = displaytext.innerHTML.substring(start, end); //returns whole innerHTML not selection
alert(sel);
}
Is there a problem with selecionStart
on innerHTML
? Workaround? Syntax error? Thanks for any suggesions.
First of all, don't use innerHTML
for this kind of things. textContent
is the way to go here.
I'm assuming a standard-compliant browser or IE9+ here.
The first thing to do is to get the document selection. You can do this by
var sel = getSelection();
Now, the selection can be empty, or can be outside the element, so check:
var rng, startSel, endSel, sel;
if (!sel.rangeCount
|| displaytext.compareDocumentPosition((rng = sel.getRangeAt(0)).startContainer) === Node.DOCUMENT_POSITION_PRECEDING
|| displaytext.compareDocumentPosition(rng.endContainer) === Node.DOCUMENT_POSITION_FOLLOWING)
sel = "";
else {
...
At this point, rng
contains a Range
object, with the properties startOffset
and endOffset
. This values are integers that refers to the position of the textContent
property of startContainer
and endContainer
respectively.
You have a fairly simple case, which is a <span>
element with a single text node. In this case, rng.startContainer
and rng.endContainer
will always be either displaytext
, or some preceding or following element, but not a descendant of displaytext
.
...
else {
startSel = displaytext.compareDocumentPosition(rng.startContainer) === Node.DOCUMENT_POSITION_FOLLOWING ? 0 : rng.startOffset;
endSel = displaytext.compareDocumentPosition(rng.endContainer) === Node.DOCUMENT_POSITION_PRECEDING ? displaytext.textContent.length : rng.endOffset;
sel = displaytext.textContent.substring(startSel, endSel);
}
In case of a more complex structure of displaytext
, with children elements and all, things become a little tricky. You'd have to find the text offset of the starting node in the descendants tree of displaytext
, and the best way to do so is using a TreeWalker
object walking the text nodes:
var tw = document.createTreeWalker(displaytext, NodeFilter.SHOW_TEXT, null, null);