7

I use a contenteditable div as an input field to type some text and insert icons via a button (small html pictures) within that text.

As long as the text is narrower than the contenteditable field, it's fine.

Once the text is longer than the field (so it's partially hidden):

when I enter a text character, it's all good too, the last character is automatically shown after the key is pressed so that you can always see what you are typing. But when I enter an icon via the button, then the icon is there alright but it's hidden as the field content doesn't move to make the newly entered icon visible, until I enter another text character.

Any solution to this so that the last element entered (text or html) is always visible to the user?

function pasteIcon(html) {
  var sel, range;
  if (window.getSelection) {
    sel = window.getSelection();
    if (sel.getRangeAt && sel.rangeCount) {
      range = sel.getRangeAt(0);
      range.deleteContents();
      var el = document.createElement("div");
      el.innerHTML = html;
      var frag = document.createDocumentFragment(),
        node, lastNode;
      while ((node = el.firstChild)) {
        lastNode = frag.appendChild(node);
      }
      range.insertNode(frag);
      if (lastNode) {
        range = range.cloneRange();
        range.setStartAfter(lastNode);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);
      }
    }
  } else if (document.selection && document.selection.type != "Control") {
    document.selection.createRange().pasteIcon(html);
  }
}

$(document).ready(function() {
  $('.buttOn').click(function() {
    $('.contEd').focus();
    pasteIcon('<img class="icOn" src="http://www.bmgstuff.com/files/interface/generator_frame/text_blood.png">');
  })
});
[contenteditable="true"] {
  display: inline;
  white-space: nowrap;
  overflow: hidden !important;
  text-overflow: inherit;
  -webkit-user-select: text !important;
  -moz-user-select: text !important;
  -ms-user-select: text !important;
  user-select: text !important;
}

[contenteditable="true"] br {
  display: none;
}

.contAiner {
  display: flex;
}

.buttOn {
  width: 24px;
  height: 24px;
  border: none;
  background: #666;
  color: white;
}

.contEd {
  height: 22px;
  text-align: center;
  width: 100px;
  line-height: 23px;
  color: black;
  font-size: 10.5px;
  font-family: arial;
  border: 1px solid black;
}

.icOn {
  width: 9px;
  height: 13px;
  top: 1px;
  position: relative;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="contAiner">
  <input class="buttOn" type="button" value="B">
  <div class="contEd" contenteditable="true" spellcheck="false" autocomplete="off"></div>
</div>

Here is the jsFiddle

And here is the original thread I took the "pasteIcon" function from.

PS: I tried to trigger a keycode 39 (right arrow), just after the pasteIcon function in order to simulate a keypress but it just didn't work.

Bachir Messaouri
  • 754
  • 2
  • 8
  • 31

1 Answers1

5

You can just scroll your editor to the inserted icon. Pay attention on two lines of code just after you move selection. Hope it works as you expected :)

UPDATE:

To cover all the cases we need to check whether inserted image is in or out the editor bounds. First, let's add id to the editor element just to find it more easy. Then we can utilize the function getBoundingClientRect returning an actual element's rectangle on the screen. Finally, we compare the rectangles and if the image rectangle is not inside the editor (imgRect.left < editorRect.left || imgRect.right > editorRect.right) then we scroll.

UPDATE 2:

During the investigation of the problem described in the latest comments I found that after certain length of edited content jQuery function 'offset' returns not accurate results. Most probably, it's because the editor's leftOffset is not automatically updated in this circumstances. Finally, I changed the desired scroll position calculation to image DOM element's offsetLeft minus editor element's offsetLeft minus 1 (border size) and now it works fine with any content length.

