Jak wstawić tekst do obszaru tekstu w aktualnej pozycji kursora?

126

Chciałbym stworzyć prostą funkcję, która dodaje tekst do obszaru tekstowego w pozycji kursora użytkownika. To musi być czysta funkcja. Tylko podstawy. Resztę wymyślę.

JoshMWilliams
źródło
3
możliwy duplikat Jak wstawić tekst w miejscu kursora?
Derek 朕 會 功夫
2
Spójrz na tę odpowiedź już opublikowaną: stackoverflow.com/questions/4456545/…
John Culviner
Ciekawy artykuł 2018: Jak wstawić tekst do
obszaru
Jeśli szukasz prostego modułu z obsługą cofania, wypróbuj wstaw-tekst- obszar -tekst . Jeśli potrzebujesz obsługi IE8 +, wypróbuj pakiet wstawiania tekstu przy kursorze .
fregante

Odpowiedzi:

117
function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        myField.focus();
        sel = document.selection.createRange();
        sel.text = myValue;
    }
    //MOZILLA and others
    else if (myField.selectionStart || myField.selectionStart == '0') {
        var startPos = myField.selectionStart;
        var endPos = myField.selectionEnd;
        myField.value = myField.value.substring(0, startPos)
            + myValue
            + myField.value.substring(endPos, myField.value.length);
    } else {
        myField.value += myValue;
    }
}
Raab
źródło
19
aby naprawić „utratę pozycji karetki”: dodaj wstawianie tych wierszy wcześniej} else { myField.selectionStart = startPos + myValue.length; myField.selectionEnd = startPos + myValue.length;
user340140
10
Dzięki Rab za odpowiedź i @ user340140 za poprawkę. Oto działający przykład .
Znarkus
@ user340140, twoja poprawka "stracić pozycję karetki", działa tylko wtedy, gdy skupię się na wejściu tuż przed sugerowanymi liniami. Wydaje się, że nie można zmienić zaznaczenia na nieokreślonym polu, przynajmniej w Chrome (aktualna wersja 62.0)
Jette
Jest drobny problem z tym kodem: selectionStartjest wartością liczbową, a zatem powinien być porównywany, 0a nie '0'i prawdopodobnie powinien być używany===
Herohtar
83

Ten fragment może ci w tym pomóc w kilku wierszach jQuery 1.9+: http://jsfiddle.net/4MBUG/2/

$('input[type=button]').on('click', function() {
    var cursorPos = $('#text').prop('selectionStart');
    var v = $('#text').val();
    var textBefore = v.substring(0,  cursorPos);
    var textAfter  = v.substring(cursorPos, v.length);

    $('#text').val(textBefore + $(this).val() + textAfter);
});
Adriano Alves
źródło
Wspaniały! Działa również z wersją 1.6 z niewielkimi modyfikacjami.
Șerban Ghiță
1
Ale nie może zastąpić zaznaczonego tekstu
Sergey Goliney
@mparkuk: nadal cierpi z powodu problemu „utraty pozycji karetki” wspomnianego powyżej przez użytkownika340140. (Przepraszam, powinienem to naprawić, ale zabrakło mi czasu.)
jbobbins
4
Dziękuję za działające skrzypce. Zaktualizowałem go, aby również zresetować pozycję daszka i uczyniłem go wtyczką jquery: jsfiddle.net/70gqn153
wolnośćn-m
To działa, ale kursor kończy się w niewłaściwym miejscu.
AndroidDev
36

Ze względu na poprawny JavaScript

HTMLTextAreaElement.prototype.insertAtCaret = function (text) {
  text = text || '';
  if (document.selection) {
    // IE
    this.focus();
    var sel = document.selection.createRange();
    sel.text = text;
  } else if (this.selectionStart || this.selectionStart === 0) {
    // Others
    var startPos = this.selectionStart;
    var endPos = this.selectionEnd;
    this.value = this.value.substring(0, startPos) +
      text +
      this.value.substring(endPos, this.value.length);
    this.selectionStart = startPos + text.length;
    this.selectionEnd = startPos + text.length;
  } else {
    this.value += text;
  }
};
Erik Aigner
źródło
bardzo ładne rozszerzenie! działa zgodnie z oczekiwaniami. Dzięki!
Martin Johansson
Najlepsze rozwiązanie! Dziękuję
Dima Melnik
4
Nie jest dobrym pomysłem przedłużanie prototypu obiektów, których nie posiadasz. Po prostu uczyń to zwykłą funkcją i działa równie dobrze.
fregante
Spowoduje to wyczyszczenie buforu cofania dla elementu edit po ustawieniu this.value = .... Czy jest sposób, aby to zachować?
c00000fd
19

Nowa odpowiedź:

https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setRangeText

Nie jestem jednak pewien, czy przeglądarka obsługuje to.

Przetestowano w Chrome 81.

function typeInTextarea(newText, el = document.activeElement) {
  const [start, end] = [el.selectionStart, el.selectionEnd];
  el.setRangeText(newText, start, end, 'select');
}

document.getElementById("input").onkeydown = e => {
  if (e.key === "Enter") typeInTextarea("lol");
}
<input id="input" />
<br/><br/>
<div>Press Enter to insert "lol" at caret.</div>
<div>It'll replace a selection with the given text.</div>

Stara odpowiedź:

Czysta modyfikacja JS odpowiedzi Erika Pukinskisa:

function typeInTextarea(newText, el = document.activeElement) {
  const start = el.selectionStart
  const end = el.selectionEnd
  const text = el.value
  const before = text.substring(0, start)
  const after  = text.substring(end, text.length)
  el.value = (before + newText + after)
  el.selectionStart = el.selectionEnd = start + newText.length
  el.focus()
}

document.getElementById("input").onkeydown = e => {
  if (e.key === "Enter") typeInTextarea("lol");
}
<input id="input" />
<br/><br/>
<div>Press Enter to insert "lol" at caret.</div>

Przetestowano w Chrome 47, 81 i Firefox 76.

Jeśli chcesz zmienić wartość aktualnie zaznaczonego tekstu podczas pisania w tym samym polu (dla autouzupełniania lub podobnego efektu), podaj document.activeElementjako pierwszy parametr.

Nie jest to najbardziej elegancki sposób, ale jest całkiem prosty.

Przykładowe zastosowania:

typeInTextarea('hello');
typeInTextarea('haha', document.getElementById('some-id'));
Jayant Bhawal
źródło
nie zamknąłeś linii za pomocą >>; <<
Phoenix,
4
Średniki @Phoenix są opcjonalne w JavaScript. Działa również bez nich. Chociaż, jeśli chcesz, możesz edytować średnikami. Nic takiego.
Jayant Bhawal
3
Zrobiłem demo na JSFiddle. Działa również przy użyciu Version 54.0.2813.0 canary (64-bit)Chrome Canary 54.0.2813.0. Na koniec, jeśli chcesz wstawić go do pola tekstowego według identyfikatora, użyj document.getElementById('insertyourIDhere')zamiast elw funkcji.
Haykam
Która część mojej odpowiedzi nie jest „czystym” JS? Czy zapomniałem tam C ++?
Erik Aigner,
2
Hej @ErikAigner! Mój zły, nie zdawałem sobie sprawy, że na to pytanie odpowiedziało dwóch Erików. Miałem na myśli Erik Pukinskis. Zaktualizuję odpowiedź, aby lepiej to odzwierciedlała.
Jayant Bhawal
15

Proste rozwiązanie, które działa w przeglądarkach Firefox, Chrome, Opera, Safari i Edge, ale prawdopodobnie nie będzie działać na starych przeglądarkach IE.

  var target = document.getElementById("mytextarea_id")

  if (target.setRangeText) {
     //if setRangeText function is supported by current browser
     target.setRangeText(data)
  } else {
    target.focus()
    document.execCommand('insertText', false /*no UI*/, data);
  }
}

