fundo

1.2.1 • Public • Published

Fundo - Undo/Redo

npm license size coverage

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 model
  • executeRemote() - Notify remote systems of the changes from this command
  • canUpdateFrom() - Called to determine if this command can be combined with another command
  • update() - 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 - The Command to be used when moving forward, into this UndoCommand, in history
  • reverse - The Command to be used when moving backward, away from this UndoCommand, in history
  • options
    • committed - Whether or not this command has already been "committed" (sent to the server). Default is false. 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 each execute and executeRemote method of the given forward and reverse commands. This overrides any context given to the UndoBuffer.

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 each execute and executeRemote 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 - The UndoCommand instance to add or update into history.
  • options
    • autoCommit - Set false 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.

Readme

Keywords

Package Sidebar

Install

npm i fundo

Weekly Downloads

10

Version

1.2.1

License

MIT

Unpacked Size

86.3 kB

Total Files

17

Last publish

Collaborators

  • sweetgum-admin