function pasteIcon(html) {
  var sel, range;
  if (window.getSelection) {
    sel = window.getSelection();
    if (sel.getRangeAt && sel.rangeCount) {
      range = sel.getRangeAt(0);
      range.deleteContents();
      var el = document.createElement("div");
      el.innerHTML = html;
      var frag = document.createDocumentFragment(),
        node, lastNode;
      while ((node = el.firstChild)) {
        lastNode = frag.appendChild(node);
      }
      range.insertNode(frag);
      if (lastNode) {
        range = range.cloneRange();
        range.setStartAfter(lastNode);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);

        var editorRect = $(contEdit)[0].getBoundingClientRect();
        var imgRect = $(lastNode)[0].getBoundingClientRect();
        if (imgRect.left < editorRect.left || imgRect.right > editorRect.right) {
          var actualLeft = $(lastNode)[0].offsetLeft - editorRect.left - 1;
          $(".contEd").scrollLeft(actualLeft);
        }
      }
    }
  } else if (document.selection && document.selection.type != "Control") {
    document.selection.createRange().pasteIcon(html);
  }
}

$(document).ready(function() {
  $('.buttOn').click(function() {
    $('.contEd').focus();
    pasteIcon('<img class="icOn" src="http://www.bmgstuff.com/files/interface/generator_frame/text_blood.png">');
  })
});
[contenteditable="true"] {
  display: inline;
  white-space: nowrap;
  overflow: hidden !important;
  text-overflow: inherit;
  -webkit-user-select: text !important;
  -moz-user-select: text !important;
  -ms-user-select: text !important;
  user-select: text !important;
}

[contenteditable="true"] br {
  display: none;
}

.contAiner {
  display: flex;
}

.buttOn {
  width: 24px;
  height: 24px;
  border: none;
  background: #666;
  color: white;
}

.contEd {
  height: 22px;
  text-align: center;
  width: 100px;
  line-height: 23px;
  color: black;
  font-size: 10.5px;
  font-family: arial;
  border: 1px solid black;
}

.icOn {
  width: 9px;
  height: 13px;
  top: 1px;
  position: relative;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="contAiner">
  <input class="buttOn" type="button" value="B">
  <div id="contEdit" class="contEd" contenteditable="true" spellcheck="false" autocomplete="off"></div>
</div>
Alexander Dayan
  • 2,846
  • 1
  • 17
  • 30
  • Mmm...Not quite actually. It does what I asked for alright. Granted. But then, go back to another place in the middle of your text and hit the icon button, it will then show you another portion of the text, not quite where you pasted the icon. Example: if I type "This is a test in order to show the problem". If I add an icon just after the last character, it will be shown. Great. But if I go back to "This" and paste an icon just after, it will not react as expected. Still, it's a big step towards the solution. – Bachir Messaouri Jan 15 '18 at 10:29
  • So, basically, the "lastNodeOffset" takes for granted that the icon will be pasted at the end of the text which is not necessarily the case. It could be pasted anywhere and wherever it is pasted, that's where it should be seen. Thanks for your help. – Bachir Messaouri Jan 15 '18 at 10:38
  • Of course, in my answer I didn't cover all the cases but just provided the general solution. Instead of checking whether the item is the last, you can just check whether it is inside or outside the visible content and then scroll if necessary. – Alexander Dayan Jan 15 '18 at 10:58
  • "you can just check whether it is inside or outside the visible content and then scroll if necessary". Well... I believe it was my question somehow. How to check if it's inside or outside the visible content is my problem to begin with. You give me too much credit for solving this on my own. If I were able to do that, there would be no problem. Thank you anyway. – Bachir Messaouri Jan 15 '18 at 11:06
  • 1
    Got your point. I will update my answer soon to provide the full solution. – Alexander Dayan Jan 15 '18 at 11:44
  • I tested your script and it's almost it (there are still unexpected behaviors). Thank you very much for your help. Here is the problem now: if I type again "This is a test in order to show the problem" and add an icon just after the last character, it will be shown. Great. If I go back to "This" and paste an icon just after, it will be shown too: super great. But then, if I go back at the end of the sentence and add the word "here" for instance and click the button again, every odd click will behave strangely while every even click will behave as expected. This is definitely a brain burner. – Bachir Messaouri Jan 15 '18 at 13:41
  • Tested a bit and so far so good. I thank you a thousand times. I know for a fact that this was way beyond me and my skills. Thank you again! – Bachir Messaouri Jan 16 '18 at 12:54