range - javascript contentEditable - wrap cross-tag selections -
i'm experimenting bit contenteditable
, encountered issue: have following js snippet
var range = document.getselection().getrangeat(0); var newnode = document.createelement("span"); newnode.classname = "customstyle"; range.surroundcontents(newnode);
and html fragment:
<ul> <li>the <b>only entry</b> of list</li> </ul> <p>some text here in paragraph</p>
the js code allows wrap current selection <span>
tag.
it works when selection includes whole html tags (e.g. selecting 'the entry of') not, of course, when selection includes 1 of endings (e.g. selecting 'entry' 'some', both included).
though i'm aware problem not trivial, i'm looking suggestions best approach. in advance!
the basic approach if you're interested in wrapping text parts is:
- get selection range
- for each range boundary, if lies in middle of text node, need split text node in 2 @ boundary , update range's boundary range stays in place (example code rangy)
- get text nodes within range (example code)
- surround each text node in
<span>
element - re-select range
this approach taken class applier module of rangy library.
i've created example, using code adapted rangy:
function getnextnode(node) { var next = node.firstchild; if (next) { return next; } while (node) { if ( (next = node.nextsibling) ) { return next; } node = node.parentnode; } } function getnodesinrange(range) { var start = range.startcontainer; var end = range.endcontainer; var commonancestor = range.commonancestorcontainer; var nodes = []; var node; // walk parent nodes start common ancestor (node = start.parentnode; node; node = node.parentnode) { nodes.push(node); if (node == commonancestor) { break; } } nodes.reverse(); // walk children , siblings start until end found (node = start; node; node = getnextnode(node)) { nodes.push(node); if (node == end) { break; } } return nodes; } function getnodeindex(node) { var = 0; while ( (node = node.previoussibling) ) { ++i; } return i; } function insertafter(node, precedingnode) { var nextnode = precedingnode.nextsibling, parent = precedingnode.parentnode; if (nextnode) { parent.insertbefore(node, nextnode); } else { parent.appendchild(node); } return node; } // note cannot use splittext() because bugridden in ie 9. function splitdatanode(node, index) { var newnode = node.clonenode(false); newnode.deletedata(0, index); node.deletedata(index, node.length - index); insertafter(newnode, node); return newnode; } function ischaracterdatanode(node) { var t = node.nodetype; return t == 3 || t == 4 || t == 8 ; // text, cdatasection or comment } function splitrangeboundaries(range) { var sc = range.startcontainer, = range.startoffset, ec = range.endcontainer, eo = range.endoffset; var startendsame = (sc === ec); // split end boundary if necessary if (ischaracterdatanode(ec) && eo > 0 && eo < ec.length) { splitdatanode(ec, eo); } // split start boundary if necessary if (ischaracterdatanode(sc) && > 0 && < sc.length) { sc = splitdatanode(sc, so); if (startendsame) { eo -= so; ec = sc; } else if (ec == sc.parentnode && eo >= getnodeindex(sc)) { ++eo; } = 0; } range.setstart(sc, so); range.setend(ec, eo); } function gettextnodesinrange(range) { var textnodes = []; var nodes = getnodesinrange(range); (var = 0, node, el; node = nodes[i++]; ) { if (node.nodetype == 3) { textnodes.push(node); } } return textnodes; } function surroundrangecontents(range, templateelement) { splitrangeboundaries(range); var textnodes = gettextnodesinrange(range); if (textnodes.length == 0) { return; } (var = 0, node, el; node = textnodes[i++]; ) { if (node.nodetype == 3) { el = templateelement.clonenode(false); node.parentnode.insertbefore(el, node); el.appendchild(node); } } range.setstart(textnodes[0], 0); var lasttextnode = textnodes[textnodes.length - 1]; range.setend(lasttextnode, lasttextnode.length); } document.onmouseup = function() { if (window.getselection) { var templateelement = document.createelement("span"); templateelement.classname = "highlight"; var sel = window.getselection(); var ranges = []; var range; (var = 0, len = sel.rangecount; < len; ++i) { ranges.push( sel.getrangeat(i) ); } sel.removeallranges(); // surround ranges in reverse document order prevent surrounding subsequent ranges messing already-surrounded ones = ranges.length; while (i--) { range = ranges[i]; surroundrangecontents(range, templateelement); sel.addrange(range); } } };
.highlight { font-weight: bold; color: red; }
select of text , highlighted: <ul> <li>the <b>only entry</b> of list</li> </ul> <p>some text here in paragraph</p> <ul> <li>the <b>only entry</b> of list</li> </ul> <p>some text here in paragraph</p> <ul> <li>the <b>only entry</b> of list</li> </ul> <p>some text here in paragraph</p>
Comments
Post a Comment