Reputation: 1528
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
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