Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a undo/redo function

I want to add a undo/redo function in my script. I have looked around and see some suggestions, most of them recommended to use command pattern.

The function must work over one page - after a reload of the page the function must able to redo/undo the last things.

I don't know how command pattern works, I think about to create a object, to store the a name of the function, the old and the new value - but I'm not sure if this is a efficient way to do this or not.

Maybe somebody could give me a small example how the code for a undo/redo function should look.

like image 256
RainerS Avatar asked Jan 29 '19 08:01

RainerS


People also ask

How do you implement undo redo?

If “UNDO” string is encountered, pop the top element from Undo stack and push it to Redo stack. If “REDO” string is encountered, pop the top element of Redo stack and push it into the Undo stack.

How do you add an undo button in Excel?

If there is no such a mark, it means you need to add it into your quick access toolbar. Click the rightmost arrow in the quick access toolbar, and choose the Undo item, then you will successfully add it into the quick access toolbar.

What is the formula for redo?

To redo something we have undone, we need to press the keyboard's shortcut key Ctrl + Y. Alternately. We can also press the F4 function key; however, we first need to press the Fn or F-Lock key to activate the function keys.

What is the function of undo and Redo command?

The undo function is used to reverse a mistake, such as deleting the wrong word in a sentence. The redo function restores any actions that were previously undone using an undo. Some people may refer to this feature as a reverse undo.


1 Answers

There's 2 common ways of implementing undo/redo:

  • The Memento Pattern, where you capture the whole current state. It's easy to implement, but memory-inefficient since you need to store similar copies of the whole state.
  • The Command Pattern, where you capture commands/actions that affect the state (the current action and it's inverse action). Harder to implement since for for each undoable action in your application you must explicitly code it's inverse action, but it's far more memory-efficient since you only store the actions that affect the state.

The Memento Pattern

Before an action is applied, you take a snapshot of the current state and save it into an array. That snapshot is the Memento.

If the user wants to undo, you simply pop the last memento and apply it. The program returns to the state it was before the last action was applied.

This pattern is memory intensive; each memento is relatively large since it captures the whole current state.

But it's also the easiest to implement since you don't need to explicitly code all cases and their inverse actions that you need to in the Command Pattern (see below).

const mementos = []
const input = document.querySelector('input')

function saveMemento() {
  mementos.push(input.value)
}

function undo() {
  const lastMemento = mementos.pop()
   
  input.value = lastMemento ? lastMemento : input.value
}
<h4> Type some characters and hit Undo </h4>
<input onkeydown="saveMemento()" value="Hello World"/>
<button onclick="undo()">Undo</button>

The Command Pattern

For each action the user takes, you also save a corresponding inverse action/command. For example, each time you add character in a textbox, you save the inverse function; which is to delete the character at that position.

If the user wants to undo, you pop the last inverse action/command and apply it.

const commands = []
const input = document.querySelector('input')

function saveCommand(e) {
  commands.push({
    // the action is also saved for implementing redo, which
    // is not implemented in this example.
    action: { type: 'add', key: e.key, index: input.selectionStart },
    inverse: { type: 'remove', index: input.selectionStart }
  })
}

function undo() {
  let value = input.value.split('')
  const lastCommand = commands.pop()
 
  if (!lastCommand) return
    
  switch (lastCommand.inverse.type) {
    case 'remove':
      value.splice(lastCommand.inverse.index, 1)
      break;      
  }
  
  input.value = value.join('')
}
<h4> Type some characters and hit Undo </h4>
<input onkeydown="saveCommand(event)" value="Hello World"/>
<button onclick="undo()">Undo</button>

The snippets I've written only work when adding characters, then hitting undo to return to the state before the characters were added so they are an oversimplification on how you should implement this.

Nevertheless I think they demonstrate the core concepts of both patterns.

FWIW I'm using UndoManager in my projects as a stack for my commands.

like image 164
nicholaswmin Avatar answered Oct 19 '22 11:10

nicholaswmin