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

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

我的DIV的ContentEditable设置为TRUE。在其中有几个跨度,其中ContentEditable设置为FALSE。

我捕获了BackSpace键,以便如果光标下的元素是<span>,我可以将其删除。

问题是它只能与奇数范围交替使用。

因此,例如,使用下面的html代码,将光标置于DIV中文本的末尾,然后一直按Backspace直到div的开头。观察到它将选择/删除第一个跨度,然后保留第二个,然后选择/删除第三个跨度,然后保留第四个,依此类推。

此行为仅在Internet Explorer上。它完全可以在Firefox上正常运行。

如何使行为在Internet Explorer中保持一致?

以下html代码可用于重现行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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;
}

1
2
  (<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) )

编辑

仅供参考。我也在MSDN论坛中也问过这个问题。


面临的挑战是使IE11从右侧直接相对于退格。然后,下一个退格键将其选中并突出显示。这似乎是一个简单的目标,但是IE11不会合作。应该有一个快速简单的补丁,对不对?我想出的方法是将树向后移到第一个先前的非空节点,清除它们之间的空节点以安抚IE,然后评估一些条件。如果插入符应位于的右侧,则可以手动创建它(因为IE不会),方法是创建一个新的范围obj,并在的末尾进行选择。在线演示我添加了一个如果两个跨度相互拖动,则IE会产生额外的冲突。例如,Field2Field3。然后,当您从右侧退格到Field3上,然后再次退格以删除它时,IE会将插入符号向左跳到Field2上。跳过Field2。 rr解决方法是拦截该错误,并在一对跨度之间插入一个空格。我不相信你会对此感到满意。但是,您知道,这是一种解决方法。无论如何,这又出现了另一个错误,即IE将插入的空间更改为两个空的textnode。更多grrr。因此,一种变通办法。请参阅非isCollapsed代码。

代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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\t\t\t\t\t\t\t//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;
       }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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;
        }
1
(<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) )