Trying to modify the text of a document onkeydown is inserting text incorrectly
I have the following html:
<html>
<head>
<script>
function myKeyDown()
{
var myDiv = document.getElementById('myDiv');
myDiv.innerHTML = myDiv.innerHTML.replace(/(@[a-z0-9_]+)/gi, '<strong>$1</strong>');
}
function init()
{
document.addEventListener('keydown', myKeyDown, false);
document.designMode = "on";
}
window.onload = init;
</script>
</head>
<body>
&l开发者_运维技巧t;div id="myDiv">
This is my variable name: @varname. If I type here things go wrong...
</div>
</body>
</html>
My goal is to do a kind of syntax highlighting on edit, to highlight variable names that begin with an @ symbol. However, when I edit the document's body, the function runs but the cursor is automatically shifted to the beginning of the body before the keystroke is performed.
My hypothesis is that the keypress event is trying to insert the new character at a specified index, but when I run the replace function the indices get messed up so it defaults the character insertion point to the beginning.
I'm using Firefox to test by the way.
Any help would be appreciated.
Thanks, B.J.
Your hypothesis is correct. Doing the replacement using innerHTML
means that the browser has to throw away all the nodes inside your div and create new ones from the HTML string you provide, meaning the nodes the selection previously existed in no longer exist. It's very inefficient, particularly to be doing this on every keypress. You need to do this using DOM methods instead.
I'd suggest waiting for a period of keyboard inactivity before doing any substitutions rather than doing it after every keypress.
Finally, which browsers does this need to work in?
UPDATE
I decided this was an interesting problem, so I've written a full solution. I also changed my mind about using innerHTML
. The complicated bit is the completely different method you have to use for saving and restoring the selection in IE from all other browsers. I also changed it to use contenteditable
rather than designMode
. This rules out Firefox 2, but it simplified some things so I hope that's OK.
<script type="text/javascript">
function getBoundary(el, textNodes, charIndex) {
var charsSoFar = 0, textNodeLength;
// Walk text nodes
for (var i = 0, len = textNodes.length; i < len; ++i) {
textNodeLength = textNodes[i].data.length;
charsSoFar += textNodeLength;
if (charsSoFar >= charIndex) {
return {
node: textNodes[i],
offset: charIndex + textNodeLength - charsSoFar
};
}
}
throw new Error("Boundary not found");
}
function highlightVars() {
var myDiv = document.getElementById('myDiv');
var selectedRange, range, divText;
var selectionStartPos, selectionLength;
var hasRanges = !!(window.getSelection
&& document.createRange);
var hasTextRanges = !!(document.selection
&& document.body.createTextRange);
// Get the selection text position within the div
if (hasRanges) {
selectedRange = window.getSelection().getRangeAt(0);
range = document.createRange();
range.selectNodeContents(myDiv);
divText = range.toString();
range.setEnd(selectedRange.startContainer, selectedRange.startOffset);
selectionStartPos = range.toString().length;
selectionLength = selectedRange.toString().length;
} else if (hasTextRanges) {
selectedRange = document.selection.createRange();
range = document.body.createTextRange();
range.moveToElementText(myDiv);
divText = range.text;
range.setEndPoint("EndToStart", selectedRange);
selectionStartPos = range.text.length;
selectionLength = selectedRange.text.length;
}
// Substitute in existing text with vars highlighted
myDiv.innerHTML = divText.replace(/(@[a-z0-9_]+)/gi,
'<strong>$1</strong>').replace(/ $/, "\u00a0");
// Restore selection
if (hasRanges) {
var textNodes = [];
for (var n = myDiv.firstChild; n; n = n.nextSibling) {
textNodes.push( (n.nodeType == 1) ? n.firstChild : n );
}
var selectionStartBoundary = getBoundary(myDiv, textNodes,
selectionStartPos);
var selectionEndBoundary = getBoundary(myDiv, textNodes,
selectionStartPos + selectionLength);
range.setStart(selectionStartBoundary.node,
selectionStartBoundary.offset);
range.setEnd(selectionEndBoundary.node,
selectionEndBoundary.offset);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (hasTextRanges) {
range.moveToElementText(myDiv);
range.moveStart("Character", selectionStartPos);
range.collapse();
range.moveEnd("Character", selectionLength);
range.select();
}
}
var keyTimer;
function myKeyDown() {
if (keyTimer) {
window.clearTimeout(keyTimer);
}
keyTimer = window.setTimeout(function() {
keyTimer = null;
highlightVars();
}, 1000);
}
function init() {
var myDiv = document.getElementById("myDiv");
myDiv.onkeydown = myKeyDown;
}
window.onload = init;
</script>
<body>
<div id="myDiv" contenteditable="true">This is my variable
name: @varname. If I type here things go wrong...</div>
</body>
精彩评论