2010-09-01 15 views
7

Sto provando ad estrarre la selezione esatta e la posizione del cursore da un'area di testo. Come al solito, ciò che è facile nella maggior parte dei browser non è in IE.IE document.selection.createRange non include righe vuote iniziali o finali

Sto usando questo:

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; 

Quali opere il 99% del tempo. Il problema è che TextRange.text non restituisce caratteri iniziali o finali di nuove righe. Quindi, quando il cursore è un paio di righe vuote dopo un paragrafo, restituisce una posizione alla fine del paragrafo precedente, piuttosto che la posizione attuale del cursore.

esempio:

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

| <- when really it's here 

L'unica soluzione mi viene in mente è quello di inserire temporaneamente un carattere prima e dopo la selezione, afferrare la selezione vera e propria e quindi rimuovere di nuovo quei personaggi di temp. È un hack ma in un rapido esperimento sembra che funzionerà.

Ma prima vorrei essere sicuro che non ci sia un modo più semplice.

risposta

12

Sto aggiungendo un'altra risposta dato che la mia precedente è

Questo è quello che considero la versione migliore ancora: prende l'approccio di Bobince (menzionato nei commenti alla mia prima risposta) e corregge le due cose che non mi piacevano su di esso, che erano le prime che si basa su TextRanges che si allontanano dal textarea (danneggiando così le prestazioni), e in secondo luogo la sporcizia di dover scegliere un numero gigante per il numero di caratteri per spostare il limite dell'intervallo.

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

Bello. Ti piace l'idea di usare la lunghezza del testo invece del numero veramente grande sul moveStart/moveEnd. –

+0

Ah, non l'ho visto. Il risultato è 20-30ms ora, lavoro eccellente! –

+1

@Andy: ho scritto un plug-in jQuery che include questo. Non ancora documentato e integrato in un progetto tenuamente correlato, ma funzionante: http://code.google.com/p/rangy/downloads/detail?name=textinputs_jquery-src.js –

1

N.B. Si prega di fare riferimento al mio other answer per la migliore soluzione che posso offrire. Lascio questo qui per lo sfondo.

Mi sono imbattuto in questo problema e ho scritto quanto segue che funziona in tutti i casi. In IE utilizza il metodo che hai suggerito di inserire temporaneamente un carattere al limite della selezione e quindi usa document.execCommand("undo") per rimuovere il carattere inserito e impedire che l'inserimento resti nello stack di annullamento. Sono abbastanza sicuro che non ci sia un modo più semplice. Fortunatamente, IE 9 supporterà le proprietà selectionStart e 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); 

UPDATE

Sulla base di approccio suggerito di bobince nei commenti, ho creato il seguente, che sembra funzionare bene. Alcune note:

  1. L'approccio di bobince è più semplice e più breve.
  2. Il mio approccio è invadente: apporta modifiche al valore dell'input prima di ripristinare tali modifiche, sebbene non vi sia alcun effetto visibile di questo.
  3. Il mio approccio ha il vantaggio di mantenere tutte le operazioni all'interno dell'input. L'approccio di Bobince si basa sulla creazione di intervalli che spaziano dall'inizio del corpo alla selezione corrente.
  4. Una conseguenza di 3. è che le prestazioni di bobince variano con la posizione dell'input all'interno del documento mentre il mio no. I miei semplici test suggeriscono che quando l'input è vicino all'inizio del documento, l'approccio di Bobince è significativamente più veloce. Quando l'input è dopo una parte significativa dell'HTML, il mio approccio è più veloce.

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

Questa sembra una soluzione molto disordinato! C'è qualche ragione per preferirlo semplicemente usando il metodo 'range.moveStart ('character', -10000000)' per determinare i limiti di selezione in una textarea? Questo ha ancora '\ r \ n' da sistemare, ma questo è relativamente semplice in confronto. – bobince

+0

bobince: erm. Non riesco a ricordare il problema che pensavo esistesse con il tuo suggerimento e ora non riesco a trovarne uno. Tornerò da te. –

+0

bobince: hmm. Sembra che tu abbia ragione. Ero sicuro che c'era un qualche tipo di problema con le linee vuote con il tuo approccio, ma non sembra esserci. –

1

L'azione di bazillion negativo sembra funzionare perfettamente.

Ecco cosa ho finito 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"); 

Grazie bobince - come posso votare la vostra risposta quando è solo un commento :(

+0

Ho approfondito questo aspetto. Vedi la mia risposta per le mie conclusioni. Due punti su ciò che hai lì: genererà un errore se lo usi su un input invece che su una textarea, e anche le posizioni che restituisce sono relative a un pezzo di testo che non è il valore reale nell'input: I pensate che la posizione di selezione sia concettualmente un offset all'interno del valore di input, che contiene '\ r \ n' per ogni interruzione di linea piuttosto che' \ n'. –

+0

Sì, ecco perché la funzione menzionata su http://stackoverflow.com/questions/1738808#1739088 restituisce le stringhe effettive, corrette per '\ r \ n', piuttosto che gli indici nel valore; Immagino che lo stesso accadrà qui con 'm_text'. – bobince

+0

Sì. Tuttavia, il tuo esempio non restituisce le posizioni. –

1

Un plugin jQuery per ottenere l'indice di selezione inizio e fine nell'area di testo. I codici javascript sopra non funzionavano per IE7 e IE8 e davano risultati molto incoerenti, quindi ho scritto questo piccolo plugin jQuery. Consente di salvare temporaneamente l'indice di inizio e fine della selezione e di evidenziare la selezione in un secondo momento.

Un esempio di lavoro e breve versione è qui: http://jsfiddle.net/hYuzk/3/

A Maggiori dettagli versione con i commenti, ecc è qui: 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

"i codici javascript precedenti" sono un riferimento alla risposta accettata? Se è così, un commento alla risposta stessa sarebbe utile per poter migliorare la risposta se necessario. Potresti fornire un esempio specifico dei suoi risultati incoerenti? –

Problemi correlati