
Oops.js is a JavaScript library that adds advanced undo and redo functionalities to your applications.
It implements the command pattern, supporting transactions, automatic command merging, and state snapshots, which allow you to create undo/redo systems comparable to industry-leading software like Figma, Photoshop, or Visual Studio Code.
At its core, Oops.js implements the command pattern, simplifying user actions management by treating each action as an encapsulated object. This allows you to easily extend functionality without complicated code changes. The library offers various advanced features, including:
- Transaction Support: It groups multiple actions into a single operation. This can be useful when users need to perform multiple steps that should be undone or redone.
- Automatic Command Merging: By intelligently merging commands executed within a specific time window, Oops.js minimizes redundant actions. For instance, multiple minor edits in rapid succession can be combined into one undoable event, saving memory and improving performance.
- Snapshot System: By taking snapshots of your application’s state, Oops.js allows you to recover from any point in time with precision. This ensures more reliable state management and error recovery.
- History Compression: Oops.js optimizes memory usage by compressing the command history when it exceeds a set threshold, keeping your app running smoothly.
- Event Notifications: The system notifies when significant changes occur, so the UI can be updated dynamically, reflecting the undo/redo capabilities available at any moment.
- State Serialization: Oops.js supports exporting and importing the state, so you can persist undo/redo data across sessions, adding another layer of robustness to your app.
- And much more.
How to use it:
1. Install Oops.js via NPM and then import it into your project.
npm install @heyputer/oops.js
import Oops from '@heyputer/oops.js';
2. Or include directly in HTML:
<script src="/path/to/dist/oops.min.js"></script>
3. Create a new Oops instance:
const undoManager = new Oops({
// options and defaults
maxStackSize: 100,
snapshotInterval: 10,
compressThreshold: 50,
mergeWindow: 1000
});4. Define commands that encapsulate actions:
class myCommand {
// your command here
}5. Execute commands through the undo manager:
undoManager.execute(new myCommand(parameter));
6. Perform undo and redo operations:
undoManager.undo();
undoManager.redo();
7. More API methods.
// Executes a command and optionally adds it to the undo stack.
// Returns the result of the command execution, if any.
// command (Command|string): The command to execute. Can be a Command object or a string identifier for a registered command.
execute(command, {
silent (boolean) // If true, suppresses notification to listeners after execution. Default is false.
undoable (boolean) // If false, the command will not be added to the undo stack. Default is true.
})
// Undoes a specified number of commands from the undo stack.
undo(steps)
// Redoes a specified number of commands from the redo stack.
// steps (Number, optional): The number of commands to undo. Default is 1.
redo(steps)
// Begins a new transaction, allowing grouping of multiple commands.
beginTransaction()
// Commits the current transaction, executing all commands in the transaction as a single unit.
commitTransaction()
// Aborts the current transaction, undoing all commands in the transaction.
abortTransaction()
// Registers a command factory with a given name.
// name (String): The name to associate with the command factory.
// factory (Function): A function that returns a new instance of the command.
registerCommand(name, factory)
// Adds a change listener to be notified of state changes.
// listener (Function): The listener function to be called on state changes.
addChangeListener(listener)
// Removes a previously added change listener.
// listener (Function): The listener function to be removed.
removeChangeListener(listener)
// Clears all undo and redo history.
clear()
// Exports the current state of the undo/redo manager. Returns an object representing the serialized state.
exportState()
// Imports a previously exported state into the undo/redo manager.
// state (Object): The state object to import.
importState(state)
// Serializes the current state to a JSON string. Returns a JSON string representing the current state.
serializeState()
// Deserializes a JSON string and imports the state.
// jsonState (string): A JSON string representing a previously serialized state.
deserializeState(jsonState)8. The library provides two key properties to indicate the availability of undo/redo actions.
canUndo
canRedo
9. Oops.js also offers a
CompositeCommand class for grouping multiple commands:
// commands (Array): An array of Command objects to be executed as part of this composite command.
new CompositeCommand(commands)
10. This class includes
execute() and
undo() methods, as well as
serialize() and
deserialize(data, deserializeCommand) for managing composite commands.
// Executes all the commands in the composite command in the order they were added.
execute()
// Undoes all the commands in the composite command in reverse order.
undo()
// Serializes the CompositeCommand for storage or transmission.
// Returns an object with the following structure:
// type (string): Always 'CompositeCommand'.
// data (Array): An array of serialized sub-commands.
serialize()
// Static method to deserialize a CompositeCommand.
// Returns a new CompositeCommand instance.
// data (Array): An array of serialized sub-commands. deserializeCommand (Function): A function to deserialize individual commands.
static deserialize(data, deserializeCommand)
// Checks if this CompositeCommand can be merged with another command.
// Returns false: CompositeCommands typically can't be merged.
// other (Command): Another command to check for merge compatibility.
canMerge(other)