Comment insérer du texte dans la zone de texte à la position actuelle du curseur?

126

Je voudrais créer une fonction simple qui ajoute du texte dans une zone de texte à la position du curseur de l'utilisateur. Cela doit être une fonction propre. Juste les bases. Je peux comprendre le reste.

JoshMWilliams
la source
3
duplication possible de Comment insérer du texte là où se trouve le curseur?
Derek 朕 會 功夫
2
Jetez un œil à cette réponse déjà publiée: stackoverflow.com/questions/4456545/…
John Culviner
Si vous recherchez un module simple avec support d'annulation, essayez insert-text-textarea . Si vous avez besoin du support IE8 +, essayez le package insert-text-at-cursor .
fregante

Réponses:

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
la source
19
pour corriger "perd la position du curseur": ajouter insérez ces lignes avant} else { myField.selectionStart = startPos + myValue.length; myField.selectionEnd = startPos + myValue.length;
user340140
10
Merci Rab pour la réponse et @ user340140 pour le correctif. Voici un exemple fonctionnel .
Znarkus
@ user340140, votre correction de "perte de potition de caret", ne fonctionne que si je mets l'accent sur l'entrée juste avant les lignes que vous suggérez. Il semble impossible de modifier la sélection sur un champ non ciblé, du moins dans Chrome (version actuelle 62.0)
Jette
Il y a un problème mineur avec ce code: selectionStartest une valeur numérique, et devrait donc être comparé à 0et non '0', et devrait probablement utiliser===
Herohtar
82

Cet extrait de code pourrait vous aider dans quelques lignes 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
la source
Génial! Fonctionne également avec 1.6 avec des modifications mineures.
Şerban Ghiţă
1
Mais il ne peut pas remplacer le texte sélectionné
Sergey Goliney
@mparkuk: il souffre toujours du problème "perd la position du curseur" mentionné ci-dessus par user340140. (Désolé, je devrais le réparer, mais je n'ai plus de temps.)
jbobbins
4
Merci de fournir un violon fonctionnel. Je l'ai mis à jour pour réinitialiser également la position du curseur et en faire un plugin jquery: jsfiddle.net/70gqn153
Freedomn-m
Cela fonctionne mais le curseur se retrouve au mauvais endroit.
AndroidDev
36

Pour un bon 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
la source
très belle extension! fonctionne comme prévu. Merci!
Martin Johansson
Meilleure solution! Merci
Dima Melnik
4
Ce n'est pas une bonne idée d'étendre le prototype d'objets que vous ne possédez pas. Faites-en une fonction régulière et cela fonctionne aussi bien.
fregante
Cela efface le tampon d'annulation de l'élément d'édition après le réglage this.value = .... Y a-t-il un moyen de le préserver?
c00000fd
19

Nouvelle réponse:

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

Je ne suis pas sûr de la prise en charge du navigateur pour cela.

Testé dans 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>

Ancienne réponse:

Une pure modification JS de la réponse d'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>

Testé dans Chrome 47, 81 et Firefox 76.

Si vous souhaitez changer la valeur du texte actuellement sélectionné pendant que vous tapez dans le même champ (pour un effet de saisie semi-automatique ou similaire), passez document.activeElementcomme premier paramètre.

Ce n'est pas la façon la plus élégante de le faire, mais c'est assez simple.

Exemples d'utilisations:

typeInTextarea('hello');
typeInTextarea('haha', document.getElementById('some-id'));
Jayant Bhawal
la source
vous n'avez pas fermé la ligne avec >>; <<
Phoenix
4
Les points-virgules @Phoenix sont facultatifs en Javascript. Fonctionne sans eux aussi. Cependant, vous pouvez modifier en point-virgule si vous le souhaitez. Pas de problème.
Jayant Bhawal
3
J'ai fait une démo sur JSFiddle. Il fonctionne également en utilisant Version 54.0.2813.0 canary (64-bit), qui est essentiellement Chrome Canary 54.0.2813.0. Enfin, si vous souhaitez qu'il s'insère dans la zone de texte par ID, utilisez document.getElementById('insertyourIDhere')à la place de eldans la fonction.
haykam
Quelle partie de ma réponse n'est pas JS "pur"? Ai-je oublié du C ++ là-dedans?
Erik Aigner
2
Salut @ErikAigner! Mon mauvais, je n'ai pas réalisé que cette question avait des réponses de deux Erik. Je voulais dire Erik Pukinskis. Je mettrai à jour la réponse pour mieux refléter cela.
Jayant Bhawal
15

Une solution simple qui fonctionne sur Firefox, Chrome, Opera, Safari et Edge mais ne fonctionnera probablement pas sur les anciens navigateurs 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 fonction vous permet de remplacer la sélection actuelle par le texte fourni ou, en l'absence de sélection, d'insérer le texte à la position du curseur. Il est uniquement pris en charge par Firefox pour autant que je sache.

Pour les autres navigateurs, il existe la commande "insertText" qui n'affecte que l'élément html actuellement focalisé et a le même comportement que setRangeText

Inspiré en partie de cet article

Ramast
la source
C'est presque la bonne manière. L'article que vous avez lié offre une solution complète sous forme de package: insert-text-at-cursor . Cependant, je préfère execCommandcar il prend en charge undoet fait insert-text-textarea . Pas de support IE mais plus petit
fregante
1
Malheureusement, execCommandest considéré comme obsolète par MDN: developer.mozilla.org/en-US/docs/Web/API/Document/execCommand Je ne sais pas pourquoi, cela semble vraiment utile!
Richard
1
Oui, execCommand est utilisé pour d'autres navigateurs, pour Firefox, la fonction setRangeText est utilisée à la place.
Ramast
Ramast, ce n'est pas ce que fait votre code. Il utilisera setRangeText plutôt que execCommand pour tout navigateur qui le définit (le plus). Pour le comportement que vous décrivez, vous devez d'abord appeler document.execCommand, puis vérifier la valeur de retour. Si c'est faux, utilisez target.setRangeText.
Jools
@Jools si setRangeText est pris en charge, pourquoi ne pas l'utiliser à la place d'execCommand? Pourquoi dois-je d'abord essayer execCommand?
Ramast
10

La réponse de Rab fonctionne très bien, mais pas pour Microsoft Edge, j'ai donc ajouté une petite adaptation pour Edge également:

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
la source
9

J'aime le javascript simple et j'ai généralement jQuery. Voici ce que j'ai trouvé, basé sur 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
})

