2010-09-01 14 views
7

Próbuję wyodrębnić dokładny wybór i położenie kursora z obszaru tekstowego. Jak zwykle, to, co jest łatwe w większości przeglądarek, nie jest w IE.IE's document.selection.createRange nie zawiera początkowych lub końcowych pustych wierszy.

używam to:

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; 

Który działa 99% czasu. Problem polega na tym, że TextRange.text nie zwraca początkowych ani końcowych znaków nowego wiersza. Tak więc, gdy kursor jest parą pustych linii po akapicie, uzyskuje pozycję na końcu poprzedzającego akapitu - zamiast rzeczywistej pozycji kursora.

np

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

| <- when really it's here 

Jedyna poprawka mogę myśleć jest tymczasowo wstawić znak przed i po selekcji, chwyć rzeczywisty wybór, a następnie ponownie usunąć te znaki tymczasowe. To hack, ale w szybkim eksperymencie wygląda na to, że zadziała.

Ale najpierw chciałbym być pewien, że nie jest łatwiejszy sposób.

Odpowiedz

12

Dodaję kolejną odpowiedź, ponieważ mój poprzedni jest już jest nieco epicki

Oto, co uważam za najlepszą wersję: wymaga podejścia Bobka (wymienionego w komentarzach do mojej pierwszej odpowiedzi) i naprawia dwie rzeczy, które mi się nie podobają, które były po raz pierwszy opiera się na TextRanges, które zbłądzą poza textarea (w ten sposób szkodząc wydajności), a po drugie niechęć do wybierania gigantycznej liczby dla liczby znaków, aby przesunąć granicę zasięgu.

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

Nice. Podobnie jak pomysł użycia długości tekstu zamiast naprawdę dużej liczby na moveStart/moveEnd. –

+0

Ah, nie widziałem tego. Wynik to 20-30ms, świetna robota! –

+1

@Andy: Napisałem wtyczkę jQuery, która zawiera to. Jeszcze nieudokumentowane i połączone ze słabo powiązanym projektem, ale działające: http://code.google.com/p/rangy/downloads/detail?name=textinputs_jquery-src.js –

1

N.B. Zapoznaj się z moim other answer, aby uzyskać najlepsze rozwiązanie, jakie mogę zaoferować. Zostawiam to tutaj na tle.

Natknąłem się na ten problem i napisałem poniżej, że działa we wszystkich przypadkach. W IE używa on metody, którą zasugerowałeś do tymczasowego wstawienia znaku na granicy selekcji, a następnie używa document.execCommand("undo"), aby usunąć wstawiony znak i zapobiec pozostawaniu wstawki na stosie cofania. Jestem prawie pewien, że nie ma łatwiejszego sposobu. Na szczęście IE 9 będzie obsługiwał właściwości selectionStart i 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

podstawie bobince sugerowanej podejścia w komentarzach, jakie stworzył następujące dane, które wydaje się działać dobrze. Niektóre uwagi:

  1. Podejście bobince jest prostsze i krótsze.
  2. Moje podejście jest uciążliwe: wprowadza zmiany w wartości wejściowej przed cofnięciem tych zmian, chociaż nie ma widocznego tego efektu.
  3. Moje podejście ma tę zaletę, że zachowuje wszystkie operacje w obrębie danych wejściowych. Podejście bobince polega na tworzeniu zakresów, które rozciągają się od początku ciała do aktualnej selekcji.
  4. Konsekwencją 3. jest to, że działanie bobince zmienia się w zależności od pozycji danych wejściowych w dokumencie, podczas gdy moje nie. Moje proste testy sugerują, że kiedy dane wejściowe są zbliżone do początku dokumentu, podejście bobince jest znacznie szybsze. Gdy dane wejściowe są po znacznym fragmencie kodu HTML, moje podejście jest szybsze.

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

To wydaje się bardzo brudny obejście! Czy istnieje jakikolwiek powód, aby preferować go po prostu za pomocą metody 'range.moveStart ('character', -10000000)' określającej granice wyboru w obszarze tekstowym? To wciąż ma "\ r \ n" do naprawienia, ale jest to względnie proste w porównaniu. – bobince

+0

bobince: erm. Nie pamiętam problemu, który według mnie istniał z twoją sugestią, a teraz nie mogę go znaleźć. Wrócę do ciebie. –

+0

Bobek: hmm. Wygląda na to, że masz rację. Byłem pewien, że był jakiś problem z pustymi liniami z twoim podejściem, ale wydaje się, że tak nie jest. –

1

Przejście przez negatywną bazillion wydaje się doskonale działa.

Oto, co skończyło się z:

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

Dzięki bobince - jak mogę głosować w górę swoją odpowiedź, kiedy to tylko komentarz :(

+0

Zajrzałem do tego nieco dalej. Zobacz moją odpowiedź na moje wnioski. Dwie uwagi na temat tego, co tam masz: wygeneruje błąd, jeśli użyjesz go na wejściu zamiast w polu tekstowym, a także pozycje, które zwraca, odnoszą się do fragmentu tekstu, który nie jest rzeczywistą wartością na wejściu: I uważam, że pozycja wyboru jest koncepcyjnie przesunięciem w wartości wejściowej, która zawiera '\ r \ n' dla każdego podziału linii zamiast' \ n'. –

+0

Tak, dlatego funkcja wspomniana na http://stackoverflow.com/questions/1738808#1739088 zwraca rzeczywiste ciągi poprawione dla '\ r \ n', a nie indeksy do wartości; Myślę, że to samo stanie się tutaj z 'm_text'. – bobince

+0

Tak. Twój przykład nie zwraca jednak pozycji. –

1

Wtyczka jquery do pobierania indeksu rozpoczynającego i kończącego w obszarze tekstowym. Powyższe kody javascript nie działały dla IE7 i IE8 i dawały bardzo niespójne wyniki, więc napisałem tę małą wtyczkę jQuery. Umożliwia tymczasowe zapisanie początkowego i końcowego indeksu zaznaczenia i zaostrzenie selekcji w późniejszym czasie.

przykładem pracę i krótka wersja jest tutaj: http://jsfiddle.net/hYuzk/3/

Bardziej szczegółowe wersja z komentarzami itp jest tutaj: 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

Czy "powyższe kody javascript" stanowią odniesienie do zaakceptowanej odpowiedzi? Jeśli tak, pomocny byłby komentarz do samej odpowiedzi, aby w razie potrzeby poprawić odpowiedź. Czy możesz podać konkretny przykład niespójnych wyników? –

Powiązane problemy