2010-09-01 4 views
7

Estoy tratando de extraer la selección exacta y la ubicación del cursor de un área de texto. Como de costumbre, lo que es fácil en la mayoría de los navegadores no está en IE.Document.selection.createRange de IE no incluye líneas en blanco iniciales o finales

estoy usando esto:

var sel=document.selection.createRange(); 
var temp=sel.duplicate(); 
temp.moveToElementText(textarea); 
temp.setEndPoint("EndToEnd", sel); 
selectionEnd = temp.text.length; 
selectionStart = selectionEnd - sel.text.length; 

que trabaja el 99% de las veces. El problema es que TextRange.text no devuelve caracteres de línea nuevos iniciales o finales. Entonces, cuando el cursor está en un par de líneas en blanco después de un párrafo, cede una posición al final del párrafo anterior, en lugar de la posición real del cursor.

por ejemplo:

the quick brown fox| <- above code thinks the cursor is here 

| <- when really it's here 

La única solución que se me ocurre es insertar un carácter temporal antes y después de la selección, agarra la selección real y luego quitar esos caracteres temporales de nuevo. Es un truco, pero en un experimento rápido parece que funcionará.

Pero antes me gustaría asegurarme de que no haya una manera más fácil.

Respuesta

12

estoy añadiendo otra respuesta desde mi anterior es Ya se está volviendo un tanto épico.

Esto es lo que considero la mejor versión hasta ahora: toma el enfoque de Bobince (mencionado en los comentarios de mi primera respuesta) y corrige las dos cosas que no me gustaban, que fueron las primeras se basa en TextRanges que se pierden fuera del textarea (lo que daña el rendimiento), y en segundo lugar la suciedad de tener que elegir un número gigante para el número de caracteres para mover el límite del rango.

function getSelection(el) { 
    var start = 0, end = 0, normalizedValue, range, 
     textInputRange, len, endRange; 

    if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") { 
     start = el.selectionStart; 
     end = el.selectionEnd; 
    } else { 
     range = document.selection.createRange(); 

     if (range && range.parentElement() == el) { 
      len = el.value.length; 
      normalizedValue = el.value.replace(/\r\n/g, "\n"); 

      // Create a working TextRange that lives only in the input 
      textInputRange = el.createTextRange(); 
      textInputRange.moveToBookmark(range.getBookmark()); 

      // Check if the start and end of the selection are at the very end 
      // of the input, since moveStart/moveEnd doesn't return what we want 
      // in those cases 
      endRange = el.createTextRange(); 
      endRange.collapse(false); 

      if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { 
       start = end = len; 
      } else { 
       start = -textInputRange.moveStart("character", -len); 
       start += normalizedValue.slice(0, start).split("\n").length - 1; 

       if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) { 
        end = len; 
       } else { 
        end = -textInputRange.moveEnd("character", -len); 
        end += normalizedValue.slice(0, end).split("\n").length - 1; 
       } 
      } 
     } 
    } 

    return { 
     start: start, 
     end: end 
    }; 
} 

var el = document.getElementById("your_textarea"); 
var sel = getSelection(el); 
alert(sel.start + ", " + sel.end); 
+0

Agradable. Me gusta la idea de usar la longitud del texto en lugar del número realmente grande en moveStart/moveEnd. –

+0

Ah, no vi esta. El resultado es 20-30ms ahora, excelente trabajo! –

+1

@Andy: He escrito un complemento jQuery que incluye esto. Todavía no está documentado y se ha agrupado con un proyecto tenuemente relacionado, pero funciona: http://code.google.com/p/rangy/downloads/detail?name=textinputs_jquery-src.js –

1

N.B. Por favor, consulte mi other answer para la mejor solución que puedo ofrecer. Lo dejo aquí por antecedentes.

He encontrado este problema y he escrito lo siguiente que funciona en todos los casos. En IE utiliza el método que sugirió para insertar temporalmente un carácter en el límite de selección, y luego usa document.execCommand("undo") para eliminar el carácter insertado y evitar que la inserción permanezca en la pila de deshacer. Estoy bastante seguro de que no hay una manera más fácil. Felizmente, IE 9 soportará las propiedades selectionStart y selectionEnd.

