简体   繁体   English

ContentEditable DIV中的不可编辑跨度的选择和删除问题

[英]Selection and deletion problems with Non-Editable Span inside ContentEditable DIV

I have a DIV with ContentEditable set to TRUE. 我有一个ContentEditable设置为TRUE的DIV。 Inside it there are several spans with ContentEditable set to FALSE. 在其中有几个跨度,ContentEditable设置为FALSE。

I trap the BackSpace key so that if the element under cursor is <span> I can delete it. 我捕获BackSpace键,以便如果光标下的元素是<span>我可以删除它。

The problem is that it works alternately with odd spans only. 问题是它只与奇数跨度交替工作。

So, for example, with the html code below, put the cursor at the end of text in DIV, and press backspace all the way till the beginning of div. 因此,例如,使用下面的html代码,将光标放在DIV中的文本末尾,并一直按退格键直到div的开头。 Observe that it will select/delete first span, then leave the second, then select/delete the third span, then leave the fourth and so on. 观察它将选择/删除第一个跨度,然后离开第二个跨度,然后选择/删除第三个跨度,然后离开第四个跨度,依此类推。

This behavior is only on Internet Explorer. 此行为仅适用于Internet Explorer。 It works exactly as expected on Firefox. 它在Firefox上完全按预期工作。

How should I make the behavior consistant in Internet Explorer? 我应该如何在Internet Explorer中使行为一致?

