2011-09-16 30 views
10

Tengo un div satisfactorio que contiene el típico editor wysiwyg html (negrita, anclajes, listas).Contenteditable DIV: cómo puedo determinar si el cursor está al principio o al final del contenido

Necesito determinar si el cursor actual está, enKeyDown, al inicio y al final de la div. El motivo es que, en función de la posición del cursor y de la tecla presionada, podría fusionar este div con el div anterior en un espacio posterior, o crear un nuevo div siguiente en enter.

He estado jugueteando con los rangos, pero cuando trabajas con html dentro del elemento, las cosas se vuelven bastante complicadas.

Espero que esté pasando por alto alguna solución simple.

¿Hay una manera relativamente simple de determinar esto? Estoy abierto a usar una biblioteca como Rangy.

Gracias!

Editar: Estoy pensando en algo como lo siguiente:

$('.mycontenteditable').bind('keydown', handle_keydown) 

handle_keydown = function(e) { 

    range = window.getSelection().getRangeAt(0) 
    start_range = document.createRange() 
    start_range.selectNodeContents(this.firstChild) 
    start_range.collapse(true) // collapse to start 
    is_start = start_range.compareBoundaryPoints(Range.START_TO_START,range) 
    end_range = document.createRange() 
    end_range.selectNodeContents(this.lastChild) 
    end_range.collapse(false) 
    is_end = end_range.compareBoundaryPoints(Range.END_TO_END,range) 
} 

¿Voy a correr en cualquier problema extraño con algo como esto?

Respuesta

13

Así es como terminé resolviendo esto. La solución propuesta anteriormente funcionaba a veces pero había muchos casos extremos, así que terminé considerando cuánto texto había antes o después del cursor, y si eso era 0 caracteres, entonces estaba al principio o al final:

handle_keydown = function(e) { 
    // Get the current cusor position 
    range = window.getSelection().getRangeAt(0) 
    // Create a new range to deal with text before the cursor 
    pre_range = document.createRange(); 
    // Have this range select the entire contents of the editable div 
    pre_range.selectNodeContents(this); 
    // Set the end point of this range to the start point of the cursor 
    pre_range.setEnd(range.startContainer, range.startOffset); 
    // Fetch the contents of this range (text before the cursor) 
    this_text = pre_range.cloneContents(); 
    // If the text's length is 0, we're at the start of the div. 
    at_start = this_text.textContent.length === 0; 
    // Rinse and repeat for text after the cursor to determine if we're at the end. 
    post_range = document.createRange(); 
    post_range.selectNodeContents(this); 
    post_range.setStart(range.endContainer, range.endOffset); 
    next_text = post_range.cloneContents(); 
    at_end = next_text.textContent.length === 0; 
} 

Todavía no estoy del todo seguro de que haya otros casos extremos, ya que no estoy completamente seguro de cómo probar esto, ya que requiere interacción del mouse; probablemente haya una biblioteca para tratar esto en alguna parte.

+0

Esto debe ser bastante robusta, a excepción de IE <9, que no soporta objetos de Campo o de selección. –

+1

Tenga en cuenta que esta solución no tiene en cuenta las nuevas líneas. Si el único texto entre el cursor y el final es una serie de líneas nuevas, erróneamente indicará que el cursor está al final de la div. Las líneas vacías siempre se representan con '
', pero no se puede simplemente buscar '
' s en 'post_range.cloneContents() ', porque incluye la línea actual'
'si la línea actual es la última línea. Además, en Chrome 'cloneContents()' es exactamente '


' si está en la última línea Y si está en la penúltima línea y la última está vacía. Entonces, los rangos solos no pueden resolver este problema. –

15

Me gustaría utilizar un enfoque similar al suyo, excepto el uso del método toString() de Range objetos en lugar de cloneContents() para evitar la clonación innecesaria. Además, en IE < 9 (que no admite rangos), puede utilizar un enfoque similar con la propiedad text de TextRange.

Tenga en cuenta que esto tendrá problemas cuando haya saltos de línea iniciales y/o finales en el contenido porque el método toString() de un rango funciona igual que la propiedad textContent de un nodo y solo considera los nodos de texto, por lo tanto no tiene en cuenta saltos de línea implicados por <br> o elementos de bloque. Además, CSS no se tiene en cuenta: por ejemplo, se incluye texto dentro de los elementos que están ocultos a través de display: none.

He aquí un ejemplo:

Demostración en directo: http://jsfiddle.net/YA3Pu/1/

Código:

function getSelectionTextInfo(el) { 
    var atStart = false, atEnd = false; 
    var selRange, testRange; 
    if (window.getSelection) { 
     var sel = window.getSelection(); 
     if (sel.rangeCount) { 
      selRange = sel.getRangeAt(0); 
      testRange = selRange.cloneRange(); 

      testRange.selectNodeContents(el); 
      testRange.setEnd(selRange.startContainer, selRange.startOffset); 
      atStart = (testRange.toString() == ""); 

      testRange.selectNodeContents(el); 
      testRange.setStart(selRange.endContainer, selRange.endOffset); 
      atEnd = (testRange.toString() == ""); 
     } 
    } else if (document.selection && document.selection.type != "Control") { 
     selRange = document.selection.createRange(); 
     testRange = selRange.duplicate(); 

     testRange.moveToElementText(el); 
     testRange.setEndPoint("EndToStart", selRange); 
     atStart = (testRange.text == ""); 

     testRange.moveToElementText(el); 
     testRange.setEndPoint("StartToEnd", selRange); 
     atEnd = (testRange.text == ""); 
    } 

    return { atStart: atStart, atEnd: atEnd }; 
} 
Cuestiones relacionadas