Pav Sidhu
Pav Sidhu

Reputation: 6944

Get and set cursor position with contenteditable div

I have a contenteditable div which I would like to be able to have users insert things such as links, images or YouTube videos. At the moment this is what I have:

function addLink() {
  var link = $('#url').val();
  $('#editor').focus();
  document.execCommand('createLink', false, link);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- Text Editor -->
<div id="editor" contenteditable="true"></div>

<!-- Add Link -->
<input type="text" id="url">
<button onclick="addLink()">Submit</button>

As you can see, the user has to type into a separate text box to enter the link address. As a result, when the link is added to the editor, it is not added to the position that the pointer/caret was on.

My question is how I can get and set the location of the pointer/caret. I have seen other questions such as this for setting the pointer however I would prefer to have a solution which is supported in all modern browsers, including Chrome, Safari, Firefox and IE9+.

Any ideas? Thanks.

Edit:

I found the code below which gets the position however, it only gets the position according to the line it is on. For example if I had this (where | is the cursor):

This is some text
And som|e more text

Then I would be returned the value 7, not 24.

function getPosition() {
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.getRangeAt) {
            return sel.getRangeAt(0).startOffset;
        }
    }
    return null;
}

Upvotes: 26

Views: 28617

Answers (5)

I&#39;m random
I&#39;m random

Reputation: 42

I found a problem with emojis. Emojis have a length which is more than 1. I solved it.

<script>
function getCursorPosition() {
        if(window.getSelection()){
            var sel = window.getSelection();
            if(sel.getRangeAt){
                var pos = sel.getRangeAt(0).startOffset;
                var endPos = pos + Array.from(editor.innerHTML.slice(0,pos)).length - editor.innerHTML.slice(0,pos).split("").length;
                return endPos;
            }
        }
        return null;
    }
var editor = document.querySelector("#editor");
var output = document.querySelector("#output");
editor.addEventListener("input",function(){
output.innerHTML = "Selection: " +  getCursorPosition();
});

</script>
<div contenteditable id="editor">text</div>
<div id="output">Selection:</div>

Upvotes: 1

zer00ne
zer00ne

Reputation: 43870

There's a ton of related info onsite. This one works for me and my clients.

DEMO

https://stackoverflow.com/a/6249440/2813224

function setCaret(line, col) {
  var ele = document.getElementById("editable");
  var rng = document.createRange();
  var sel = window.getSelection();
  rng.setStart(ele.childNodes[line], col);
  rng.collapse(true);
  sel.removeAllRanges();
  sel.addRange(rng);
  ele.focus();
}

//https://stackoverflow.com/a/6249440/2813224

var line = document.getElementById('ln').value;
var col = document.getElementById('cl').value;
var btn = document.getElementById('btn');
btn.addEventListener('click', function(event) {
  var lineSet = parseInt(line, 10);
  var colSet = parseInt(col, 10);
  setCaret(lineSet, colSet);
}, true);
<div id="editable" contenteditable="true">
  <br/>text text text text text text
  <br/>text text text text text text
  <br/>text text text text text text
  <br/>
  <br/>
</div>
<fieldset>
  <button id="btn">focus</button>
  <input type="button" class="fontStyle" onclick="document.execCommand('italic',false,null);" value="I" title="Italicize Highlighted Text">
  <input type="button" class="fontStyle" onclick="document.execCommand('bold',false,null);" value="B" title="Bold Highlighted Text">
  <input id="ln" placeholder="Line#" />
  <input id="cl" placeholder="Column#" />
</fieldset>

Upvotes: 16

Gaucho
Gaucho

Reputation: 1339

This is what you asked for, in your bounty: on the following example you can see how to detect the exact number of characters of the actual point where you clicked the mouse on:

   <!-- Text Editor -->
   <div id="editor" class="divClass" contenteditable="true">type here some text</div>


    <script>



   document.getElementById("editor").addEventListener("mouseup", function(key) {

alert(getCaretCharacterOffsetWithin(document.getElementById("editor")));

}, false);


 function getCaretCharacterOffsetWithin(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;
}
 </script>

Upvotes: 2

SpeedySan
SpeedySan

Reputation: 572

A good rich-text editor is one of the harder things to do currently, and is pretty much a project by itself (unfriendly API, huge number of corner cases, cross-browser differences, the list goes on). I would strongly advise you to try and find an existing solution.

Some libraries that can be used include:

Upvotes: 7

Ziv Weissman
Ziv Weissman

Reputation: 4516

I have tried to find a solution,

With a little help it can be perfected. It is a combination of answers I've found on SO, and my exp.

Its tricky, its messy... but if you must, you can use it but it requires a bit of work to support inner links (if you cursor is on an anchor it will create anchor inside anchor)

Here's the JS:

var lastPos;
var curNode = 0;
function setCaret() {
  curNode=0;
  var el = document.getElementById("editor");
  var range = document.createRange();
  var sel = window.getSelection();

  console.log(el.childNodes);
  if (el.childNodes.length > 0) {
      while (lastPos > el.childNodes[curNode].childNodes[0].textContent.length) {
      lastPos = lastPos - el.childNodes[curNode].childNodes[0].textContent.length;
      curNode++;

      }
      range.setStart(el.childNodes[curNode].childNodes[0], lastPos);
      range.collapse(true);
      sel.removeAllRanges();
      sel.addRange(range);
  }
  el.focus();
};


function savePos() {
  lastPos = getCaretCharacterOffsetWithin(document.getElementById('editor'));
}

function addLink() {
  console.log(lastPos);

  setCaret();
  console.log(getCaretCharacterOffsetWithin(document.getElementById('editor')));

  console.log('focus');

  // $("#editor").focus();
  var link = $('#url').val();
  document.execCommand('createLink', false, link);

}

function getCaretCharacterOffsetWithin(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;
}

fiddle

Upvotes: 4

Related Questions