Voici une démo: http://codepen.io/erikpukinskis/pen/EjaaMY?editors=101

Erik Pukinskis
la source
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>

la source
4

Si l'utilisateur ne touche pas l'entrée après l'insertion du texte, l'événement 'input' n'est jamais déclenché et l'attribut value ne reflétera pas la modification. Par conséquent, il est important de déclencher l'événement d'entrée après l'insertion de texte par programme. Se concentrer sur le terrain ne suffit pas.

Ce qui suit est une copie de la réponse de Snorvarg avec un déclencheur d'entrée à la fin:

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édit à plainjs.com pour la fonction triggerEvent

En savoir plus sur l'événement oninput sur w3schools.com

J'ai découvert cela en créant un sélecteur d'emoji pour une discussion. Si l'utilisateur sélectionne juste quelques emojis et clique sur le bouton "envoyer", le champ de saisie n'est jamais touché par l'utilisateur. Lors de la vérification de l'attribut de valeur, il était toujours vide, même si les unicodes emoji insérés étaient visibles dans le champ de saisie. Il s'avère que si l'utilisateur ne touche pas le champ, l'événement 'input' ne s'est jamais déclenché et la solution était de le déclencher comme ça. Il a fallu un certain temps pour comprendre celui-ci ... j'espère que cela permettra à quelqu'un de gagner du temps.

Jette
la source
0

Publication de la fonction modifiée pour votre propre référence. Cet exemple insère un élément sélectionné à partir de l' <select>objet et place le curseur entre les balises:

//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
la source
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
la source
Bien que cela puisse répondre à la question, il est préférable d'expliquer les parties essentielles de la réponse et éventuellement quel était le problème avec le code des OP.
pirho
0

Le code ci-dessous est une adaptation TypeScript du package https://github.com/grassator/insert-text-at-cursor par 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
la source
-1

Je l'ai changé en 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
la source
3
Cela va frapper le DOM bien plus qu'il n'en faut ... le stockage myfielden local est bien meilleur pour les performances
TMan
2
Wow, vraiment trop de répétitions document.getElementById(myField)! Faites-le une fois en haut et utilisez un nom de variable. Combien de fois de suite avez-vous l'intention de rechercher de manière redondante le même élément?
doug65536