function getSelectionBoundary(el, isStart) { 
    var property = isStart ? "selectionStart" : "selectionEnd"; 
    var originalValue, textInputRange, precedingRange, pos, bookmark; 

    if (typeof el[property] == "number") { 
     return el[property]; 
    } else if (document.selection && document.selection.createRange) { 
     el.focus(); 
     var range = document.selection.createRange(); 

     if (range) { 
      range.collapse(!!isStart); 

      originalValue = el.value; 
      textInputRange = el.createTextRange(); 
      precedingRange = textInputRange.duplicate(); 
      pos = 0; 

      if (originalValue.indexOf("\r\n") > -1) { 
       // Trickier case where input value contains line breaks 

       // Insert a character in the text input range and use that as 
       // a marker 
       range.text = " "; 
       bookmark = range.getBookmark(); 
       textInputRange.moveToBookmark(bookmark); 
       precedingRange.setEndPoint("EndToStart", textInputRange); 
       pos = precedingRange.text.length - 1; 

       // Executing an undo command to delete the character inserted 
       // prevents this method adding to the undo stack. This trick 
       // came from a user called Trenda on MSDN: 
       // http://msdn.microsoft.com/en-us/library/ms534676%28VS.85%29.aspx 
       document.execCommand("undo"); 
      } else { 
       // Easier case where input value contains no line breaks 
       bookmark = range.getBookmark(); 
       textInputRange.moveToBookmark(bookmark); 
       precedingRange.setEndPoint("EndToStart", textInputRange); 
       pos = precedingRange.text.length; 
      } 
      return pos; 
     } 
    } 
    return 0; 
} 

var el = document.getElementById("your_textarea"); 
var startPos = getSelectionBoundary(el, true); 
var endPos = getSelectionBoundary(el, false); 
alert(startPos + ", " + endPos); 

ACTUALIZACIÓN

Basado en el enfoque propuesto por bobince en los comentarios, he creado los siguientes, que parece funcionar bien. Algunas notas:

  1. El enfoque de bobince es más simple y más corto.
  2. Mi enfoque es intrusivo: realiza cambios en el valor de la entrada antes de revertir esos cambios, aunque no hay un efecto visible de esto.
  3. Mi enfoque tiene la ventaja de mantener todas las operaciones dentro de la entrada. El enfoque de Bobince se basa en la creación de rangos que abarcan desde el inicio del cuerpo hasta la selección actual.
  4. Una consecuencia de 3. es que el rendimiento de bobince varía con la posición de la entrada dentro del documento mientras que el mío no. Mis simples pruebas sugieren que cuando la entrada está cerca del inicio del documento, el enfoque de Bobince es significativamente más rápido. Cuando la entrada es posterior a un fragmento significativo de HTML, mi enfoque es más rápido.

function getSelection(el) { 
    var start = 0, end = 0, normalizedValue, textInputRange, elStart; 
    var range = document.selection.createRange(); 
    var bigNum = -1e8; 

    if (range && range.parentElement() == el) { 
     normalizedValue = el.value.replace(/\r\n/g, "\n"); 

     start = -range.moveStart("character", bigNum); 
     end = -range.moveEnd("character", bigNum); 

     textInputRange = el.createTextRange(); 
     range.moveToBookmark(textInputRange.getBookmark()); 
     elStart = range.moveStart("character", bigNum); 

     // Adjust the position to be relative to the start of the input 
     start += elStart; 
     end += elStart; 

     // Correct for line breaks so that offsets are relative to the 
     // actual value of the input 
     start += normalizedValue.slice(0, start).split("\n").length - 1; 
     end += normalizedValue.slice(0, end).split("\n").length - 1; 
    } 
    return { 
     start: start, 
     end: end 
    }; 
} 

var el = document.getElementById("your_textarea"); 
var sel = getSelection(el); 
alert(sel.start + ", " + sel.end); 
+0

Esto parece una solución muy desordenado! ¿Hay alguna razón para preferir simplemente usar el método 'range.moveStart ('character', -10000000)' para determinar los límites de selección en un área de texto? Eso todavía tiene '\ r \ n' para arreglar, pero eso es relativamente simple en comparación. – bobince

+0

bobince: erm. No recuerdo el problema que pensé que existía con su sugerencia y ahora no puedo encontrar uno. Volvere a ti. –

+0

bobince: hmm. Parece que tienes razón. Estaba seguro de que había algún tipo de problema con las líneas vacías con su enfoque, pero parece que no lo es. –

1

La jugada de bazillion negativo parece funcionar perfectamente.

Esto es lo que terminó con:

var sel=document.selection.createRange(); 
var temp=sel.duplicate(); 
temp.moveToElementText(textarea); 
var basepos=-temp.moveStart('character', -10000000); 

this.m_selectionStart = -sel.moveStart('character', -10000000)-basepos; 
this.m_selectionEnd = -sel.moveEnd('character', -10000000)-basepos; 
this.m_text=textarea.value.replace(/\r\n/gm,"\n"); 