setRangeTextFunkcja pozwala zastąpić bieżące zaznaczenie podanym tekstem lub, jeśli nie ma zaznaczenia, wstawić tekst w pozycji kursora. O ile wiem, jest obsługiwany tylko przez Firefoxa.

W przypadku innych przeglądarek istnieje polecenie "insertText", które ma wpływ tylko na aktualnie aktywny element html i działa tak samo jak setRangeText

Częściowo zainspirowany tym artykułem

Ramast
źródło
To prawie właściwa droga. Podlinkowany artykuł oferuje pełne rozwiązanie w postaci pakietu: wstaw tekst w miejscu kursora . Jednak wolę, execCommandponieważ obsługuje undoi tworzy pole tekstowe wstawiania tekstu . Brak wsparcia dla IE, ale mniejsze
fregante
1
Niestety execCommandMDN uważa , że jest przestarzały: developer.mozilla.org/en-US/docs/Web/API/Document/execCommand Nie wiem dlaczego, wydaje się być naprawdę przydatny!
Richard
1
Tak, execCommand jest używana w innych przeglądarkach, w przypadku przeglądarki Firefox zamiast tego używana jest funkcja setRangeText.
Ramast
Ramast, to nie jest to, co robi twój kod. Będzie używał setRangeText zamiast execCommand dla każdej przeglądarki, która go definiuje (większość). W przypadku opisywanego zachowania musisz najpierw wywołać document.execCommand, a następnie sprawdzić zwracaną wartość. Jeśli to fałsz, użyj target.setRangeText.
Jools
@Jools jeśli setRangeText jest obsługiwany, dlaczego nie użyć go zamiast execCommand? Dlaczego najpierw muszę wypróbować execCommand?
Ramast
10

