1

I am trying to create an icon in my contenteditable div after clicking on a button with the icon. The issue is that while selecting the icon/button I lose the caret in the text. I want this caret back at its old place after closing the buttons.

I made a JS-Fiddle to illustrate the issue: JSFiddle

PS: The question is especially about the caret. It doesn't matter a lot to me if the icon is inserted or not. That part should be easy to figure out :).

html

<div id="write-mail">
   <div style="position: relative;">
      <div id="new-mail-content" contenteditable="true">
          <div>How to return the caret where it was in the sentence after clicking the buttons?</div>
          <div>Maybe prevent the caret from being removed after clicking the buttons?</div>
          <div>I know I can store the current selection by using window.getSelection(). If I would be able to reactivate this selection, that would be great!</div>
      </div>
      <div id="emotepicker">
        <button><span>&#10004;</span></button>
        <button><span>&#10031;</span></button>
        <button><span>&#10008;</span></button>
      </div>
  </div>
        <button class="add-icon">Add Icon</button>
</div>

css

#new-mail-content{
  background-color: white;
  min-height: 5em;
}
#write-mail button{
  height: 30px;
}
#emotepicker{
  display: none; 
  position: absolute;
  left: 0px;
  right: 0px;
  bottom: 0px;
  background-color: rgba(0,0,0,0.1);
  text-align: center;
}
div{
  padding: 2px;
}

jquery/javascript

$(".add-icon").click(function(){
    $("#emotepicker").toggle();
});
Rob Monhemius
  • 4,822
  • 2
  • 17
  • 49

2 Answers2

2

Use the jquery focus method in your click event method.

You might want to make the new-mail-content div into a text area. Something like this should work.

$(".add-icon").click(function(){
    $("#emotepicker").toggle();
    $("#new-mail-content").focus();
});

Here is a link to the documentation: https://api.jquery.com/focus/

John Meyer
  • 2,296
  • 1
  • 31
  • 39
  • This doesn't really answer the question. As stated in the question I am working with a contenteditable div. I can't work around this. With a text area there is not really a problem placing the caret back on its old spot. Also .focus() does not retain the old position of the caret. – Rob Monhemius Apr 18 '17 at 13:32
  • Just by focusing on the control won't do the trick as the editor is no longer there after another div was opened in front of it. You need to save the cursor position and restore it after that div has been closed. – ProgrammerV5 Apr 18 '17 at 17:47
  • Hmm I might've been too quick to dismiss this answer. For some reason it actually returns the caret to where it was before: https://jsfiddle.net/bdy2Ltwo/6/. Is this some inbuilt functionality of jQuery? I can't find anything about the caret in the official jQuery docs. Is this reliable in all browsers (it seems to work in Chrome and MS-Edge)? – Rob Monhemius Apr 19 '17 at 01:53
  • The focus method has been part of JQuery since the 1.0 release in 2006. I can't say for certain if it works with all browsers, but I would think that 11 year old functionality should be supported. – John Meyer Apr 19 '17 at 20:26
0

This should do what you need:

var caretpos =0 ;
$(".add-icon").click(function(){
    $("#emotepicker").toggle();
});

$('#close').click(function(e){
    $("#emotepicker").toggle();
  $("#new-mail-content").focus();
  var node = document.querySelector("new-mail-content");
  node.focus();
  var textNode = node.firstChild;
  var caret = caretpos;
  var range = document.createRange();
  range.setStart(textNode, caret);
  range.setEnd(textNode, caret);
  var sel = window.getSelection();
  sel.removeAllRanges();
  sel.addRange(range);
})

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;
}

function showCaretPos() {
    var el = document.getElementById("new-mail-content");
    var caretPosEl = document.getElementById("caretposition");
    caretpos = getCaretCharacterOffsetWithin(el);
    caretPosEl.innerHTML = "Caret position: " + carePosEl;
}

document.body.onkeyup = showCaretPos;
document.body.onmouseup = showCaretPos;

https://jsfiddle.net/bdy2Ltwo/4/

ProgrammerV5
  • 1,915
  • 2
  • 12
  • 22
  • The getCaretCharacterOffsetWithin(element) seems to work like a charm. You made a small typo in "showCaretPos()"; carePosEl was supposed to be caretposEl. In "$('#close').click(function(e)" I am not sure if document.querySelector("new-mail-content") is correct. This actually led me to remove part of the code and I ended up with John Meyer's answer. Your answer is a very good step in the right direction and might lead to better cross-browser compatability however. – Rob Monhemius Apr 19 '17 at 02:05
  • You are absolutely right, I tried but I can't, it says that it need to be edited for me to vote. I tried to modify it (I can't add nothing meaningful to it) but needs to go trough peer review. IF you add something to your post I'll up vote it as soon as I can. – ProgrammerV5 Apr 21 '17 at 15:40
  • Thank you, however the comment was directed at RMo. I guess I can move the link to the bottom... – John Meyer Apr 21 '17 at 18:51