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.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With