fundo
v1.2.1
Published
Command undo/redo for user interfaces to keep server state in sync with local
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 Commands 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- TheCommandto be used when moving forward, into thisUndoCommand, in historyreverse- TheCommandto be used when moving backward, away from thisUndoCommand, in historyoptionscommitted- 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 eachexecuteandexecuteRemotemethod 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
optionsexecutionContext- An object to pass to eachexecuteandexecuteRemotemethod 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- TheUndoCommandinstance to add or update into history.optionsautoCommit- Setfalseto 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.
