I am trying to listen for an event that indicates a new line has been created in a content editable div. The purpose is to eventually show a user some options each time a new empty line is created and the caret is on that line, or if the user clicks the caret position to a currently empty line.
In my book there seem to be four events that would lead to the user being on a new line within a contentediable div:
Of course in a contentediable div a new line means different things to different browsers, in chrome it seems to create <div><br/></div>
tags, but having browsed around SO enough it seems that other browsers might create <div><p></p></div>
or perhaps <span>
tags.
I've now tried to figure this out a couple of times and just have to get it done. Is it really the best way to listen for new elements being added under that div, and/or check if the caret position is currently within empty 'new line' tags. checking each time the caret moves seems highly inefficient - is there a better way to do this?
To summarise for tldr; people
Just fyi this is within an Angular context and I have jQuery also (the answer can choose to use or not use these libraries).
----- edit -----
So far the answers have focused on the creation of new line 'events' and monitoring those events, perhaps polling the caret position is a more reliable way to determine if the caret is on a empty line or not.
The other answers so far discussed monitoring the events that create a newline within a content editable div. However from the efforts so far it seems that this is not a reliable way to monitor if the user has brought the caret at a new line.
Despite concerns that having a setInterval event is clunky and adding to overhead, perhaps this is just my perception. I've tested the below in Firefox and Chrome on OSX.
function getNewLines() {
console.log(document.getSelection());
if (document.getSelection().anchorNode) {
var node = document.getSelection().anchorNode;
var nodeContents = "";
if (node.innerHTML) {
nodeContents = node.innerHTML.toLowerCase();
} else {
nodeContents = node.innerHTML;
}
if (nodeContents !== undefined) {
if (nodeContents === "" || nodeContents == "<br>" || nodeContents ==
"<br/>" || nodeContents.substr(nodeContents.length - 5) == "<br/>" ||
nodeContents.substr(nodeContents.length - 4) == "<br>") {
console.log("you are on a new line :]");
} else {
console.log('nope not a new line');
}
} else {
console.log('nice try, but no not a new line');
}
}
}
setInterval(getNewLines, 100);
<div contenteditable="true">
<p>Lets get started!</p>
</div>
See Fiddle Here
side-note: the behaviour of newlines in content editable seems varied, but I noted in firefox that it will mimic the existing elements in the field. ie if you are using <div></div>
, firefox will use <div></div>
, if you are using <p></p>
firefox will do the same
This is what I did:
$("#main").keyup(function (e) {
if (e.which == 13) {
doSomething();
}
});
$("#main").on("click", "div:has(br)", function () {
doSomething();
});
$("#main").on("paste", function () {
setTimeout(function () {
debugger;
if ($('#main').children().last().children().last().has('br').length >0) {
doSomething();
}
}, 100);
});
function doSomething(){
alert("new line...");
}
Here is the JSFiddle demo
I've tested this code on Chrome and Edge. Edge removed the empty line when I copy and pasted from notepad.
http://codepen.io/tzach/pen/XbGWjZ?editors=101
angular
.module('app', [])
.directive('contenteditable', function ($timeout) {
return {
link: function($scope, element, attrs) {
element.css('white-space', 'pre');
element.on('keypress', function (e) {
if (e.keyCode === 13) {
$timeout(function () {
onNewLine();
}, 100);
}
});
element.on('paste', function (e) {
$timeout(function () {
var text = element.text();
var textRows = text.split('\n');
var html = element.html();
var htmlRows = html.split('<br>');
if (textRows[textRows.length - 1].trim() == '' ||
htmlRows[htmlRows.length - 1].trim() == '') {
onNewLine();
}
},0);
});
element.on('click', function(e) {
var html = e.target.innerHTML;
var text = e.target.innerText;
if (text === '\n' || html.toLowerCase() === '<br>') {
onNewLine();
}
});
function onNewLine() {
alert('new empty line');
}
}
}
});
Here's a start for you. I wouldn't even bother trying to guess at different browser implementations. Instead, shim it via pushing focus to a hidden textarea and then testing based on a newline in that (e.g. textarea.value.match(/\n$/)
).
That should handle all use cases, and the rest is just faking caret and selection via range. There are plugins that exist for just this purpose.
JavaScript
var contentEditable = document.querySelector('.content pre'),
textarea = document.querySelector('.content textarea');
contentEditable.addEventListener('click', function() {
textarea.focus();
contentEditable.classList.add('pseudo-focus');
});
textarea.addEventListener('keyup', function(e) {
contentEditable.innerText = textarea.value;
if (textarea.value.match(/\n$/)) {
alert('newline');
}
});
HTML
<div class="content">
<pre id="editable" contenteditable></pre>
<textarea></textarea>
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With