rydwolf
rydwolf

Reputation: 1528

Enter key needs to be pressed twice for range to be added

I just learned how to use selection and range, and built a better way to press enter in contenteditable divs. When enter is pressed, event.preventDefault() is called, and the program inserts <br> where contenteditable would usually add a div. However, I need to press enter twice for anything to happen. Another odd fact: this doesn't happen when there is no text on that line. I am new to selection and range, and I'm sure it's a rookie mistake. Here is the code:

 function keyExamine(e) {
    var doc = document.getElementById('cmd');
    e.keyCode = e.keyCode || e.which;
    if (e.keyCode == 13) {
        if (e.ctrlKey) {
            alert(doc.innerHTML);
        } else {
            e.preventDefault();
            initiateCommand(doc.innerHTML);
            var dv = doc.ownerDocument.defaultView;
            var sel = dv.getSelection();
            var range = sel.getRangeAt(0);
            var enterNode = document.createElement('br');
            range.insertNode(enterNode);
            range.setStartAfter(enterNode);
            range.setEndAfter(enterNode); 
            sel.removeAllRanges();
            sel.addRange(range);
            document.getElementById('maxback').innerText = doc.innerText.replace(/(\r\n\t|\n|\r\t)/gm,"").length;
        }
    } else if (e.keyCode == 38) {
        e.preventDefault();
    } else if (e.keyCode == 37 || e.keyCode == 8 || e.keyCode == 46) {
        var ml = Number(document.getElementById('maxback').innerText);
        if (getCaretPosition(doc) <= ml) {
            e.preventDefault();
        }
    } else {
        var ml = Number(document.getElementById('maxback').innerText);
        if (getCaretPosition(doc) < ml) {
            e.preventDefault();
        }
    }
}

function getCaretPosition(element) {
    var caretOffset = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            caretOffset = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
    }
    return caretOffset;
}

I have the following HTML:

<p id='maxback' style='display: none'>0</p>
<p contenteditable="true" onkeydown="keyExamine(event)" id="cmd"></p>

Upvotes: 1

Views: 316

Answers (1)

mmKALLL
mmKALLL

Reputation: 154

There exists a way to handle this that takes care of all the issues while avoiding numerous pitfalls. You can insert a text node containing a newline, and then collapse all selections to the end of it.

This method is both platform- and browser-independent, working all the way back to IE9 (in theory).

The major downside? It relies on undefined behavior, setting the collapse index beyond the node length on purpose. It just so happens that all browsers correctly set the selection after the inserted node, even on the very first Enter press.

function handleKey(event) {
  const editor = document.getElementById('cmd')
  if (event.key === 'Enter') {
    const node = document.createTextNode('\n')
    const range = window.getSelection().getRangeAt(0)
    range.insertNode(node)
    
    const selection = document.getSelection()
    if (selection) {
      selection.collapse(node, 2)
    }
    
    event.preventDefault()
  }
}

Looking forward to browser vendors making this the default behavior when collapse is called with index 1 as well. Hopefully that happens someday.

Upvotes: 1

Related Questions