Set cursor position in content-editable div

Summary

I try to achieve an effect when the user types ( or [ into the editable content div , second ) or ] automatically inserted, and the caret should be located between two of them, that is, between ( and ) .


Fiddle

Type to the right of -- and see how it works in the first line, until it works in the second.


My effort:

I use this code ( Tim Down ) to select part of the text and set the cursor position. The former works, but the latter does not :(

 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 children (mostly <br> s).
  • Only Chrome support required using vanilla JS

My question is:

  • Why is this feature not working?
  • How can this be made to work?

I spent hours searching for this and did not find anything useful. Some of them related to setting at the beginning or end of a child div , but for me it can be anywhere, anywhere.

UPDATE

Thanks to everyone, finally, the finished development !

+7
javascript html google-chrome caret contenteditable
source share
2 answers

This is a much simpler approach. There are a few things to note:

  • keypress is the only key event in which you can reliably determine which character was entered. keyup and keydown will not execute.
  • The code handles the insertion of brackets / brackets manually, preventing the default action of the keypress event.
  • Selection / carriage elements are simple when using DOM methods.
  • This will not work in IE <= 8, which have different range and selection APIs. If you need support for these browsers, I would suggest using my own Rangy library. It is possible without it, but I really do not want to write additional code.

Demo:

http://jsfiddle.net/HPeb2/

the 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); 
+10
source share

To complete the task specified in the summary, try changing the node value at the current cursor position. Since your code is attached to the keyup event, you can be sure that the range is already minimized by the node text (all this happens when you press a key that has already been started).

 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

In addition, I see that you used innerHTML to set a new value (in Element.prototype.setText ). That sounds alarming! innerHTML completely destroys all contents previously in the container. Since the cursor is bound to a specific element, and these elements have just received a nuk, what should the browser do? Try to avoid using this if you don't care about where your cursor ends at all.

Regarding the highlightText problem, it's hard to say why it is broken. Your violin does not show that it is used anywhere, and I will need to see its use for further diagnosis. However, I have an idea of ​​what might go wrong:

I think you should look carefully at getCaretPosition . You treat it as if it were returning the cursor position, but that’s not what it does. Remember that in the browser, your cursor position is always a range. It always has a beginning and an end. Sometimes, when a range falls, the beginning and end are the same. However, the idea that you can get the cursor position and view it as one point is a dangerous simplification.

getCaretPosition has another problem. For your editable div it does this:

  • Selects all text in a new range.
  • Resets the end position of the new range to the equal end position of the cursor (so that all text to the end position of the cursor is selected).
  • Calls toString() and returns the length of the resulting string.

As you noticed, some elements (e.g. <br /> ) affect the results of toString() . Some elements (e.g. <span></span> ) do not work. To correctly perform this calculation, you need to push it for some types of elements, and not for others. It will be dirty and difficult. If you need a number that you can pass into highlightText , and let it work properly, your current getCaretPosition unlikely to be useful.

Instead, I think you should try working directly with the start and end points of the cursor as two separate locations and update highlightText accordingly. Cancel the current getCaretPosition and use your own browser concepts range.startContainer , range.startOffset , range.endContainer and range.endOffset .

+3
source share

All Articles