Wednesday 12:20 pm
1st May 2019 ·
How We Got Line Break To Work Consistently with contentEditable
(Well, In Firefox At Least)
A challenge of getting the markdown parser to work more reliably was getting predictable HTML from the contentEditable node. Depending on what the user entered, contents of a contentEditable (at least in Firefox for these tests) generated <div> elements around <br>s, and a collection of seemingly random combinations of HTML tags.
The other main quirk we encountered is not being able to append new lines unless <br> was the last child node of a contentEditable div.
For this we wrote an event handler to intervene when the Enter key is pressed. It forces the contentEditable to insert <br> tags where needed, instead of Firefox's default behaviour which tries to generate HTML the best it can. This way we can make a contentEditable behave more like a <textarea> and still have rich-text editing features for images, videos, and links.
We have attached the code used which is minimalist and has only been tested in Firefox itself. Other browsers will be tested in later coding sessions. It works as expected although we are constantly searching for other bugs that appear.
Throughout these code snippets, index_editdescription
is the node of the <div> being edited, usually accessed through document.getElementById(...)
.
Firstly, we need to capture the key presses to intervene when Enter is pressed:
index_editdescription.addEventListener('keydown', index_js_editorKeyDownEvent, false);
Next, the following function inserts the required <br> tags; one at the cursor, and another if the last child element isn't a line break. Additionally, the function also prevents new lines being inserted in a blank div although this is oversimplified and doesn't check for white-space characters.
function index_js_editorKeyDownEvent(ev)
{
if (ev.keyCode == 13) {
ev.preventDefault();
if (index_editdescription.innerHTML == '') {
return false;
}
if (window.getSelection) {
var sel = window.getSelection();
var range = sel.getRangeAt(0);
var brNode = document.createElement('br');
range.deleteContents();
range.insertNode(brNode);
range = range.cloneRange();
range.selectNodeContents(brNode.nextSibling);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
} // TODO: Impliment document.getSelection and document.selection.createRange for cross-browser compatibility.
var lastChildTag = index_editdescription.lastChild.nodeName.toLowerCase();
if (lastChildTag != 'br') {
index_editdescription.appendChild(document.createElement('br'));
}
index_editdescription.focus();
}
}