Как я могу отменить программную вставку текста в текстовую область?

У меня есть текстовое поле и кнопка. При нажатии кнопки текст вставляется в текстовое поле.

Есть ли способ разрешить пользователю нажимать Ctrl/Cmd+z, чтобы отменить вставку текста и вернуть текстовую область в ее предыдущее состояние?


person sluther    schedule 28.11.2012    source источник


Ответы (11)


Я думаю, что самый простой способ использовать стек отмены браузера вместо захвата событий.

Для этого нужно использовать разные коды для разных браузеров. К счастью, из всех основных браузеров только у Firefox есть другой подход.

// http://stackoverflow.com/a/9851769/529024
// Opera 8.0+
var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;

// Firefox 1.0+
var isFirefox = typeof InstallTrigger !== 'undefined';

// Safari 3.0+ "[object HTMLElementConstructor]" 
var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 || (function(p) {
  return p.toString() === "[object SafariRemoteNotification]";
})(!window['safari'] || safari.pushNotification);

// Internet Explorer 6-11
var isIE = /*@cc_on!@*/ false || !!document.documentMode;

// Edge 20+
var isEdge = !isIE && !!window.StyleMedia;

// Chrome 1+
var isChrome = !!window.chrome && !!window.chrome.webstore;



var position = 0;

// text to anser
var text = 'Inserted Text';

// Just for fun :)
if (isFirefox)
  text = "  __ Firefox __ ";
else if (isIE)
  text = "  __ IE __ ";
else if (isEdge)
  text = "  __ Edge __ ";
else if (isSafari)
  text = "  __ Safari __ ";
else if (isOpera)
  text = "  __ Opera __ ";
else if (isChrome)
  text = "  __ Chrome __ ";

/* Adding text on click based on browser */
jQuery(".addText").on("click", function() {
  if (isFirefox) {
    // Firefox
    var val = jQuery(".textArea").val();

    var firstText = val.substring(0, position);
    var secondText = val.substring(position);

    jQuery(".textArea").val(firstText + text + secondText);
  } else {

    jQuery(".textArea").focus();
    var val = jQuery(".textArea").val();

    jQuery(".textArea")[0].selectionStart = position;
    jQuery(".textArea")[0].selectionEnd = position;

    document.execCommand('insertText', false, text);
  }
});

