Summary:
I am trying to achieve the effect where when user types a (
or [
in the content-editable div
, the second )
or ]
is auto-inserted, and the caret be positioned between the two of them, that is, between (
and )
.
FIDDLE
Type to the right of the --
s and see how in the first line it works while doesn't work in the second.
My effort:
I am using this code (by Tim Down) to both highlight some part of text and set cursor position. The former works but latter doesn't :(
function getTextNodesIn(node) { // helper
var textNodes = [];
if (node.nodeType == 3) {
textNodes.push(node);
} else {
var children = node.childNodes;
for (var i = 0, len = children.length; i < len; ++i) {
textNodes.push.apply(textNodes, getTextNodesIn(children[i]));
}
}
return textNodes;
}
function highlightText(el, start, end) { // main
if (el.tagName === "DIV") { // content-editable div
var range = document.createRange();
range.selectNodeContents(el);
var textNodes = getTextNodesIn(el);
var foundStart = false;
var charCount = 0,
endCharCount;
for (var i = 0, textNode; textNode = textNodes[i++];) {
endCharCount = charCount + textNode.length;
if (!foundStart && start >= charCount && (start < endCharCount || (start == endCharCount && i < textNodes.length))) {
range.setStart(textNode, start - charCount);
foundStart = true;
}
if (foundStart && end <= endCharCount) {
range.setEnd(textNode, end - charCount);
break;
}
charCount = endCharCount;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else { // textarea
el.selectionStart = start;
el.selectionEnd = end;
}
}
Notes:
<div>
will have child elements (mostly <br>
s).My question:
I have spent hours searching for this and found nothing much useful. Some were about setting at start or end of a child div
but for me it can be any location, anywhere.
UPDATE:
Thanks to everyone is finally finished development!
Here's a much simpler approach. There are a few things to note:
keypress
is the only key event in which you can reliably detect which character has been typed. keyup
and keydown
won't do.keypress
event.Demo:
http://jsfiddle.net/HPeb2/
Code:
var editableEl = document.getElementById("editable");
editableEl.addEventListener("keypress", function(e) {
var charTyped = String.fromCharCode(e.which);
if (charTyped == "{" || charTyped == "(") {
// Handle this case ourselves
e.preventDefault();
var sel = window.getSelection();
if (sel.rangeCount > 0) {
// First, delete the existing selection
var range = sel.getRangeAt(0);
range.deleteContents();
// Insert a text node at the caret containing the braces/parens
var text = (charTyped == "{") ? "{}" : "()";
var textNode = document.createTextNode(text);
range.insertNode(textNode);
// Move the selection to the middle of the inserted text node
range.setStart(textNode, 1);
range.setEnd(textNode, 1);
sel.removeAllRanges();
sel.addRange(range);
}
}
}, false);
To accompish the goal stated in your summary, try altering the node value at the current cursor position. Since your code is attached to a keyup
event, you can be assured that the range is already collapsed and on a text node (all of that happens on key down, which would have already fired).
function insertChar(char) {
var range = window.getSelection().getRangeAt(0);
if (range.startContainer.nodeType === Node.TEXT_NODE) {
range.startContainer.insertData(range.startOffset, char);
}
}
function handleKeyUp(e) {
e = e || window.event;
var char, keyCode;
keyCode = e.keyCode;
char =
(e.shiftKey ? {
"222": '"',
"57": ')',
"219": '}'
} : {
"219": "]"
})[keyCode] || null;
if (char) {
insertChar(char);
}
}
document.getElementById("editable").onkeyup = handleKeyUp;
Fiddle
Also, I see that you were using innerHTML
to set the new value (in Element.prototype.setText
). This sounds alarming to me! innerHTML
totally nukes whatever contents were previously in the container. Since the cursor is bound to a particular element, and those elements just got nuked, what is the browser supposed to do? Try to avoid using this if you care at all about where your cursor ends up afterwards.
As for the highlightText
issue, it's hard to say why it is broken. Your fiddle does not show it being used anywhere, and I would need to see its usage to further diagnose it. However, I have an idea about what might be going wrong:
I think you should take a close look at getCaretPosition
. You are treating this as though it returns the cursor position, but that isn't what it does. Remember, to a browser, your cursor position is always a range. It always has a beginning and an end. Sometimes, when the range is collapsed, the beginning and the end are the same point. However, the idea that you could get a cursor position and treat it as a single point is a dangerous oversimplification.
getCaretPosition
has another issue. For your editable div
, it does this:
toString()
and returns the length of the resulting string.As you have noted, some elements (such as <br />
) affect the results of toString()
. Some elements (such as <span></span>
) do not. In order to get this calculation right, you'll need to nudge it for some element types and not for others. This is going to be messy and complicated. If you want a number that you can feed into highlightText
and have it work as expected, your current getCaretPosition
is unlikely to be helpful.
Instead, I think you should try working directly with the cursor's start and end points as two separate locations, and update highlightText
accordingly. Discard the current getCaretPosition
and use the browser's native concepts of range.startContainer
, range.startOffset
, range.endContainer
, and range.endOffset
directly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With