6

have this html:

<div id="editable" contentEditable="true"  >
    <span contentEditable="false" >Text to delete</span>
</div>

need that the span (and all text inside) is removed with a single backspace, is it possible?

byterussian
  • 3,539
  • 6
  • 28
  • 36
  • Please could you provide more information about what should happen when the user presses backspace when, for example, the caret is in the middle of a text node, or at the start of the editable area. – Tim Down Feb 01 '10 at 18:18

2 Answers2

12

This turned out to be more complicated than I thought. Or I've made it more complicated than it needs to be. Anyway, this should work in all the big browsers:

function getLastTextNodeIn(node) {
    while (node) {
        if (node.nodeType == 3) {
            return node;
        } else {
            node = node.lastChild;
        }
    }
}

function isRangeAfterNode(range, node) {
    var nodeRange, lastTextNode;
    if (range.compareBoundaryPoints) {
        nodeRange = document.createRange();
        lastTextNode = getLastTextNodeIn(node);
        nodeRange.selectNodeContents(lastTextNode);
        nodeRange.collapse(false);
        return range.compareBoundaryPoints(range.START_TO_END, nodeRange) > -1;
    } else if (range.compareEndPoints) {
        if (node.nodeType == 1) {
            nodeRange = document.body.createTextRange();
            nodeRange.moveToElementText(node);
            nodeRange.collapse(false);
            return range.compareEndPoints("StartToEnd", nodeRange) > -1;
        } else {
            return false;
        }
    }
}

document.getElementById("editable").onkeydown = function(evt) {
    var sel, range, node, nodeToDelete, nextNode, nodeRange;
    evt = evt || window.event;
    if (evt.keyCode == 8) {
        // Get the DOM node containing the start of the selection
        if (window.getSelection && window.getSelection().getRangeAt) {
            range = window.getSelection().getRangeAt(0);
        } else if (document.selection && document.selection.createRange) {
            range = document.selection.createRange();
        }

        if (range) {
            node = this.lastChild;
            while (node) {
                if ( isRangeAfterNode(range, node) ) {
                    nodeToDelete = node;
                    break;
                } else {
                    node = node.previousSibling;
                }
            }

            if (nodeToDelete) {
                this.removeChild(nodeToDelete);
            }
        }
        return false;
    }
};
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • no, i need to remove only the element at the left of text cursor | – byterussian Feb 01 '10 at 16:27
  • how can i make this code more dynamic by add span usnig Enter KeyDown Press and remove the span using backspace help me if you can. – Basbous Jul 25 '11 at 15:14
  • this works in crome but not in firefox. Is there any node related issue in firefox browser. I need help , please [see attached link](http://jsfiddle.net/C5R8M/7) . – Dhananjay C Jun 23 '14 at 12:00
  • Dhananjay C: it works fine without the errors in your code - http://jsfiddle.net/dpx4f9hm/ – Kelly Mar 27 '15 at 21:22
11

Because you want to delete the whole element, it's better to make it contenteditable="false" so that browser won't let the contents of an element to be deleted.

Then you can use this attribute for tests in event handler as follows:

$('#editable').on('keydown', function (event) {
    if (window.getSelection && event.which == 8) { // backspace
        // fix backspace bug in FF
        // https://bugzilla.mozilla.org/show_bug.cgi?id=685445
        var selection = window.getSelection();
        if (!selection.isCollapsed || !selection.rangeCount) {
            return;
        }

        var curRange = selection.getRangeAt(selection.rangeCount - 1);
        if (curRange.commonAncestorContainer.nodeType == 3 && curRange.startOffset > 0) {
            // we are in child selection. The characters of the text node is being deleted
            return;
        }

        var range = document.createRange();
        if (selection.anchorNode != this) {
            // selection is in character mode. expand it to the whole editable field
            range.selectNodeContents(this);
            range.setEndBefore(selection.anchorNode);
        } else if (selection.anchorOffset > 0) {
            range.setEnd(this, selection.anchorOffset);
        } else {
            // reached the beginning of editable field
            return;
        }
        range.setStart(this, range.endOffset - 1);


        var previousNode = range.cloneContents().lastChild;
        if (previousNode && previousNode.contentEditable == 'false') {
            // this is some rich content, e.g. smile. We should help the user to delete it
            range.deleteContents();
            event.preventDefault();
        }
    }
});

demo on jsfiddle

SleepWalker
  • 1,152
  • 14
  • 17
  • Exactly fixed my problem – Saravanan Sep 23 '16 at 05:14
  • wow... what a alarming simple solution! setting the contenteditable to false of the div I wanted to delete let me delete the div in one go! – Ayudh May 06 '20 at 03:49
  • Great answer! Many thanks! One question though. What do you mean by "selection is in character mode."? I also cannot seem to trigger that event. /K – Kermit Apr 17 '21 at 10:35
  • I managed to trigger the section "selection is in character mode" by deleting a line break. /K – Kermit Apr 21 '21 at 17:20