Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trigger event on new line in content editable div

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:

  • pressing enter
  • pasting content that has a empty new line at the end
  • clicking causing the caret (blinking line) to move position to an empty line
  • using the arrow keys to move to a new line

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

  • is there a better way to check for new lines in content editable divs?
  • how would I trigger an event based upon that efficiently?

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.

like image 379
David Avatar asked Aug 05 '15 11:08

David


4 Answers

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

like image 195
David Avatar answered Nov 07 '22 16:11

David


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

like image 25
Ahs N Avatar answered Nov 07 '22 16:11

Ahs N


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');
                }
            }
        }
    });
like image 1
Tzach Ovadia Avatar answered Nov 07 '22 16:11

Tzach Ovadia


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>

JSFiddle Example

like image 1
Josh Burgess Avatar answered Nov 07 '22 17:11

Josh Burgess