8

I am making use of a contenteditable div in combination with the rangy Javascript library to insert HTML at the cursor position.

End of the day the contents of the div commonly looks like:

<div contenteditable="true">
    "Hello "
    <button contenteditable="false" data-id="147">@John Smith</button>
    " "
</div>

Users get suggested upon pressing '@' and get subsequently inserted as a button when selected (ala Google Plus). I also insert a &nbsp; after this button.

The button gets removed in Chrome/Safari/Firefox when you hit backspace (after first removing the &nbsp;), but not in IE8. In IE8 the cursor merely jumps over the button without removing it. What's even more bizarre in IE8 is if you leave the &nbsp; next to the button - and rather place the cursor right next to the button - it removes the button on backspace. So it's happy when there's a &nbsp; to the right of the cursor.

Does anyone know what I need in order to make IE8 work i.t.o. removing the button upon backspace without the need for a &nbsp; to the right of the cursor? (some info on this strange behaviour might also help)

P.S. I haven't tested other versions of IE

Markus Coetzee
  • 3,264
  • 26
  • 26
  • why do you have the contenteditable=false on the button? – Morgan T. Apr 03 '12 at 14:56
  • 1
    So that it deletes it from the DOM (in one go) when you hit backspace or when you hit delete (this currently works in Chrome/Safari/Firefox). If contenteditable=true then the button's text becomes editable, which is not what my use case is. – Markus Coetzee Apr 03 '12 at 15:09
  • cool I didn't know that. (I havent worked with inputs in contEd before). What about handling the backspace keydown and checking previoussibling of first node in selection range? – Morgan T. Apr 03 '12 at 19:37
  • I'm not sure how I would go about checking the previous sibling of first node in selection range. I know you can delete the contents of a range. Pointers? – Markus Coetzee Apr 03 '12 at 19:45
  • Is this what you mean: http://stackoverflow.com/questions/2177958/how-to-delete-an-html-element-inside-a-div-with-attribute-contenteditable – Markus Coetzee Apr 03 '12 at 20:02

2 Answers2

9

My suggestion would be to first check the caret is positioned after the non-editable node, and if so, create a range that starts immediately after the non-editable element and ends at the caret position. Check whether the text in this range is empty. If it is, that means the caret is placed directly after the non-editable element, so in that case remove the element. Finally, place the caret where the non-editable had been.

Live demo: http://jsfiddle.net/timdown/vu3ub/

Code:

document.onkeypress = function(e) {
    e = e || window.event;
    var keyCode = e.which || e.keyCode;
    if (keyCode !== 8) {
        return;
    }

    var sel = rangy.getSelection();
    if (sel.rangeCount === 0) {
        return;
    }

    var selRange = sel.getRangeAt(0);
    if (!selRange.collapsed) {
        return;
    }

    var nonEditable = document.getElementById("nonEditable");
    if (!nonEditable) {
        return;
    }

    // Check the caret is located after the non-editable element
    var range = rangy.createRange();
    range.collapseAfter(nonEditable);

    if (selRange.compareBoundaryPoints(range.START_TO_END, range) == -1) {
        return;
    }

    // Check whether there is any text between the caret and the
    // non-editable element. If not, delete the element and move
    // the caret to where it had been
    range.setEnd(selRange.startContainer, selRange.startOffset);
    if (range.toString() === "") {
        selRange.collapseBefore(nonEditable);
        nonEditable.parentNode.removeChild(nonEditable);
        sel.setSingleRange(selRange);

        // Prevent the default browser behaviour
        return false;
    }
};
Tim Down
  • 292,637
  • 67
  • 429
  • 506
  • Brilliant! Huge thanks for such a thorough answer. I tweaked it slightly - instead of using document.getElementById("nonEditable") I made use of selRange.startContainer.previousSibling (as I don't know what node I need to remove on backspace). – Markus Coetzee Apr 07 '12 at 19:08
  • Thanks a ton for the great answer, Tim. This has been extremely helpful for me today. – Brent Dillingham Feb 12 '13 at 19:18
1

I made a jsfiddle with a quick sample of how to look at the range of the selection and use the previousSibling property of the startContainer to find the button: jsfiddle

Put the cursor in aaa and it will show the button is prev sibling. put it in ccc and bbb will show.

So with this you could handle the keydown event of the div, check if key is backspace + previousSibling is your button, and remove it with jQuery.

previousSibling is null if it is a textnode though, just FYI.

Morgan T.
  • 1,911
  • 1
  • 17
  • 20
  • Very cool. I think I can use the previous sibling in combination with the range offset as a solution. I will report back on how it goes. – Markus Coetzee Apr 04 '12 at 08:38