Como inserir texto na área de texto na posição atual do cursor?

124

Eu gostaria de criar uma função simples que adiciona texto a uma área de texto na posição do cursor do usuário. Precisa ser uma função limpa. Apenas o básico. Eu posso descobrir o resto.

JoshMWilliams
fonte
3
possível duplicação de Como inserir algum texto onde está o cursor?
Derek朕會功夫
2
Dê uma olhada nesta resposta já publicada: stackoverflow.com/questions/4456545/…
John Culviner
Interessante 2,018 artigo: Como inserir texto em Textarea no Cursor Rápido
Delgan
Se você estiver procurando por um módulo simples com suporte para desfazer, tente inserir texto-texto-área . Se você precisar do suporte ao IE8 +, tente o pacote inserir texto no cursor .
28819 fregante

Respostas:

115
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
fonte
19
Para corrigir "perde posição do cursor": add inser estas linhas antes} else { myField.selectionStart = startPos + myValue.length; myField.selectionEnd = startPos + myValue.length;
user340140
10
Obrigado Rab pela resposta e @ user340140 pela correção. Aqui está um exemplo de trabalho .
Znarkus
@ user340140, sua correção "perder caret potition", só funciona se eu focar a entrada logo antes das linhas sugeridas. Parece ser impossível mudar a seleção em um campo não-focalizada, pelo menos no Chrome (versão atual 62.0)
Jette
Há um pequeno problema com este código: selectionStarté um valor numérico e, portanto, deve ser comparado 0e não '0', e provavelmente deve ser usado #===
Herohtar 18/12/1918
82

Este trecho pode ajudá-lo em algumas linhas do 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
fonte
Ótimo! Também funciona com o 1.6 com pequenas modificações.
Banerban Ghiță
1
Mas não pode substituir o texto selecionado #
Sergey Goliney
@mparkuk: ainda sofre com o problema "perde posição de sinal de intercalação" mencionado acima pelo usuário340140. (Desculpe, eu deveria consertá-lo, mas eu corri para fora de tempo.)
jbobbins
4
Obrigado, fornecendo um violino de trabalho. Eu atualizei isso também redefinir a posição do cursor e fez um plugin jQuery: jsfiddle.net/70gqn153
freedomn-m
Isso funciona, mas o cursor acaba no local errado.
AndroidDev
36

Por uma questão de Javascript adequado

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
fonte
extensão muito legal! funciona exatamente como esperado. Obrigado!
Martin Johansson
Melhor solução! Obrigado
Dima Melnik
4
Não é uma boa ideia estender o protótipo de objetos que você não possui. Basta torná-lo uma função regular e funciona tão bem.
28419 fregante
Isso limpa o buffer de desfazer do elemento de edição após a configuração this.value = .... Existe uma maneira de preservá-lo?
C00000fd
18

Nova resposta:

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

Não tenho certeza sobre o suporte do navegador para isso.

Testado no 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>

Resposta antiga:

Uma modificação JS pura da resposta de Erik Pukinskis:

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>

Testado no Chrome 47, 81 e Firefox 76.

Se você deseja alterar o valor do texto selecionado no momento enquanto digita no mesmo campo (para um efeito de preenchimento automático ou similar), passe document.activeElementcomo o primeiro parâmetro.

Não é a maneira mais elegante de fazer isso, mas é bem simples.

Exemplos de usos:

typeInTextarea('hello');
typeInTextarea('haha', document.getElementById('some-id'));
Jayant Bhawal
fonte
você não fechou a linha com >>; <<
Phoenix
4
O ponto e vírgula @Phoenix é opcional em Javascript. Funciona sem eles também. No entanto, você pode editar em ponto e vírgula, se desejar. Nada demais.
Jayant Bhawal
3
Eu fiz uma demo no JSFiddle. Também funciona usando o Version 54.0.2813.0 canary (64-bit)Chrome Canary 54.0.2813.0. Por fim, se você deseja inserir na caixa de texto por ID, use document.getElementById('insertyourIDhere')no lugar de elna função
haykam
Qual parte da minha resposta não é JS "puro"? Esqueci algum C ++ lá?
Erik Aigner
2
Hey @ErikAigner! Meu mal, não sabia que esta pergunta tinha respostas de dois Erik. Eu quis dizer Erik Pukinskis. Vou atualizar a resposta para refletir melhor isso.
Jayant Bhawal 06/06
15

Uma solução simples que funciona no Firefox, Chrome, Opera, Safari e Edge, mas provavelmente não funcionará em navegadores IE antigos.

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

setRangeTextA função permite substituir a seleção atual pelo texto fornecido ou, se não houver seleção, insira o texto na posição do cursor. É suportado apenas pelo firefox, tanto quanto eu sei.

Para outros navegadores, existe o comando "insertText", que afeta apenas o elemento html atualmente focado e tem o mesmo comportamento que setRangeText

Inspirado parcialmente por este artigo

Ramast
fonte
Este é quase o caminho certo. O artigo que você vinculou oferece uma solução completa como um pacote: inserir texto no cursor . No entanto, eu prefiro execCommandporque ele suporta undoe faz insert-text-textarea . No IE support, mas menor
fregante 29/03/19
1
Infelizmente, execCommandé considerado obsoleto pelo MDN: developer.mozilla.org/en-US/docs/Web/API/Document/execCommand Não sei por que, parece ser realmente útil!
Richard
1
Sim, execCommand é usado para outros navegadores, para o Firefox a função setRangeText é usada.
Ramast 17/03
Ramast, não é isso que o seu código faz. Ele usará setRangeText em vez de execCommand para qualquer navegador que o definir (a maioria). Para o comportamento que você descreve, é necessário chamar document.execCommand primeiro e depois verificar o valor de retorno. Se for falso, use target.setRangeText.
Jools
@ Jools se setRangeText é suportado, por que não usá-lo em vez de execCommand? Por que preciso experimentar o execCommand primeiro?
Ramast
10

A resposta de Rab funciona muito bem, mas não para o Microsoft Edge, então adicionei uma pequena adaptação para o 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
fonte
9

Eu gosto de javascript simples e geralmente tenho jQuery por perto. Aqui está o que eu criei , baseado no mparkuk's :

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
})

