Establecer la posición del cursor en div editable por contenido

Resumen :

Estoy tratando de lograr el efecto cuando el usuario escribe a ( o [ en el div editable por contenido, el segundo ) o ] se inserta automáticamente, y el cursor se coloca entre los dos, es decir, entre ( y ) .


VIOLÍN

Escriba a la derecha de la -- s y vea cómo en la primera línea funciona mientras que no funciona en la segunda.


Mi esfuerzo:

Estoy usando este código (por Tim Down ) para resaltar alguna parte del texto y establecer la posición del cursor. El primero funciona pero el último no 🙁

 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 = 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; } } 

Notas:

  1. tendrá elementos secundarios (mayormente en s).
  2. Solo se requiere soporte de Chrome usando vanilla JS

Mi pregunta:

  1. ¿Por qué no funciona la función anterior?
  2. ¿Cómo se puede hacer que funcione?

Pasé horas buscando esto y no encontré nada muy útil. Algunos trataban de establecer al principio o al final de un div infantil, pero para mí puede ser cualquier ubicación, en cualquier lugar.

ACTUALIZAR :

¡Gracias a todos, finalmente se terminó el desarrollo !

Aquí hay un enfoque mucho más simple. Hay algunas cosas a tener en cuenta:

  • keypress es el único evento clave en el que puede detectar de manera confiable qué carácter se ha tipeado. keyup y keydown no funcionarán.
  • El código maneja la inserción de paréntesis / llaves manualmente al evitar la acción predeterminada del evento de keypress de keypress .
  • El material de selección / intercalación es simple cuando se usan métodos DOM.
  • Esto no funcionará en IE <= 8, que tienen diferentes rangos y API de selección. Si necesita soporte para esos navegadores, sugeriría usar mi propia biblioteca Rangy . Es posible sin él, pero realmente no quiero escribir el código adicional.

Manifestación:

http://jsfiddle.net/HPeb2/

Código:

 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); 

Para lograr el objective establecido en su resumen, intente alterar el valor del nodo en la posición actual del cursor. Dado que su código está adjunto a un evento de keyup , puede estar seguro de que el rango ya está contraído y en un nodo de texto (todo eso sucede al keyup tecla, que ya se habría disparado).

 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; 

Violín

Además, veo que estaba usando innerHTML para establecer el nuevo valor (en Element.prototype.setText ). ¡Esto me parece alarmante! innerHTML totalmente los contenidos que estaban previamente en el contenedor. Dado que el cursor está ligado a un elemento en particular, y esos elementos solo se nukearon, ¿qué se supone que debe hacer el navegador? Intenta evitar usar esto si te preocupa en absoluto dónde termina el cursor.

En cuanto al tema de highlightText , es difícil decir por qué está roto. Su violín no muestra que se esté usando en ningún lado, y necesitaría ver su uso para diagnosticarlo aún más. Sin embargo, tengo una idea sobre lo que podría estar yendo mal:

Creo que deberías echarle un vistazo a getCaretPosition . Está tratando esto como si devuelve la posición del cursor, pero eso no es lo que hace. Recuerde, para un navegador, la posición de su cursor siempre es un rango. Siempre tiene un comienzo y un final. Algunas veces, cuando el rango se contrae, el comienzo y el final son el mismo punto. Sin embargo, la idea de que podría obtener una posición del cursor y tratarla como un único punto es una simplificación excesiva peligrosa.

getCaretPosition tiene otro problema. Para su div editable, hace esto:

  1. Selecciona todo el texto en un nuevo rango
  2. Restablece la posición final del nuevo rango para igualar la posición final del cursor (por lo que se selecciona todo el texto hasta la posición final del cursor).
  3. Llama a toString() y devuelve la longitud de la cadena resultante.

Como ha notado, algunos elementos (como
) afectan los resultados de toString() . Algunos elementos (como ) no lo hacen. Para que este cálculo sea correcto, tendrá que empujarlo para algunos tipos de elementos y no para otros. Esto va a ser complicado y complicado. Si desea un número que pueda incluir en highlightText y que funcione como se espera, es probable que su getCaretPosition actual no sea útil.

En cambio, creo que debería intentar trabajar directamente con los puntos de inicio y fin del cursor como dos ubicaciones separadas y actualizar highlightText consecuencia. Descarte la actual getCaretPosition y use los conceptos nativos del navegador de range.startContainer , range.startOffset , range.endContainer , y range.endOffset directamente.