21

I have a <textarea> element that I listen for certain key presses from. Like if a user types the Tab key, I prevent the default action of changing focus and add the tab character at the correct position.

The problem is when users press one of the keys I listen for, undo goes a little haywire. How do I get the undo/redo functionality to work? I thought about listening for ctrl/cmd-z and ctrl/cmd-shift-z key presses, recording everything, and processing the undos/redos, but then the edit and context menu choices wouldn't work...

You can see by typing letters with tabs and enters and then trying to undo and redo:

const textarea = document.querySelector('textarea')
textarea.addEventListener('keydown', function (event) {
  if (event.key == "Tab") {
    event.preventDefault()
    const cursor = textarea.selectionStart
    textarea.value = textarea.value.slice(0, cursor) + '\t' + textarea.value.slice(textarea.selectionEnd)
    textarea.selectionStart = textarea.selectionEnd = cursor + 1
  } else if (event.key == "Enter") {
    event.preventDefault()
    const cursor = textarea.selectionStart
    textarea.value = textarea.value.slice(0, cursor) + '\n' + textarea.value.slice(textarea.selectionEnd)
    textarea.selectionStart = textarea.selectionEnd = cursor + 1
  }
})
<textarea cols="50" rows="20"></textarea>
at.
  • 50,922
  • 104
  • 292
  • 461
  • it's working for me when i run it above, i'm using firefox – inarilo Jun 10 '17 at 09:28
  • it's working for me also same code, Google Crome Version 59.0.3071.86 (Official Build) (64-bit) – Santosh Jun 10 '17 at 09:30
  • 1
    That's interesting it's working for you guys. It consistently fails for me on Google Chrome Version 59.0.3071.86 (Official Build) (64-bit), on macOS 10.12.5. I just tried on the latest Firefox and undo/redo works great! – at. Jun 10 '17 at 19:26

1 Answers1

16

I believe the problem's core is the lack of interaction between JavaScript and the browser's default methods of undoing. Appending text using JavaScript to a textarea does not in any way tell the browser's "undo" to delete the appended text, as the browser's "undo" is only meant for removing text the user inputted, not text JavaScript inputted.

Take for example your code. Upon pushing Enter, you tell the event listener to preventDefault, which altogether prevents the Enter key from appending user input to the textarea. You then synthesize the input using JavaScript, which the browser's "undo" does not keep track of.

You can overcome this lack of interaction by using document.execCommand(). You can check it's browser support via the link.

const textarea = document.querySelector('textarea');

textarea.addEventListener('keydown', function (event) {
  const cursor = textarea.selectionStart;
  if (event.key === "Tab") {
    event.preventDefault();
    // appends a tab and makes the browser's default undo/redo aware and automatically moves cursor
    document.execCommand("insertText", false, '\t');
  } else if (event.key === "Enter") {
    event.preventDefault();
    document.execCommand("insertText", false, '\n');
  }
});
<textarea cols="50" rows="8"></textarea>
user3840170
  • 26,597
  • 4
  • 30
  • 62
Siracle
  • 332
  • 2
  • 10
  • I was unaware of the `document.execCommand` mechanism to insert a string at the cursor position! I played with it a bit in Chrome 59, `delete` works a little funny, but in general it does seem to make undo/redo work! Thanks. – at. Jun 11 '17 at 08:36
  • 4
    It would be fantastic if Stackoverflow would have the `Tab` part in their editor! – Avatar Sep 17 '18 at 12:09
  • Heavily browser dependent indeed, `execCommand` is supposed to work on Firefox but this specific snippet won't work there (i'm using v75.0). The issue is known for [ages](https://bugzilla.mozilla.org/show_bug.cgi?id=1220696) – Matteljay Apr 25 '20 at 14:38
  • 3
    According to MDN, `execCommand()` is deprecated now. From the [W3C Draft](https://w3c.github.io/editing/docs/execCommand/): *"This spec is incomplete and it is not expected that it will advance beyond draft status"* (According to Git, this has been the case since 7 Aug 2015.) That being said, here's a related question which doesn't show up in the "Related" sidebar: https://stackoverflow.com/q/13597007 – user2009388 Jun 06 '21 at 08:27
  • Please update this answer!!! execCommand is deprecated and should NOT be used. – Zordid Mar 22 '23 at 07:17
  • 1
    @Zordid Please enlighten us what should be used instead. – user3840170 Apr 02 '23 at 21:45
  • 1
    I don't know. It just sucks massively to find outdated answers on SO that immediately create NEW problems. – Zordid May 22 '23 at 09:48