Following html code can be used to reproduce the behavior: 以下html代码可用于重现行为:

 var EditableDiv = document.getElementById('EditableDiv'); EditableDiv.onkeydown = function(event) { var ignoreKey; var key = event.keyCode || event.charCode; if (!window.getSelection) return; var selection = window.getSelection(); var focusNode = selection.focusNode, anchorNode = selection.anchorNode; if (key == 8) { //backspace if (!selection.isCollapsed) { if (focusNode.nodeName == 'SPAN' || anchorNode.nodeName == 'SPAN') { anchorNode.parentNode.removeChild(anchorNode); ignoreKey = true; } } else if (anchorNode.previousSibling && anchorNode.previousSibling.nodeName == 'SPAN' && selection.anchorOffset <= 1) { SelectText(event, anchorNode.previousSibling); ignoreKey = true; } } if (ignoreKey) { var evt = event || window.event; if (evt.stopPropagation) evt.stopPropagation(); evt.preventDefault(); return false; } } function SelectText(event, element) { var range, selection; EditableDiv.focus(); if (document.body.createTextRange && element.nodeName == 'SPAN') { range = document.body.createTextRange(); range.moveToElementText(element); range.select(); } else if (window.getSelection) { selection = window.getSelection(); range = document.createRange(); range.selectNodeContents(element); selection.removeAllRanges(); selection.addRange(range); } var evt = (event) ? event : window.event; if (evt.stopPropagation) evt.stopPropagation(); if (evt.cancelBubble != null) evt.cancelBubble = true; return false; } 
 #EditableDiv { height: 75px; width: 500px; font-family: Consolas; font-size: 10pt; font-weight: normal; letter-spacing: 1px; background-color: white; overflow-y: scroll; overflow-x: hidden; border: 1px solid black; padding: 5px; } #EditableDiv span { color: brown; font-family: Verdana; font-size: 8.5pt; min-width: 10px; _width: 10px; } #EditableDiv p, #EditableDiv br { display: inline; } 
 <div id="EditableDiv" contenteditable="true"> &nbsp;(<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field1</span> < 500) <span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>OR</span> (<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field2</span> > 100 <span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>AND</span> (<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field3</span> <=200) ) </div> 

EDIT 编辑

Just FYI. 仅供参考。 I have asked this question in MSDN Forum as well. 我也在MSDN论坛上问了这个问题。

The challenge to this is to get IE11 to backspace from the right directly against the <span>. 对此的挑战是让IE11直接从<span>直接退格。 Then the next backspace will select and highlight it. 然后下一个退格将选择并突出显示它。 This seems like such a simple objective, but IE11 just won't cooperate. 这似乎是一个如此简单的目标,但IE11只是不会合作。 There should be a quick easy patch, right? 应该有一个快速简单的补丁,对吧? And so the bugs begin. 所以错误开始了。

The approach I came up with is to walk the tree backwards to the first previous non-empty node, clearing the empty nodes between to appease IE, and then evaluate a few conditions. 我想出的方法是将树向后移动到第一个非空节点,清除之间的空节点以安抚IE,然后评估一些条件。 If the caret should end up at the right side of the <span>, then do it manually (because IE won't) by creating a new range obj with the selection there at the end of the <span>. 如果插入符号应该最后位于<span>的右侧,则通过在<span>末尾创建一个新的范围obj,然后手动执行(因为IE不会)。

online demo 在线演示


I added an additional kludge for IE in the case that two spans are dragged against eachother. 在两个跨度相互拖拽的情况下,我为IE添加了额外的kludge。 For example, Field2Field3. 例如,Field2Field3。 When you then backspace from the right onto Field3, then backspace once again to delete it, IE would jump the caret leftward over Field2. 当你从右边退回到Field3上,然后再次退格以删除它时,IE会将插入符号向左跳过Field2。 Skip right over Field2. 跳过Field2。 grrr. 哎呀。 The workaround is to intercept that and insert a space between the pair of spans. 解决方法是拦截它并在这对跨距之间插入一个空格。 I wasn't confident you'd be happy with that. 我不相信你会对此感到高兴。 But, you know, it's a workaround. 但是,你知道,这是一种解决方法。 Anyway, that turned-up yet another bug, where IE changes the inserted space into two empty textnodes. 无论如何,这又出现了另一个错误,IE将插入的空间更改为两个空文本节点。 more grrr. 更多的。 And so a workaround for the workaround. 因此,解决方法的解决方法。 See the non-isCollapsed code. 请参阅非isCollapsed代码。

CODE SNIPPET 代码链

 var EditableDiv = document.getElementById('EditableDiv'); EditableDiv.onkeydown = function(event) { var ignoreKey; var key = event.keyCode || event.charCode; if (!window.getSelection) return; var selection = window.getSelection(); var focusNode = selection.focusNode, anchorNode = selection.anchorNode; var anchorOffset = selection.anchorOffset; if (!anchorNode) return if (anchorNode.nodeName.toLowerCase() != '#text') { if (anchorOffset < anchorNode.childNodes.length) anchorNode = anchorNode.childNodes[anchorOffset] else { while (!anchorNode.nextSibling) anchorNode = anchorNode.parentNode // this might step out of EditableDiv to "justincase" comment node anchorNode = anchorNode.nextSibling } anchorOffset = 0 } function backseek() { while ((anchorOffset == 0) && (anchorNode != EditableDiv)) { if (anchorNode.previousSibling) { if (anchorNode.previousSibling.nodeName.toLowerCase() == '#text') { if (anchorNode.previousSibling.nodeValue.length == 0) anchorNode.parentNode.removeChild(anchorNode.previousSibling) else { anchorNode = anchorNode.previousSibling anchorOffset = anchorNode.nodeValue.length } } else if ((anchorNode.previousSibling.offsetWidth == 0) && (anchorNode.previousSibling.offsetHeight == 0)) anchorNode.parentNode.removeChild(anchorNode.previousSibling) else { anchorNode = anchorNode.previousSibling while ((anchorNode.lastChild) && (anchorNode.nodeName.toUpperCase() != 'SPAN')) { if ((anchorNode.lastChild.offsetWidth == 0) && (anchorNode.lastChild.offsetHeight == 0)) anchorNode.removeChild(anchorNode.lastChild) else if (anchorNode.lastChild.nodeName.toLowerCase() != '#text') anchorNode = anchorNode.lastChild else if (anchorNode.lastChild.nodeValue.length == 0) anchorNode.removeChild(anchorNode.lastChild) else { anchorNode = anchorNode.lastChild anchorOffset = anchorNode.nodeValue.length //break //don't need to break, textnode has no children } } break } } else while (((anchorNode = anchorNode.parentNode) != EditableDiv) && !anchorNode.previousSibling) {} } } if (key == 8) { //backspace if (!selection.isCollapsed) { try { document.createElement("select").size = -1 } catch (e) { //kludge for IE when 2+ SPANs are back-to-back adjacent if (anchorNode.nodeName.toUpperCase() == 'SPAN') { backseek() if (anchorNode.nodeName.toUpperCase() == 'SPAN') { var k = document.createTextNode(" ") // doesn't work here between two spans. IE makes TWO EMPTY textnodes instead ! anchorNode.parentNode.insertBefore(k, anchorNode) // this works anchorNode.parentNode.insertBefore(anchorNode, k) // simulate "insertAfter" } } } } else { backseek() if (anchorNode == EditableDiv) ignoreKey = true else if (anchorNode.nodeName.toUpperCase() == 'SPAN') { SelectText(event, anchorNode) ignoreKey = true } else if ((anchorNode.nodeName.toLowerCase() == '#text') && (anchorOffset <= 1)) { var prev, anchorNodeSave = anchorNode, anchorOffsetSave = anchorOffset anchorOffset = 0 backseek() if (anchorNode.nodeName.toUpperCase() == 'SPAN') prev = anchorNode anchorNode = anchorNodeSave anchorOffset = anchorOffsetSave if (prev) { if (anchorOffset == 0) SelectEvent(prev) else { var r = document.createRange() selection.removeAllRanges() if (anchorNode.nodeValue.length > 1) { r.setStart(anchorNode, 0) selection.addRange(r) anchorNode.deleteData(0, 1) } else { for (var i = 0, p = prev.parentNode; true; i++) if (p.childNodes[i] == prev) break r.setStart(p, ++i) selection.addRange(r) anchorNode.parentNode.removeChild(anchorNode) } } ignoreKey = true } } } } if (ignoreKey) { var evt = event || window.event; if (evt.stopPropagation) evt.stopPropagation(); evt.preventDefault(); return false; } } function SelectText(event, element) { var range, selection; EditableDiv.focus(); if (window.getSelection) { selection = window.getSelection(); range = document.createRange(); range.selectNode(element) selection.removeAllRanges(); selection.addRange(range); } else { range = document.body.createTextRange(); range.moveToElementText(element); range.select(); } var evt = (event) ? event : window.event; if (evt.stopPropagation) evt.stopPropagation(); if (evt.cancelBubble != null) evt.cancelBubble = true; return false; } 
 #EditableDiv { height: 75px; width: 500px; font-family: Consolas; font-size: 10pt; font-weight: normal; letter-spacing: 1px; background-color: white; overflow-y: scroll; overflow-x: hidden; border: 1px solid black; padding: 5px; } #EditableDiv span { color: brown; font-family: Verdana; font-size: 8.5pt; min-width: 10px; /*_width: 10px;*/ /* what is this? */ } #EditableDiv p, #EditableDiv br { display: inline; } 
 <div id="EditableDiv" contenteditable="true"> &nbsp;(<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field1</span> < 500) <span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>OR</span> (<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field2</span> > 100 <span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>AND</span> (<span contenteditable='false' onclick='SelectText(event, this);' unselectable='on'>Field3</span> <= 200) ) </div> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM