Following solution doesn't work for IE, you'll need to apply TextRange objects etc. for that. As this uses selections to perform this, it shouldn't break the HTML in normal cases, for example:
<div>abcd<span>efg</span>hij</div>
With highlight(3,6);
outputs:
<div>abc<em>d<span>ef</span></em><span>g</span>hij</div>
Take note how it wraps the first character outside of the span into an em
, and then the rest within the span
into a new one. Where as if it would just open it at character 3 and end at character 6, it would give invalid markup like:
<div>abc<em>d<span>ef</em>g</span>hij</div>
The code:
var r = document.createRange();
var s = window.getSelection()
r.selectNode($('div')[0]);
s.removeAllRanges();
s.addRange(r);
// not quite sure why firefox has problems with this
if ($.browser.webkit) {
s.modify("move", "backward", "documentboundary");
}
function highlight(start,end){
for(var st=0;st<start;st++){
s.modify("move", "forward", "character");
}
for(var st=0;st<(end-start);st++){
s.modify("extend", "forward", "character");
}
}
highlight(2,6);
var ra = s.getRangeAt(0);
var newNode = document.createElement("em");
newNode.appendChild(ra.extractContents());
ra.insertNode(newNode);
Example: http://jsfiddle.net/niklasvh/4NDb9/
edit Looks like at least my FF4 had some issues with
s.modify("move", "backward", "documentboundary");
but at the same time, it seems to work without it, so I just changed it to
if ($.browser.webkit) {
s.modify("move", "backward", "documentboundary");
}
edit
as Tim Pointed out, modify is only available from FF4 onwards, so I took a different approach to getting the selection, which doesn't need the modify method, in hopes in making it a bit more browser compatible (IE still needs its own solution).
The code:
var r = document.createRange();
var s = window.getSelection()
var pos = 0;
function dig(el){
$(el).contents().each(function(i,e){
if (e.nodeType==1){
// not a textnode
dig(e);
}else{
if (pos<start){
if (pos+e.length>=start){
range.setStart(e, start-pos);
}
}
if (pos<end){
if (pos+e.length>=end){
range.setEnd(e, end-pos);
}
}
pos = pos+e.length;
}
});
}
var start,end, range;
function highlight(element,st,en){
range = document.createRange();
start = st;
end = en;
dig(element);
s.addRange(range);
}
highlight($('div'),3,6);
var ra = s.getRangeAt(0);
var newNode = document.createElement("em");
newNode.appendChild(ra.extractContents());
ra.insertNode(newNode);
example: http://jsfiddle.net/niklasvh/4NDb9/