fundo
v1.2.1
Published
Command undo/redo for user interfaces to keep server state in sync with local
Downloads
8
Readme
Fundo - Undo/Redo
Table of Contents
Introduction
Fundo is an undo/redo library for user interfaces that helps keep your remote server in sync with the local state.
Commands in fundo are aware of the difference between local and remote state. When commands are executed they automatically modify the application's local state. Any remote systems that need to stay in sync are notified only when appropriate. For user interfaces this is usually after some debouncing period after the user has stopped interacting with the application.
Installation
Install with npm, yarn, etc.
npm install fundo
yarn add fundo
Usage
import { UndoBuffer, UndoCommand } from 'fundo'
// create an undo buffer
const buffer = new UndoBuffer()
// add commands
const command = new UndoCommand(myFowardCommand, myReverseCommand)
buffer.addOrUpdate(command)
const anotherCommand = new UndoCommand(anotherFowardCommand, anotherReverseCommand)
buffer.addOrUpdate(anotherCommand)
// undo/redo
buffer.undo()
buffer.redo()
Details
Fundo maintains a history of commands, which are implemented by you.
Defining Commands
Create your own commands by deriving from the basic Fundo Command
class.
import { set } from 'lodash'
import { Command } from '@/fundo'
class SetPropertyCommand extends Command {
// Store anything you need; it's your command
constructor(obj, property, value) {
super()
this.obj = obj
this.property = property
this.value = value
}
// Commands can be combined with eachother. This method tells fundo if another
// command can be combined with this one
canUpdateFrom(other) {
return (other instanceof SetPropertyCommand
&& other.obj === this.obj
&& other.property === this.property)
}
// Update this command with the data from a different command
update(other) {
this.value = other.value
}
// This method should affect the local application state
execute(context) {
set(this.obj, this.property, this.value)
}
// This method should affect the remote state(s)
executeRemote(context) {
// send this change to the remote system
}
}
A Command
can override 4 methods:
execute()
- Make the changes on the local data modelexecuteRemote()
- Notify remote systems of the changes from this commandcanUpdateFrom()
- Called to determine if this command can be combined with another commandupdate()
- Update the state of this command with data from another command
Using UndoBuffer
Command history is managed by an UndoBuffer
.
import { UndoBuffer, UndoCommand } from 'fundo'
const obj = {prop: 0}
const undoBuffer = new UndoBuffer()
const forwardCommand = new SetPropertyCommand(obj, 'prop', 123)
const reverseCommand = new SetPropertyCommand(obj, 'prop', obj.prop)
const undoCommand = new UndoCommand(forwardCommand, reverseCommand)
undoBuffer.addOrUpdate(undoCommand)
To add a new entry to the UndoBuffer
you must combine 2 Command
s into a single UndoCommand
. The first command will be executed when the history moves forward, toward the UndoCommand
, and the second command will be executed when the history moves in reverse, away from the UndoCommand
.
Once you have an UndoCommand
object you can addOrUpdate
it to the UndoBuffer
. The addOrUpdate
method will either add the UndoCommand
to the history or update the current front of the history if possible.
Once the UndoBuffer
has some commands in it you can move back and forth through history and your local state and remote state will stay in sync.
undoBuffer.undo()
undoBuffer.redo()
Execution Contexts
Your commands may need external application state in order to execute. You can provide an execution context as an option to the UndoBuffer
constructor.
new UndoBuffer({ executionContext: myContextObject })
This context object will be provided to every command's execute
and executeRemote
method that was invoked from this UndoBuffer
instance.
For more fine-grained control, pass a context to the constructor of individual UndoCommand
instances.
new UndoCommand(fowardCommand, reverseCommand, { executionContext: myContextObject })
Context objects given to UndoCommand
instances override those that provided to the UndoBuffer
for that command.
API
UndoCommand
Methods
constructor(forward, reverse, options)
Creates a new UndoCommand
. Create a new instance of UndoCommand
every time you want to add or update a command in the undo history.
Args
forward
- TheCommand
to be used when moving forward, into thisUndoCommand
, in historyreverse
- TheCommand
to be used when moving backward, away from thisUndoCommand
, in historyoptions
committed
- Whether or not this command has already been "committed" (sent to the server). Default isfalse
. This can be useful when you need to send and receive the result of a remote command before you are able to construct the undo commands.commitDebounce
- How long to wait, in milliseconds, without any updates before sending the most recent command to the remote system. Use this to debounce use inputs like text inputs, sliders, drag-and-drop, etc. Default is 0, which causes the command to execute it's remote command immediately.executionContext
- An object to pass to eachexecute
andexecuteRemote
method of the given forward and reverse commands. This overrides any context given to theUndoBuffer
.
UndoBuffer
Properties
canUndo
This will be true
if there are commands than can be undone, false
otherwise.
canRedo
This will be true
if there are commands than can be redone, false
otherwise.
Methods
constructor(options)
Creates a new UndoBuffer
instance. Create a new one of these for every record of history that you need.
Args
options
executionContext
- An object to pass to eachexecute
andexecuteRemote
method of all commands executed by this undo buffer.
addOrUpdate(undoCommand, options)
If the given command is compatible with the most recent command in the undo history then that command will be updated with this one, otherwise this command will be added to the history.
A command is considered "compatible" if the existing command in history has never been sent to the remote system and it's canUpdateFrom
returns true
.
Args
undoCommand
- TheUndoCommand
instance to add or update into history.options
autoCommit
- Setfalse
to disable auto-commit for this command. See below.
autoCommit
By default commands will commit themselves to the remote system after the debounce delay (given with the commitDebounce
option to UndoCommand
). You can disable this behaviour by setting the autoCommit
option to false
. Note that this will not cancel the debounce timer on an existing command added with auto commit.
Disabling autoCommit
allows you to gain manual control over when a command's remote execute is invoked. Add your commands with autoCommit: false
until they are ready to commit. Then add another one with autoCommit: true
to start the commit process.
Adding a new UndoCommand
with the committed
option set to true
is similar to using autoCommit
. The difference is that autoCommit
still allows the command to execute its remote commands for the first time, whereas using committed: true
will only execute the remote commands when the command is the target of a "redo" operation.
undo()
Reverses the most current command in the undo history.
redo()
Re-applies the command immediately following the current command in history.
Command
The Command
is the basic unit of work in Fundo. Derive your own class from Command
and override the following methods.
Methods
canUpdateFrom(otherCommand)
Called when the UndoBuffer
needs to determine if 2 commands are compatible. If you return true
from this method then the otherCommand
will be sent to the update()
method.
If not implemented then this command cannot be combined with any other command.
update(otherCommand)
Called to update this command with the data from another, compatible command. This method should take any steps necessary to ensure that this command will perform its actions as well as those of otherCommand
.
execute(context)
Called when this command should affect the local state of the application.
executeRemote(context)
Called when this command should affect remote systems.