Document.selection.createRange di IE non include linee vuote iniziali o finali

Sto cercando di estrarre la selezione esatta e la posizione del cursore da una textarea. Come al solito, ciò che è facile nella maggior parte dei browser non è in IE.

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; 

Che funziona il 99% delle volte. Il problema è che TextRange.text non restituisce caratteri TextRange.text 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.

per esempio:

 the quick brown fox| <- above code thinks the cursor is here | <- when really it's here 

L’unica correzione che posso pensare è inserire temporaneamente un carattere prima e dopo la selezione, prendere la selezione effettiva e quindi rimuovere di nuovo quei caratteri temporanei. È un hack ma in un rapido esperimento sembra che funzionerà.

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

Sto aggiungendo un’altra risposta dal momento che il mio precedente sta già diventando un po ‘epico.

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 a riguardo, che erano le prime che si basa su TextRanges che si allontanano dalla textarea (danneggiando così le prestazioni), e in secondo luogo la sporcizia di dover scegliere un numero gigante per il numero di personaggi 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); 

La mossa del 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 tua risposta quando è solo un commento 🙁

Un plugin jQuery per ottenere l’indice di selezione inizia e termina 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 una breve versione sono qui: http://jsfiddle.net/hYuzk/3/

Una versione più dettagliata con 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}); } } }); 

NB Si prega di fare riferimento alla mia altra risposta 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 suggerito per l’inserimento temporaneo di un carattere al limite della selezione, quindi utilizza 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); 

AGGIORNARE

Sulla base dell’approccio suggerito da 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);