Odpowiedź Rab działa świetnie, ale nie w Microsoft Edge, więc dodałem też małą adaptację dla Edge:

https://jsfiddle.net/et9borp4/

function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        myField.focus();
        sel = document.selection.createRange();
        sel.text = myValue;
    }
    // Microsoft Edge
    else if(window.navigator.userAgent.indexOf("Edge") > -1) {
      var startPos = myField.selectionStart; 
      var endPos = myField.selectionEnd; 

      myField.value = myField.value.substring(0, startPos)+ myValue 
             + myField.value.substring(endPos, myField.value.length); 

      var pos = startPos + myValue.length;
      myField.focus();
      myField.setSelectionRange(pos, pos);
    }
    //MOZILLA and others
    else if (myField.selectionStart || myField.selectionStart == '0') {
        var startPos = myField.selectionStart;
        var endPos = myField.selectionEnd;
        myField.value = myField.value.substring(0, startPos)
            + myValue
            + myField.value.substring(endPos, myField.value.length);
    } else {
        myField.value += myValue;
    }
}
Snorvarg
źródło
9

Lubię prosty javascript i zwykle mam pod ręką jQuery. Oto, co wymyśliłem na podstawie mparkuków :

function typeInTextarea(el, newText) {
  var start = el.prop("selectionStart")
  var end = el.prop("selectionEnd")
  var text = el.val()
  var before = text.substring(0, start)
  var after  = text.substring(end, text.length)
  el.val(before + newText + after)
  el[0].selectionStart = el[0].selectionEnd = start + newText.length
  el.focus()
}

$("button").on("click", function() {
  typeInTextarea($("textarea"), "some text")
  return false
})

Oto demo: http://codepen.io/erikpukinskis/pen/EjaaMY?editors=101

Erik Pukinskis
źródło
6

function insertAtCaret(text) {
  const textarea = document.querySelector('textarea')
  textarea.setRangeText(
    text,
    textarea.selectionStart,
    textarea.selectionEnd,
    'end'
  )
}

setInterval(() => insertAtCaret('Hello'), 3000)
<textarea cols="60">Stack Overflow Stack Exchange Starbucks Coffee</textarea>

źródło
4

Jeśli użytkownik nie dotknie wejścia po wstawieniu tekstu, zdarzenie „input” nigdy nie zostanie wyzwolone, a atrybut wartości nie odzwierciedli zmiany. Dlatego ważne jest, aby wyzwolić zdarzenie wejściowe po programowym wstawieniu tekstu. Skupienie pola nie wystarczy.

