html - Selection and deletion problems with Non-Editable Span inside ContentEditable DIV -
i have div contenteditable set true. inside there several spans contenteditable set false.
i trap backspace key if element under cursor <span> can delete it.
the problem works alternately odd spans only.
so, example, html code below, put cursor @ end of text in div, , press backspace way till beginning of div. observe select/delete first span, leave second, select/delete third span, leave fourth , on.
this behavior on internet explorer. works expected on firefox.
how should make behavior consistant in internet explorer?
following html code can used reproduce behavior:
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"> (<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. have asked question in msdn forum well.
the challenge ie11 backspace right directly against <span>. next backspace select , highlight it. seems such simple objective, ie11 won't cooperate. there should quick easy patch, right? , bugs begin.
the approach came walk tree backwards first previous non-empty node, clearing empty nodes between appease ie, , evaluate few conditions. if caret should end @ right side of <span>, manually (because ie won't) creating new range obj selection there @ end of <span>.
online demo
i added additional kludge ie in case 2 spans dragged against eachother. example, field2field3. when backspace right onto field3, backspace once again delete it, ie jump caret leftward on field2. skip right on field2. grrr. workaround intercept , insert space between pair of spans. wasn't confident you'd happy that. but, know, it's workaround. anyway, turned-up yet bug, ie changes inserted space 2 empty textnodes. more grrr. , workaround workaround. see non-iscollapsed code.
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 // might step out of editablediv "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 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 ie when 2+ spans back-to-back adjacent if (anchornode.nodename.touppercase() == 'span') { backseek() if (anchornode.nodename.touppercase() == 'span') { var k = document.createtextnode(" ") // doesn't work here between 2 spans. ie makes 2 empty textnodes instead ! anchornode.parentnode.insertbefore(k, anchornode) // 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 { (var = 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;*/ /* this? */ } #editablediv p, #editablediv br { display: inline; } <div id="editablediv" contenteditable="true"> (<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>
Comments
Post a Comment