Toggle any tag just like execCommand bold does with b
I have a contenteditable div and need to be able to wrap / unfold text in other tags, for example <code>
, etc. Wrapping them is not a problem, rather expanding the selected text, for example when there is <b>hello </b><code>world</code>
and I only need to expand the letters in the middle (orl), there seems to be no easy way to detect if the selected text is inside <code>
or not. So while document.execCommand("bold")
doing this wrapping / expanding operation with tags <b>
and <i>
, is there a way to do the same with tags other than b, i, u?
source to share
So after a lot of searching for a solution and looking for nothing, I decided to try and write it myself, so if anyone finds this piece useful, I would be glad. I don't know how it works in IE or Safari, and for sure it's not the most efficient way to do it, but for most browsers it should do the job.
<!doctype html>
<html>
<head>
<title>Wrap</title>
<meta charset="utf-8">
</head>
<body>
<script>
function traverseParentCE(el){
var parent = el.parentElement;
if(!parent || (el && el.contentEditable=="true")) return el;
while(parent && parent.contentEditable!="true"){
parent = parent.parentElement;
}
return parent;
}
function finishWrap(el, innHtml, marker, wrapper){
el.innerHTML = innHtml.replace("<"+marker+">","").replace("</"+marker+">","");
var node = el.getElementsByTagName(wrapper)[0];
var range = document.createRange();
range.setStart(node, 0);
range.setEnd(node, 1);
var sel = document.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function wrap(tag){
var marker = "marker";
var wrapper = "inncnt";
var ae = document.activeElement;
if(ae.contentEditable!="true") return;
var sel = document.getSelection();
var range = sel.getRangeAt(0);
var el = document.createElement(marker);
el.appendChild(range.cloneContents());
range.deleteContents();
range.insertNode(el);
var innHtml = el.innerHTML;
var count = 0;
while(true){ // I know right, this kind of replacing is horrible, but RegExp somehow didn't work for me
var pos = innHtml.indexOf("<"+tag+">");
if(pos==-1) break;
innHtml = innHtml.replace("<"+tag+">","").replace("</"+tag+">","");
count++;
}
innHtml.replace("<"+wrapper+">","").replace("</"+wrapper+">","");
el.innerHTML = "<"+wrapper+">" + innHtml + "</"+wrapper+">";
var container = traverseParentCE(range.commonAncestorContainer);
innHtml = container.innerHTML;
if(count>0){
return finishWrap(container, innHtml, marker, wrapper);
}
var pos = innHtml.indexOf("<"+marker+">");
var contentBefore = innHtml.substr(0, pos);
var lastStarting = contentBefore.indexOf("<"+tag+">");
var lastEnding = contentBefore.indexOf("</"+tag+">");
var wrap = false;
if(lastStarting == -1) wrap = true;
else if(lastStarting < lastEnding) wrap = true;
if(wrap) innHtml = innHtml.replace(marker, tag).replace(marker, tag);
else innHtml = innHtml.replace(marker, "/"+tag).replace("/"+marker,tag);
var doublet = "<"+tag+"></"+tag+">";
while(true){
var pos = innHtml.indexOf(doublet);
if(pos==-1) break;
innHtml = innHtml.replace(doublet, "");
}
return finishWrap(container, innHtml, marker, wrapper);
}
</script>
<button onmousedown="wrap('b'); return false;" onmouseup="return false;" onclick="return false;">B</button>
<button onmousedown="wrap('i'); return false;" onmouseup="return false;" onclick="return false;">I</button>
<button onmousedown="wrap('code'); return false;" onmouseup="return false;" onclick="return false;">Code</button>
<div style="width: 800px; height: 300px; background-color: yellow" contenteditable="true" id="out">
Hello <b>World</b>! This <code>is some</code> sentence.
</div>
</body>
</html>
source to share
Modifying contenteditable divs is not an easy task. What you are asking for is possible, but too big a topic and there are not a few examples here.
However, there are many ready-made libraries that have done this before (for example CKEditor . They are very complex but also very flexible. Out of the box they probably do a lot more than you need, but can be customized so that functions you don't need , can be disabled, and they have an API that allows you to control them externally if needed.
source to share