¿Cómo insertar texto en el área de texto en la posición actual del cursor?

124

Me gustaría crear una función simple que agregue texto en un área de texto en la posición del cursor del usuario. Tiene que ser una función limpia. Solo lo básico. Puedo entender el resto.

JoshMWilliams
fuente
3
posible duplicado de ¿Cómo inserto un texto donde está el cursor?
Derek 朕 會 功夫
2
Eche un vistazo a esta respuesta ya publicada: stackoverflow.com/questions/4456545/…
John Culviner
Si está buscando un módulo simple con soporte para deshacer, intente insert-text-textarea . Si necesita soporte para IE8 +, pruebe el paquete insert-text-at-cursor .
fregante

Respuestas:

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
fuente
19
para arreglar "pierde la posición de intercalación": agregue inser estas líneas antes} else { myField.selectionStart = startPos + myValue.length; myField.selectionEnd = startPos + myValue.length;
user340140
10
Gracias Rab por la respuesta y @ user340140 por la solución. Aquí hay un ejemplo de trabajo .
Znarkus
@ user340140, su solución para "perder la poción de caret", solo funciona si le doy el foco a la entrada justo antes de las líneas que sugiere. Parece imposible cambiar la selección en un campo no enfocado, al menos en Chrome (versión actual 62.0)
Jette
Hay un problema menor con este código: selectionStartes un valor numérico y, por lo tanto, debe compararse 0y no '0', y probablemente debería usar===
Herohtar
82

Este fragmento podría ayudarlo con algunas líneas de 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
fuente
¡Excelente! También funciona con 1.6 con modificaciones menores.
Șerban Ghiță
1
Pero no puede reemplazar el texto seleccionado
Sergey Goliney
@mparkuk: todavía sufre el problema de "pierde la posición de caret" mencionado anteriormente por el usuario340140. (Lo siento, debo solucionarlo, pero se acabó el tiempo.)
jbobbins
44
Gracias por proporcionar un violín de trabajo. Lo actualicé para restablecer también la posición de caret y lo convertí en un complemento jquery: jsfiddle.net/70gqn153
freedomn-m
Esto funciona pero el cursor termina en la ubicación incorrecta.
AndroidDev
36

Por el bien de Javascript adecuado

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
fuente
Muy buena extensión! Funciona tal como se esperaba. ¡Gracias!
Martin Johansson el
¡Mejor solución! Gracias
Dima Melnik el
44
No es una buena idea extender el prototipo de objetos que no posee. Simplemente conviértalo en una función regular y funciona igual de bien.
fregante
Esto borra el búfer de deshacer para el elemento de edición después de la configuración this.value = .... ¿Hay alguna manera de preservarlo?
c00000fd
18

Nueva respuesta:

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

Sin embargo, no estoy seguro del soporte del navegador para esto.

Probado en 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>

Vieja respuesta:

Una modificación pura de JS de la respuesta 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>

Probado en Chrome 47, 81 y Firefox 76.

Si desea cambiar el valor del texto seleccionado actualmente mientras escribe en el mismo campo (para un efecto de autocompletar o similar), pase document.activeElement como primer parámetro.

No es la forma más elegante de hacer esto, pero es bastante simple.

Usos de ejemplo:

typeInTextarea('hello');
typeInTextarea('haha', document.getElementById('some-id'));
Jayant Bhawal
fuente
no cerraste la línea con >>; <<
Phoenix
44
Los puntos y comas de Phoenix son opcionales en Javascript. Funciona sin ellos también. Sin embargo, puede editar en punto y coma si lo desea. No es problema.
Jayant Bhawal
3
Hice una demostración en JSFiddle. También funciona usando Version 54.0.2813.0 canary (64-bit), que es básicamente Chrome Canary 54.0.2813.0. Finalmente, si desea que se inserte en el cuadro de texto por ID, use document.getElementById('insertyourIDhere')en lugar de elen la función.
haykam
¿Qué parte de mi respuesta no es JS "puro"? ¿Olvidé algo de C ++ allí?
Erik Aigner
2
¡Hola @ErikAigner! Lo malo, no me di cuenta que esta pregunta tenía respuestas de dos Erik. Me refería Erik Pukinskis. Actualizaré la respuesta para reflejarlo mejor.
Jayant Bhawal
15

Una solución simple que funciona en Firefox, Chrome, Opera, Safari y Edge, pero probablemente no funcionará en los antiguos navegadores 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);
  }
}