Poniżej znajduje się kopia odpowiedzi Snorvarga z wyzwalaczem wejściowym na końcu:

function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        myField.focus();
        sel = document.selection.createRange();
        sel.text = myValue;
    }
    // Microsoft Edge
    else if(window.navigator.userAgent.indexOf("Edge") > -1) {
      var startPos = myField.selectionStart; 
      var endPos = myField.selectionEnd; 

      myField.value = myField.value.substring(0, startPos)+ myValue 
             + myField.value.substring(endPos, myField.value.length); 

      var pos = startPos + myValue.length;
      myField.focus();
      myField.setSelectionRange(pos, pos);
    }
    //MOZILLA and others
    else if (myField.selectionStart || myField.selectionStart == '0') {
        var startPos = myField.selectionStart;
        var endPos = myField.selectionEnd;
        myField.value = myField.value.substring(0, startPos)
            + myValue
            + myField.value.substring(endPos, myField.value.length);
    } else {
        myField.value += myValue;
    }
    triggerEvent(myField,'input');
}

function triggerEvent(el, type){
  if ('createEvent' in document) {
    // modern browsers, IE9+
    var e = document.createEvent('HTMLEvents');
    e.initEvent(type, false, true);
    el.dispatchEvent(e);
  } else {
    // IE 8
    var e = document.createEventObject();
    e.eventType = type;
    el.fireEvent('on'+e.eventType, e);
  }
}

Kredyt dla plainjs.com za funkcję triggerEvent

Więcej o wydarzeniu oninput na w3schools.com

Odkryłem to podczas tworzenia selektora emotikonów do czatu. Jeśli użytkownik wybierze tylko kilka emotikonów i naciśnie przycisk „wyślij”, pole wprowadzania danych nigdy nie zostanie dotknięte przez użytkownika. Podczas sprawdzania atrybutu wartość był on zawsze pusty, mimo że wstawione unikody emoji były widoczne w polu wprowadzania. Okazuje się, że jeśli użytkownik nie dotknie pola, zdarzenie „input” nigdy nie zostanie wywołane i rozwiązaniem było wywołanie go w ten sposób. Zajęło mi to trochę czasu, żeby to rozgryźć ... mam nadzieję, że to zaoszczędzi komuś trochę czasu.

Jette
źródło
0

Wysyłanie zmodyfikowanej funkcji do własnego użytku. Ten przykład wstawia wybrany element z <select>obiektu i umieszcza karetkę między tagami:

//Inserts a choicebox selected element into target by id
function insertTag(choicebox,id) {
    var ta=document.getElementById(id)
    ta.focus()
    var ss=ta.selectionStart
    var se=ta.selectionEnd
    ta.value=ta.value.substring(0,ss)+'<'+choicebox.value+'>'+'</'+choicebox.value+'>'+ta.value.substring(se,ta.value.length)
    ta.setSelectionRange(ss+choicebox.value.length+2,ss+choicebox.value.length+2)
}
Alexiy
źródło
0
 /**
 * Usage "foo baz".insertInside(4, 0, "bar ") ==> "foo bar baz"
 */
String.prototype.insertInside = function(start, delCount, newSubStr) {
    return this.slice(0, start) + newSubStr + this.slice(start + Math.abs(delCount));
};


 $('textarea').bind("keydown keypress", function (event) {
   var val = $(this).val();
   var indexOf = $(this).prop('selectionStart');
   if(event.which === 13) {
       val = val.insertInside(indexOf, 0,  "<br>\n");
       $(this).val(val);
       $(this).focus();
    }
})
zaarour
źródło
Chociaż może to odpowiedzieć na pytanie, lepiej jest wyjaśnić podstawowe części odpowiedzi i być może na czym polegał problem z kodem PO.
pirho
0

Poniższy kod jest adaptacją pakietu w języku TypeScript https://github.com/grassator/insert-text-at-cursor autorstwa Dmitriya Kubyshkina.