jQuery(".textArea").on("focusout", function(e) {
  position = jQuery(this)[0].selectionStart;
});
textarea {
  padding: 10px;
  font-family: Calibri;
  font-size: 18px;
  line-height: 1.1;
  resize: none;
}
.addText {
  padding: 5px 15px;
  transition: all 0.5s;
  border: 1px solid black;
  border-radius: 2px;
  background-color: #169c16;
  width: 70px;
  margin: 10px 0;
  color: white;
  cursor: pointer;
}
.addText:hover {
  background-color: #2776b9;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea name='textArea' class='textArea' rows="7" cols="50">Suspendisse convallis, metus at laoreet congue, sapien dui ornare magna, a porttitor felis purus a ipsum. Morbi vulputate erat rhoncus, luctus neque ut, lacinia orci. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis
  egestas. Fusce aliquam, nulla nec fringilla ultrices, ipsum lectus maximus nisl, ut laoreet purus lectus eget nisl. Duis blandit cursus nulla. Vestibulum consectetur, nunc non viverra condimentum, neque neque tincidunt diam, nec vestibulum neque nisi
  ac sem. Integer aliquam a leo id laoreet. Mauris ultrices mauris lorem, eu hendrerit odio volutpat ut. Nam eget hendrerit metus.</textarea>
<div class='addText'>
  Add Text
</div>

Проверено на:

  • FF: 50
  • Хром: 55
  • Край 38
  • Сафари: 5.1
  • Опера: 42
person Kalimah    schedule 19.01.2017
comment
Спасибо, это идеальный ответ. Другие пользователи предложили переписать встроенную функцию отмены в браузере, что мне не понравилось. - person Peter; 19.01.2017
comment
По-видимому, FF теперь сбрасывает историю отмены для текстовых полей при манипулировании значением. В FF есть соответствующий отчет об ошибке, в котором предлагается правильно реализовать команду insertText: bugzilla.mozilla. org/show_bug.cgi?id=1220696 Надеюсь, в будущем мы сможем просто использовать команду insertText для всех браузеров. - person user2015253; 01.10.2018

Вам нужно вставить текст особым образом, чтобы пользователь мог использовать обычное поведение отмены/возврата.

var textEvent = document.createEvent('TextEvent');

textEvent.initTextEvent('textInput', true, true, null, "new text");

document.getElementById("your-textarea").dispatchEvent(textEvent);
person alex    schedule 28.11.2012
comment
Это не работает для меня в Firefox (хотя работает в Chrome). В противном случае это идеально. Единственное предостережение здесь заключается в том, что вам нужно вызвать focus() в текстовой области, прежде чем вызывать для нее dispatchEvent(), иначе ничего не произойдет. Любая идея, как я могу заставить его работать в Firefox? - person sluther; 28.11.2012
comment
@sluther Пока нет лучшей поддержки браузера, но это путь в будущее. Вы можете создать свой собственный буфер истории и фиксировать события отмены/повтора и извлекать оттуда текст. - person alex; 28.11.2012
comment
Похоже, Mozilla отключила эту функцию из соображений безопасности. Ле вздох... :( - person sluther; 28.11.2012
comment
Путь будущего должен быть UndoManager, но немного кажется, в последнее время был достигнут прогресс. - person Tim Down; 08.11.2014
comment
Где я могу найти документацию для этого? Я, должно быть, неправильно ищу, так как я придумал очень мало. - person user1978019; 30.03.2015
comment
Приведенный выше код вставлял текст в начало текстового поля. Мне пришлось сначала выбрать все, чтобы заменить содержимое, используя: textbox.setSelectionRange(0, textbox.value.length); - person SeBsZ; 08.09.2016
comment
Согласно Chrome, этот метод скоро перестанет работать: chromestatus.com/feature/5718803933560832 - person SeBsZ; 08.09.2016

Сохраните исходное значение textarea в его data:

var $textarea = $('textarea');

$('button').on('click', function () {
    var val = $textarea.val();

    $textarea.data('old-val', val).val(val + ' some text');
});

Если вам нужен массив данных (как предложил @ahren), используйте это:

var $textarea = $('textarea');

$('button').on('click', function () {
    var val = $textarea.val();

    if ( ! $textarea.data('old-val')) {
        $textarea.data('old-val', []);
    }

    $textarea.data('old-val').push(val);

    $textarea.val(val + ' some text');
});
person Joseph Silber    schedule 28.11.2012
comment
или даже в виде массива, и тогда вы построили себе «историю». - person ahren; 28.11.2012

$("#target").keypress(function(event) {
  if ( event.which == 'Z' && first press == 'Cmd' && second press == 'Ctrl') {//check for key press
     event.preventDefault();
    text.value = defaultValueForTextField
   }
});

Это должно быть то, что вы ищете. Первое и второе нажатие нужно будет сохранить, так как вы хотите комбо-прессы. Однако вам действительно нужно будет сохранить текстовое значение по умолчанию.

person Tony    schedule 28.11.2012
comment
Я не совсем уверен, как использовать этот фрагмент, так как мне не кажется, что вы обнаруживаете здесь два одновременных нажатия клавиш. Я ошибаюсь? - person sluther; 28.11.2012

Даже если этот вопрос год назад, я тоже хочу поделиться своим способом.

Вы можете сделать это следующим образом:

    $(function () {

  // Check to see if an array is not already defined.
  if (!$('#t').data('old-val')) {

    // If the check returns True we proceed to create an array in old-val.
    $('#t').data('old-val', []);

  }

  // Get the current content value.
  inputValue = $('#t').val();

  // Push it to the old-val array.
  $('#t').data('old-val').push(inputValue);

  // We start with a current array position of 0.
  curArrPos = 0;


  $('#c').click(function () {
    // Append a string to the #t.
    $('#t').val(' ==this is the 2nd appended text==');


    // Save the current content value.
    inputValue = $('#t').val();
    // Push it to the array.
    $('#t').data('old-val').push(inputValue);
    // Increment current array position.
    ++curArrPos;

  });


  $('#b').click(function () {
    // Append a string to the #t.
    $('#t').val(' ==this is the 1st appended text==');


    // Save the current content value.
    inputValue = $('#t').val();
    // Push it to the array.
    $('#t').data('old-val').push(inputValue);
    // Increment current array position.
    ++curArrPos;

  });

  $('#undo').click(function () {
    // First check that the old-val array length is greater than 1 (It's the initial position. No need undoing to a blank state) and current array position greater than 0 (for the same reason).
    if ($('#t').data('old-val').length > 1 && curArrPos > 0) {

      // Set current #t value to the one in the current array position, minus one.
      // Minus one gets you to the previous array position (ex. current=5; previous= current - 1 = 4).
      $('#t').val($('#t').data('old-val')[curArrPos - 1]);

      // Decrease current array position, because we effectively shifted back by 1 position.
      --curArrPos;
    }
  });

  $('#redo').click(function () {
    if (currentArrayPos < $('#c').data('old-val').length - 1) {

      $('#t').val($('#t').data('old-val')[curArrPos + 1]);

      // Increase current array position, because we effectively shifted forward by 1 position.
      ++curArrPos;

    }
  });

});

Вот скрипт, если вы хотите поэкспериментировать с ним http://jsfiddle.net/45Jwz/1/

Я написал такой код для лучшего понимания, но вы, конечно, должны писать код лучше и менее многословно, чем этот.

person Community    schedule 04.05.2014

Конкретные библиотеки и технологии сильно зависят от вашего стека.

Есть 2 основных способа, о которых я могу думать мгновенно.

Во-первых: сохраните предыдущее состояние в вашем контроллере. Подключитесь к ярлыку и замените содержимое на предыдущее состояние. Если внесены другие изменения, удалите крючок. Более или менее ваш подход 2013 года

Это быстрый способ, и он не очень хорошо работает, если вам нужен стек с более чем одной историей редактирования.

Второе: следите за текстовым вводом и периодически сохраняйте состояние в стеке. Подключиться к ярлыку. (Возьмите на себя весь процесс). Это более чистый способ, потому что ваша модификация концептуально такая же, как и пользовательские модификации.

Это может быть довольно просто с архитектурой redux/flux http://redux.js.org/docs/recipes/ImplementingUndoHistory.html

Для захвата cmd/ctrl+z вы можете заглянуть в https://github.com/madrobby/keymaster.

Если вы немного подробнее расскажете о своем стеке/требованиях, я был бы рад расширить этот ответ.

person Harper04    schedule 16.01.2017

Вот мысль:

Если мы сможем генерировать события keyborad так же, как если бы пользователь печатал в текстовом поле, тогда браузер автоматически сможет обрабатывать событие Undo. Поэтому вместо того, чтобы просто добавлять/изменять значение textarea, мы должны попытаться имитировать события клавиатуры для текста, который мы хотим вставить.

Согласно документации в MDN (ссылки приведены ниже), мы можем использовать объект KeyboardEvent для генерации таких событий, как:

  var e1 = new KeyboardEvent(<type>, <details>);
  var b1 = <textbox>.dispatchEvent(e1);

Где:

  • <type> представляет тип события, например keydown, keypress или keyup
  • <details> представляет объект, имеющий сведения о событии, такие как key, code
  • <textbox> представляет целевое текстовое поле, в котором мы хотим инициировать событие

Вот JSFiddle, где я пытался смоделировать события keydown, keypress и keyup для каждого символ в заданной строке. Хотя он запускает соответствующие обработчики событий, символы почему-то не отображаются/не добавляются в текстовое поле.

Что я заметил, так это то, что есть некоторые различия в объекте события, сгенерированном, когда я набираю a в стихах текстового поля, когда я имитирую 3 события для a, используя свой код. Различия (при тестировании в Firefox 50.1.0):

  1. explicitOriginalTarget отличается от originalTarget, когда я имитирую события; и когда я набираю текстовое поле, оба имеют одинаковое значение
  2. rangeParent и rangeOffset значения равны null/0, когда я печатаю текстовое поле; и когда я моделирую события, они имеют некоторые значения
  3. свойство isTrusted равно true, когда я набираю текстовое поле; и когда я моделирую события, это false (для любого события, сгенерированного с использованием скрипта, оно будет иметь false)

МДН ссылки:

person Vivek Athalye    schedule 18.01.2017
comment
Это интересный подход, и я был бы более рад имитировать нажатия клавиш, чем переписывать функциональность отмены, как предлагают другие люди. - person Peter; 18.01.2017

Самый простой способ выглядит так:

  • сохранить предыдущее содержимое текстовой области при нажатии кнопки в атрибуте самой текстовой области DOM.
  • перехватывать клавиатуру на уровне BODY (вы не знаете, где будет фокус, когда и если будет нажат Ctrl-Z)
  • (необязательно) также перехватывать столько событий от разных объектов, сколько нужно: ввод, нажатие клавиши и т. д., в том числе на уровне BODY.

В настоящее время:

  • когда пользователь нажимает кнопку, старое содержимое сохраняется, а для пользовательского «грязного» атрибута текстовой области устанавливается значение true
  • если пользователь нажимает Ctrl-Z или Cmd-Z, процедура отмены проста (в jQuery это будет $('#idOfTextarea').val($('#idOfTextarea').attr('prevContents')); . Процедура отмены также очищает «грязный» атрибут, поэтому отмена не вызывается дважды.
  • (необязательно), если пользователь изменяет другое поле, щелкает в другом месте и т. д., атрибут dirty текстового поля также очищается, и изменение становится невозможным.
  • (необязательные) объекты с собственной функцией отмены могут/должны останавливать распространение события, чтобы другие операции отмены не имели двух эффектов.

Вы можете хотеть или нет также перехватывать onChange в текстовой области. Если onChange срабатывает, а грязный не установлен, это начальный щелчок кнопки, и его можно игнорировать. В противном случае это может указывать на то, что пользователь добавил некоторые собственные изменения в текст, по которому щелкнул мышью. В этом случае вы можете отключить отмену, чтобы сохранить эти изменения, или запросить подтверждение у пользователя.

person LSerni    schedule 18.01.2017
comment
Хотя да, это простая идея, вы собираетесь переписать нативную функциональность браузера. Интересно, есть ли способ сообщить браузеру, что мы изменили текст, добавьте его в историю отмен? - person Peter; 18.01.2017
comment
Я искал это в Google, но, видимо, он не поддерживается (из соображений безопасности?). Если это не является частью стандарта, вам придется справиться с несколькими альтернативными способами, и вам все равно понадобится последний путь... - person LSerni; 18.01.2017

Вы можете сделать так,

HTML

<div id="text-content">
 <textarea rows="10" cols="50"></textarea>
   <input type="button" name="Insert" value="Insert" />
</div>

JQuery

var oldText = [],ctrl=false;
   $(function(){
      $("input[name=Insert]").click(function(){
          var text = oldText.length+1;
          oldText.push(text);
          var textAreaText = $('textarea').val();
          textAreaText +=text;
          $('textarea').val(textAreaText);
          $("input[name=insertText]").val('');
     });
     $('textarea').keydown(function(e){
            if(e.which == 17){ ctrl = true;}
     });
     $('textarea').keyup(function(e){
         var c = e.which;
         if(c == 17){ ctrl = false;}
         if(ctrl==true && c==90 ){
            oldText.pop(); 
            var text = '';
            $.each(oldText,function(i,ele){
               text += ele;
            });
            $('textarea').val(text);
        }
     })
  })

Вы также можете проверить и поэкспериментировать с скрипкой.

person Tonmoy Nandy    schedule 18.01.2017

Сначала добавьте html, и вы сможете использовать событие нажатия клавиши для отмены

вы также можете попробовать здесь http://jsfiddle.net/surendra786/1v5jxaa0/

            <input type="text" class="reset actor_input" name="actor" value="add actors"></input>

            <input type="text" name="actors"></input>

            <div class="found_actors"></div>

            <div id="add" class="button_content">ADD</div>

            <div id="undo" class="button_content">UNDO</div>

            <div class="actors_list"><textarea readonly style="resize: none;" rows="20" cols="20" name="actors-list"></textarea></div>

        </div>

**затем добавьте jquery**

    var items = [];

$("#add").click(function() {
    // Push the new actor in the array
    items.push($("[name='actor']").val());
    populate();
});



$(document).keydown(function(e){
       if( e.which === 90 && e.ctrlKey ){
         console.log('control + z'); 
         if (items.length > 0) {
        // remove last element of the array
        items.splice(-1,1);
        populate();
    }
      }          
}); 

populate = function() {
    $("[name='actors-list']").text('');
    $("[name='actors-list']").append(items.join('&#13;&#10;'));
    $("[name='actors']").val(items.join(','));   
}

вы можете попробовать здесь http://jsfiddle.net/surendra786/1v5jxaa0/

это работает для меня

person Surendra Kumar Ahir    schedule 18.01.2017

Здесь есть много применимых ответов, которые будут работать для того, что вы хотите. Вот что бы я сделал на вашем месте. Это позволяет изменить текстовую переменную, которую вы, скорее всего, будете использовать, вы можете установить ее или пользователь может установить ее с помощью другого поля и т. д.

кодировать здесь

Суть этого была бы примерно такой.

$(document).ready(function(){
  function deployText(){
    var textArray = [];
    var textToAdd = 'let\'s go ahead and add some more text';
    var textarea = $('textarea');
    var origValue = textarea.text();
    textArray.push(origValue);

    $('button').on('click', function(e){
      textArray.push(textToAdd);
      textarea.text(textArray);
      console.log(textArray.length);
      console.log(textArray);
    });

    $(document).on('keypress', function(e){
      var zKey = 26;
      if(e.ctrlKey && e.which === zKey){
        removePreviousText();
      }
    })

    function removePreviousText(){
      console.log(textArray);
      if(textArray.length > 1){
        textArray.pop();
        $('textarea').text(textArray);
      }
    }
  }
  deployText()
})
person Caleb Swank    schedule 19.01.2017