15

I am trying to select a dynamically added timestamp - hit enter in the contentEditable element.

$('#content').on('keypress', function(e) {
  if (e.which === 13) {
    console.log('enter pressed');
    e.preventDefault();
    var range = window.getSelection().getRangeAt(0);
    var element = document.createElement('p');
    element.setAttribute("id", "uniqueIdentifier");
    var date = new Date().getTime();
    date = date.toString();
    console.log('date: ' + date);
    element.textContent = date;
    //element.innerHTML = '<br>';
    range.insertNode(element);
    var range2 = document.createRange();
    console.log('$(#content).text(): ' + $('#content').text());
    var startOffset = $('#content').text().indexOf(date);
    console.log('startOffset: ' + startOffset);
    range2.setStart(document.getElementById('uniqueIdentifier'), startOffset);
    range2.setEnd(document.getElementById('uniqueIdentifier'), startOffset + date.length);
    //        $('#content p.new').focus();
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id='content' contentEditable='true'>test</p>

I am getting this error:

Uncaught IndexSizeError: Failed to execute 'setStart' on 'Range': There is no child at offset 4.

What am I missing?

Rahul Desai
  • 15,242
  • 19
  • 83
  • 138

1 Answers1

24

There are two ways. Either pass the DOM node or pass node's text part. If you pass the DOM node you should give the start and end offsets as 0 and 1 to create a range over the DOM node like this

range.setStart(document.getElementById('uniqueIdentifier'), 0);
range.setEnd(document.getElementById('uniqueIdentifier'), 1);

If you are giving the text node part then you have to give the length of the text node to create a range over the text part of the DOM node like this

var uid = document.getElementById('uniqueIdentifier');
range.setStart(uid.firstChild, 0); <-- firstChild refers to the text part of a DOM node
range.setEnd(uid.firstChild, uid.firstChild.length);

You are mixing the two ways. You can solve it like this

$('#content').on('keypress', function(e) {
  if (e.which === 13) {
    e.preventDefault();
    var date = new Date().getTime();
    date = date.toString();
    var range = window.getSelection().getRangeAt(0);
    var element = $('<p></p>')
                    .attr('class', 'uniqueIdentifier') 
                    .text(date);
    range.insertNode(element[0]); // the date node is added to the DIV at this stage
    var range2 = document.createRange();
    range2.setStart(element[0], 0); // <--- Give the entire date node starting at 0
    range2.setEnd(element[0], 1); // <--- Ending at 1 since there is only one node that you want a range of 
    var newNode = document.createElement("b"); // <--- I created a b element just so that the range is visible
    range2.surroundContents(newNode);
  }
});

Hope this helps.

$('#content').on('keypress', function(e) {
  if (e.which === 13) {
    e.preventDefault();
    var date = new Date().getTime();
    date = date.toString();
    var range = window.getSelection().getRangeAt(0);
    var element = $('<p></p>')
      .attr('class', 'uniqueIdentifier')
      .text(date);
    range.insertNode(element[0]);
    var range2 = document.createRange();
    range2.setStart(element[0], 0);
    range2.setEnd(element[0], 1);
    var newNode = document.createElement("b");
    newNode.style.color = getRandomColor(); // <-- Just so that range is visible
    range2.surroundContents(newNode);
  }
});

/* Courtesy: http://stackoverflow.com/a/1484514 */
function getRandomColor() {
  var letters = '0123456789ABCDEF'.split('');
  var color = '#';
  for (var i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p id='content' contentEditable='true'>test</p>
Dhiraj
  • 33,140
  • 10
  • 61
  • 78
  • 4
    great answer! Thanks, the .firstChild did it for me. Wasn't clear from the reference to me that we have to call .firstChild on a span element to define the text within. – Heribert Feb 25 '20 at 10:27