Gracias bobince - ¿cómo puedo votar hasta su respuesta cuando es sólo un comentario :(

+0

He investigado esto un poco más. Ver mi respuesta para mis conclusiones. Dos puntos acerca de lo que tienes allí: arrojará un error si lo usas en una entrada en lugar de un área de texto, y también las posiciones que devuelve son relativas a una pieza de texto que no es el valor real en la entrada: I piense que la posición de selección es conceptualmente una compensación dentro del valor de la entrada, que contiene '\ r \ n' para cada salto de línea en lugar de' \ n'. –

+0

Sí, es por eso que la función mencionada en http://stackoverflow.com/questions/1738808#1739088 devuelve las cadenas reales, corregidas para '\ r \ n', en lugar de índices en el valor; Supongo que lo mismo sucederá aquí con 'm_text'. – bobince

+0

Sí. Sin embargo, su ejemplo no devuelve las posiciones. –

1

Un plugin de jquery para obtener el índice de selección de inicio y fin en el área de texto. Los códigos javascript anteriores no funcionaron para IE7 e IE8 y dieron resultados muy inconsistentes, así que escribí este pequeño plugin jquery. Permite guardar temporalmente el índice de inicio y final de la selección y resaltar la selección en otro momento.

Un ejemplo de trabajo y una breve versión está aquí: http://jsfiddle.net/hYuzk/3/

Una versión más detalles con comentarios etc. está aquí: http://jsfiddle.net/hYuzk/4/

 // Cross browser plugins to set or get selection/caret position in textarea, input fields etc for IE7,IE8,IE9, FF, Chrome, Safari etc 
     $.fn.extend({ 
      // Gets or sets a selection or caret position in textarea, input field etc. 
      // Usage Example: select text from index 2 to 5 --> $('#myTextArea').caretSelection({start: 2, end: 5}); 
      //    get selected text or caret position --> $('#myTextArea').caretSelection(); 
      //    if start and end positions are the same, caret position will be set instead o fmaking a selection 
      caretSelection : function(options) 
      { 
      if(options && !isNaN(options.start) && !isNaN(options.end)) 
      { 
      this.setCaretSelection(options); 
      } 
      else 
      { 
      return this.getCaretSelection(); 
      } 
      }, 
      setCaretSelection : function(options) 
      { 
      var inp = this[0]; 
      if(inp.createTextRange) 
      { 
      var selRange = inp.createTextRange(); 
      selRange.collapse(true); 
      selRange.moveStart('character', options.start); 
      selRange.moveEnd('character',options.end - options.start); 
      selRange.select(); 
      } 
      else if(inp.setSelectionRange) 
      { 
      inp.focus(); 
      inp.setSelectionRange(options.start, options.end); 
      } 
      }, 
      getCaretSelection: function() 
      { 
      var inp = this[0], start = 0, end = 0; 
      if(!isNaN(inp.selectionStart)) 
      { 
      start = inp.selectionStart; 
      end = inp.selectionEnd; 
      } 
      else if(inp.createTextRange) 
      { 
      var inpTxtLen = inp.value.length, jqueryTxtLen = this.val().length; 
      var inpRange = inp.createTextRange(), collapsedRange = inp.createTextRange(); 

      inpRange.moveToBookmark(document.selection.createRange().getBookmark()); 
      collapsedRange.collapse(false); 

      start = inpRange.compareEndPoints('StartToEnd', collapsedRange) > -1 ? jqueryTxtLen : inpRange.moveStart('character', -inpTxtLen); 
      end = inpRange.compareEndPoints('EndToEnd', collapsedRange) > -1 ? jqueryTxtLen : inpRange.moveEnd('character', -inpTxtLen); 
      } 
      return {start: Math.abs(start), end: Math.abs(end)}; 

      }, 
      // Usage: $('#txtArea').replaceCaretSelection({start: startIndex, end: endIndex, text: 'text to replace with', insPos: 'before|after|select'}) 
      // Options  start: start index of the text to be replaced 
      //    end: end index of the text to be replaced 
      //    text: text to replace the selection with 
      //   insPos: indicates whether to place the caret 'before' or 'after' the replacement text, 'select' will select the replacement text 

      replaceCaretSelection: function(options) 
      { 
      var pos = this.caretSelection(); 
      this.val(this.val().substring(0,pos.start) + options.text + this.val().substring(pos.end)); 
      if(options.insPos == 'before') 
      { 
      this.caretSelection({start: pos.start, end: pos.start}); 
      } 
      else if(options.insPos == 'after') 
      { 
      this.caretSelection({start: pos.start + options.text.length, end: pos.start + options.text.length}); 
      } 
      else if(options.insPos == 'select') 
      { 
      this.caretSelection({start: pos.start, end: pos.start + options.text.length}); 
      } 
      } 
     }); 
+0

¿Los "códigos javascript anteriores" son una referencia a la respuesta aceptada? Si es así, un comentario a la respuesta en sí sería útil para que yo pueda mejorar la respuesta si es necesario. ¿Podría dar un ejemplo específico de sus resultados inconsistentes? –

Cuestiones relacionadas