setRangeTextLa función le permite reemplazar la selección actual con el texto proporcionado o, si no hay selección, insertar el texto en la posición del cursor. Solo es compatible con Firefox, que yo sepa.

Para otros navegadores hay un comando "insertText" que solo afecta al elemento html actualmente enfocado y tiene el mismo comportamiento que setRangeText

Inspirado parcialmente por este artículo

Ramast
fuente
Esta es casi la forma correcta. El artículo que vinculó ofrece una solución completa como paquete: insert-text-at-cursor . Sin embargo, prefiero execCommandporque admite undoe hizo insert-text-textarea . No es compatible con IE pero es más pequeño
fregante
1
Desafortunadamente, execCommandMDN lo considera obsoleto: developer.mozilla.org/en-US/docs/Web/API/Document/execCommand No sé por qué, ¡parece ser realmente útil!
Richard
1
Sí, execCommand se usa para otros navegadores, para firefox se usa en su lugar la función setRangeText.
Ramast
Ramast, eso no es lo que hace tu código. Utilizará setRangeText en lugar de execCommand para cualquier navegador que lo defina (la mayoría). Para el comportamiento que describe, primero debe llamar a document.execCommand y luego verificar el valor de retorno. Si es falso, use target.setRangeText.
Jools
@Jools si setRangeText es compatible, ¿por qué no usarlo en lugar de execCommand? ¿Por qué necesito probar execCommand primero?
Ramast
10

La respuesta de Rab funciona muy bien, pero no para Microsoft Edge, así que también agregué una pequeña adaptación para 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
fuente
9

Me gusta JavaScript simple, y generalmente tengo jQuery alrededor. Esto es lo que se me ocurrió, basado en mparkuk :

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

Aquí hay una demostración: http://codepen.io/erikpukinskis/pen/EjaaMY?editors=101

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

fuente
4

Si el usuario no toca la entrada después de insertar el texto, el evento 'entrada' nunca se activa y el atributo de valor no reflejará el cambio. Por lo tanto, es importante activar el evento de entrada después de insertar texto mediante programación. Enfocar el campo no es suficiente.

La siguiente es una copia de la respuesta de Snorvarg con un disparador de entrada al 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 a plainjs.com por la función triggerEvent

Más información sobre el evento de entrada en w3schools.com

Descubrí esto mientras creaba un selector de emoji para un chat. Si el usuario solo selecciona algunos emojis y presiona el botón "enviar", el usuario nunca toca el campo de entrada. Al verificar el atributo de valor, siempre estaba vacío, a pesar de que los unicodes de emoji insertados estaban visibles en el campo de entrada. Resulta que si el usuario no toca el campo, el evento 'input' nunca se disparó y la solución fue activarlo de esta manera. Me llevó bastante tiempo resolver esto ... espero que ahorre algo de tiempo.

Jette
fuente
0

Publicar la función modificada para referencia propia. Este ejemplo inserta un elemento seleccionado del <select>objeto y coloca el cursor entre las etiquetas:

//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
fuente
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
fuente
Si bien esto puede responder a la pregunta, es mejor explicar las partes esenciales de la respuesta y posiblemente cuál fue el problema con el código OP.
pirho
0

El siguiente código es una adaptación de TypeScript del paquete https://github.com/grassator/insert-text-at-cursor de 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
fuente
-1

Lo cambió a 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
fuente
3
Eso va a llegar al DOM mucho más de lo que necesita ... almacenar myfieldcomo local es mucho mejor para el rendimiento
TMan
2
Wow, realmente demasiada repetición de document.getElementById(myField)! Hazlo una vez en la parte superior y usa un nombre de variable. ¿Cuántas veces seguidas tiene la intención de buscar redundantemente el mismo elemento?
doug65536