/**
 * Inserts the given text at the cursor. If the element contains a selection, the selection
 * will be replaced by the text.
 */
export function insertText(input: HTMLTextAreaElement | HTMLInputElement, text: string) {
  // Most of the used APIs only work with the field selected
  input.focus();

  // IE 8-10
  if ((document as any).selection) {
    const ieRange = (document as any).selection.createRange();
    ieRange.text = text;

    // Move cursor after the inserted text
    ieRange.collapse(false /* to the end */);
    ieRange.select();

    return;
  }

  // Webkit + Edge
  const isSuccess = document.execCommand("insertText", false, text);
  if (!isSuccess) {
    const start = input.selectionStart;
    const end = input.selectionEnd;
    // Firefox (non-standard method)
    if (typeof (input as any).setRangeText === "function") {
      (input as any).setRangeText(text);
    } else {
      if (canManipulateViaTextNodes(input)) {
        const textNode = document.createTextNode(text);
        let node = input.firstChild;

        // If textarea is empty, just insert the text
        if (!node) {
          input.appendChild(textNode);
        } else {
          // Otherwise we need to find a nodes for start and end
          let offset = 0;
          let startNode = null;
          let endNode = null;

          // To make a change we just need a Range, not a Selection
          const range = document.createRange();

          while (node && (startNode === null || endNode === null)) {
            const nodeLength = node.nodeValue.length;

            // if start of the selection falls into current node
            if (start >= offset && start <= offset + nodeLength) {
              range.setStart((startNode = node), start - offset);
            }

            // if end of the selection falls into current node
            if (end >= offset && end <= offset + nodeLength) {
              range.setEnd((endNode = node), end - offset);
            }

            offset += nodeLength;
            node = node.nextSibling;
          }

          // If there is some text selected, remove it as we should replace it
          if (start !== end) {
            range.deleteContents();
          }

          // Finally insert a new node. The browser will automatically
          // split start and end nodes into two if necessary
          range.insertNode(textNode);
        }
      } else {
        // For the text input the only way is to replace the whole value :(
        const value = input.value;
        input.value = value.slice(0, start) + text + value.slice(end);
      }
    }

    // Correct the cursor position to be at the end of the insertion
    input.setSelectionRange(start + text.length, start + text.length);

    // Notify any possible listeners of the change
    const e = document.createEvent("UIEvent");
    e.initEvent("input", true, false);
    input.dispatchEvent(e);
  }
}

function canManipulateViaTextNodes(input: HTMLTextAreaElement | HTMLInputElement) {
  if (input.nodeName !== "TEXTAREA") {
    return false;
  }
  let browserSupportsTextareaTextNodes;
  if (typeof browserSupportsTextareaTextNodes === "undefined") {
    const textarea = document.createElement("textarea");
    textarea.value = "1";
    browserSupportsTextareaTextNodes = !!textarea.firstChild;
  }
  return browserSupportsTextareaTextNodes;
}
André Pena
źródło
-1

Zmieniono to na getElementById (myField)

 function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        document.getElementById(myField).focus();
        sel = document.selection.createRange();
        sel.text = myValue;
    }
    //MOZILLA and others
    else if (document.getElementById(myField).selectionStart || document.getElementById(myField).selectionStart == '0') {
        var startPos = document.getElementById(myField).selectionStart;
        var endPos = document.getElementById(myField).selectionEnd;
        document.getElementById(myField).value = document.getElementById(myField).value.substring(0, startPos)
            + myValue
            + document.getElementById(myField).value.substring(endPos, document.getElementById(myField).value.length);
    } else {
        document.getElementById(myField).value += myValue;
    }
}
tekagami
źródło
3
To uderzy w DOM o wiele bardziej niż potrzebujesz. Przechowywanie myfieldjako lokalny jest znacznie lepsze pod względem wydajności
TMan
2
Wow, naprawdę za dużo powtórzeń document.getElementById(myField)! Zrób to raz u góry i użyj nazwy zmiennej. Ile razy z rzędu zamierzasz ponownie wyszukać ten sam element?
doug65536