Search code examples
javascriptjquerypasteundo

Undo an overridden paste in JS


I have overridden the paste event. I noticed that because the event's default behavior is prevented, it is not currently possible to undo the "paste" with Ctrl+Z.

$(this).on('paste', function (evt) {
  // Get the pasted data via the Clipboard API.
  // evt.originalEvent must be used because this is jQuery, not pure JS.
  // https://stackoverflow.com/a/29831598
  var clipboardData = evt.originalEvent.clipboardData || window.clipboardData;
  var pastedData = clipboardData.getData('text/plain');

  // Trim the data and set the value.
  $(this).val($.trim(pastedData));

  // Prevent the data from actually being pasted.
  evt.preventDefault();
});

Is there a way to override the undo functionality or do the above differently such that Ctrl+Z will work?

Related questions


Solution

  • I found a way to make it work. Starting with this answer, I changed it to use .focus() instead of .select(), which fixes the pasting. Then, to make pasting work in Firefox, I had to keep the fallback that doesn't preserve undo history. This will have to do until Firefox fixes the bug (See bug report).

    Update: The Firefox bug was fixed in Firefox 89

    function insertAtCaretTrim(element, text) {
        element[0].focus();
        // Attempt to preserve edit history for undo.
        var inserted = document.execCommand("insertText", false, $.trim(text));
      
        // Fallback if execCommand is not supported.
        if (!inserted) {
            var caretPos = element[0].selectionStart;
            var value = element.val();
    
            // Get text before and after current selection.
            var prefix = value.substring(0, caretPos);
            var suffix = value.substring(element[0].selectionEnd, value.length);
    
            // Overwrite selected text with pasted text and trim. Limit to maxlength.
            element.val((prefix + $.trim(text) + suffix).substring(0, element.attr('maxlength')));
    
            // Set the cursor position to the end of the paste.
            caretPos += text.length;
            element.focus();
            element[0].setSelectionRange(caretPos, caretPos);
        }
    }
    
    var $inputs = $("input");
    
    $inputs.each(function () {
        $(this).on('paste', function (evt) {
        var clipboardData = evt.originalEvent.clipboardData || window.clipboardData;
        var pastedData = clipboardData.getData('text/plain');
    
        // Trim the data and set the value.
        insertAtCaretTrim($(this), pastedData);
        
        evt.preventDefault();
      });
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <input type="text" maxvalue="10" />

    Code is also in a JSFIddle: https://jsfiddle.net/mf8v97en/5/