Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting `selectionStart` from a span's innerHTML

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.

like image 796
user1904273 Avatar asked Dec 14 '12 14:12

user1904273


1 Answers

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);
like image 67
MaxArt Avatar answered Sep 20 '22 12:09

MaxArt