Reputation: 817
I'm trying to make a contenteditable div and when it was pasted by the HTML words, I hope that it could be transferred into plain text and the caret could jump to the end of the pasted HTML words automatically.
I have tried to make it, and below is my code I have typed.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Test</title>
</head>
<body>
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script>
function stripHTML(input){
var output = '';
if(typeof(input) == "string"){
output = input.replace(/(<([^>]+)>)/ig, "");
}
return output;
}
$(function(){
$("#text").focus(function(event){
setItv = setInterval('clearHTML()', 1);
});
$("#text").blur(function(event){
clearInterval(setItv);
});
});
function clearHTML(){
var patt = /(<([^>]+)>)/ig;
var input = $("#text").html();
if(input.search(patt) >= 0){
var output = '';
if(typeof(input) == "string"){
output = stripHTML(input);
}
$("#text").html(output);
}
}
</script>
<div contenteditable="true" style="border: 1px #000 solid; width: 300px;
height: 100px;" id="text">Hello, world!</div>
</body>
</html>
The biggest issue is the cursor placement. After pasting the HTML in the middle of the original text (e.g., paste between “Hello” and “world”), the caret would jump to the end of the whole text not the end of the pasted HTML. Normally, when we paste a snippet of words in the middle of the original text, the caret would jump to the end of the pasted words, not the end of the whole text. But in this case I don't know why it jumps to the end of the whole text automatically.
And the secondary issue is that the setInterval function. Maybe it doesn't cause any problem, but the way of scripting is extremely unprofessional and it may impact the efficiency of the program.
Upvotes: 3
Views: 1669
Reputation: 324567
As a starting point, I would suggest the following improvements:
innerHTML
property. This will improve performance because innerHTML
is slow. You could do this using the standard DOM method getElementsByTagName()
. If you prefer to use jQuery, which will be slightly less efficient, you could use $("#text").find("*")
. Even simpler and faster would be to check that your editable element has a single child which is a text node.textContent
property. This will more reliable than replacing HTML tags with a regular expression.keypress
, keyup
, input
and paste
for starters). I'd keep the setInterval()
with a less frequent interval to deal with other cases not covered by those eventsThe following snippet contains all these improvements:
var saveSelection, restoreSelection;
if (window.getSelection && document.createRange) {
saveSelection = function(containerEl) {
var range = window.getSelection().getRangeAt(0);
var preSelectionRange = range.cloneRange();
preSelectionRange.selectNodeContents(containerEl);
preSelectionRange.setEnd(range.startContainer, range.startOffset);
var start = preSelectionRange.toString().length;
return {
start: start,
end: start + range.toString().length
};
};
restoreSelection = function(containerEl, savedSel) {
var charIndex = 0, range = document.createRange();
range.setStart(containerEl, 0);
range.collapse(true);
var nodeStack = [containerEl], node, foundStart = false, stop = false;
while (!stop && (node = nodeStack.pop())) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
stop = true;
}
charIndex = nextCharIndex;
} else {
var i = node.childNodes.length;
while (i--) {
nodeStack.push(node.childNodes[i]);
}
}
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
} else if (document.selection) {
saveSelection = function(containerEl) {
var selectedTextRange = document.selection.createRange();
var preSelectionTextRange = document.body.createTextRange();
preSelectionTextRange.moveToElementText(containerEl);
preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
var start = preSelectionTextRange.text.length;
return {
start: start,
end: start + selectedTextRange.text.length
}
};
restoreSelection = function(containerEl, savedSel) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(containerEl);
textRange.collapse(true);
textRange.moveEnd("character", savedSel.end);
textRange.moveStart("character", savedSel.start);
textRange.select();
};
}
$(function(){
var setInv;
var $text = $("#text");
$text.focus(function(event){
setItv = setInterval(clearHTML, 200);
});
$text.blur(function(event){
clearInterval(setItv);
});
$text.on("paste keypress keyup input", function() {
// Allow a short delay so that paste and keypress events have completed their default action bewfore stripping HTML
setTimeout(clearHTML, 1)
});
});
function clearHTML() {
var $el = $("#text");
var el = $el[0];
if (el.childNodes.length != 1 || el.firstChild.nodeType != 3 /* Text node*/) {
var savedSel = saveSelection(el);
$el.text( $el.text() );
restoreSelection(el, savedSel);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div contenteditable="true" style="border: 1px #000 solid; width: 300px;
height: 100px;" id="text">Hello, world!</div>
Upvotes: 2