Aqui está uma demonstração: http://codepen.io/erikpukinskis/pen/EjaaMY?editors=101

Erik Pukinskis
fonte
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>

fonte
4

Se o usuário não tocar na entrada após a inserção do texto, o evento 'input' nunca será acionado e o atributo value não refletirá a alteração. Portanto, é importante disparar o evento de entrada após a inserção de texto de forma programática. Focar o campo não é suficiente.

A seguir, uma cópia da resposta de Snorvarg com um gatilho de entrada no final:

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

Crédito para plainjs.com pela função triggerEvent

Mais sobre o evento oninput em w3schools.com

Descobri isso ao criar um seletor de emoji para um bate-papo. Se o usuário selecionar apenas alguns emojis e pressionar o botão "enviar", o campo de entrada nunca será tocado pelo usuário. Ao verificar o atributo value, ele estava sempre vazio, mesmo que os unicodes emoji inseridos estivessem visíveis no campo de entrada. Acontece que, se o usuário não toca no campo, o evento 'input' nunca é acionado e a solução é acioná-lo dessa maneira. Demorou um pouco para descobrir isso ... espero que poupe alguém a algum tempo.

Jette
fonte
0

Lançamento da função modificada para referência própria. Este exemplo insere um item selecionado do <select>objeto e coloca o cursor entre as tags:

//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
fonte
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
fonte
Embora isso possa responder à pergunta, é melhor explicar as partes essenciais da resposta e, possivelmente, qual foi o problema com o código do OP.
Pirho
0

O código abaixo é uma adaptação TypeScript do pacote https://github.com/grassator/insert-text-at-cursor por Dmitriy Kubyshkin.


/**
 * 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
fonte
-1

Alterado para 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
fonte
3
Isso vai acertar o caminho DOM mais do que você precisa .. armazenar myfieldcomo um local é muito melhor para o desempenho
TMan
2
Uau, realmente muita repetição de document.getElementById(myField)! Faça isso uma vez no topo e use um nome de variável. Quantas vezes consecutivas você pretende procurar redundantemente o mesmo elemento?
doug65536