undo-manager
v1.1.1
Published
Simple undo manager to provide undo and redo actions in JavaScript applications.
Downloads
3,829
Readme
Undo Manager
Simple undo manager to provide undo and redo actions in JavaScript applications.
Demos
Installation
npm install undo-manager
Example
Actions (typing a character, moving an object) are structured as command pairs: one command for destruction (undo) and one for creation (redo). Each pair is added to the undo stack:
const undoManager = new UndoManager();
undoManager.add({
undo: function() {
// ...
},
redo: function() {
// ...
}
});
To make an action undoable, you'd add an undo/redo command pair to the undo manager:
const undoManager = new UndoManager();
const people = {};
function addPerson(id, name) {
people[id] = name;
};
function removePerson(id) {
delete people[id];
};
function createPerson(id, name) {
// first creation
addPerson(id, name);
// make undoable
undoManager.add({
undo: () => removePerson(id),
redo: () => addPerson(id, name)
});
}
createPerson(101, "John");
createPerson(102, "Mary");
console.log(people); // logs: {101: "John", 102: "Mary"}
undoManager.undo();
console.log(people); // logs: {101: "John"}
undoManager.undo();
console.log(people); // logs: {}
undoManager.redo();
console.log(people); // logs: {101: "John"}
Updating the UI
TL;DR UI that relies on undo manager state - for example hasUndo
and hasRedo
- needs to be updated using the callback function provided with setCallback
. This ensures that all internal state has been resolved before the UI is repainted.
Let's say we have an update function that conditionally disables the undo and redo buttons:
function updateUI() {
btn_undo.disabled = !undoManager.hasUndo();
btn_redo.disabled = !undoManager.hasRedo();
}
You might be inclined to call the update in the undo/redo command pair:
// wrong approach, don't copy
const undoManager = new UndoManager();
const states = [];
function updateState(newState) {
states.push(newState);
updateUI();
undoManager.add({
undo: function () {
states.pop();
updateUI(); // <= this will lead to inconsistent UI state
},
redo: function () {
states.push(newState);
updateUI(); // <= this will lead to inconsistent UI state
}
});
}
Instead, pass the update function to setCallback
:
// recommended approach
const undoManager = new UndoManager();
undoManager.setCallback(updateUI);
const states = [];
function updateState(newState) {
states.push(newState);
updateUI();
undoManager.add({
undo: function () {
states.pop();
},
redo: function () {
states.push(newState);
}
});
}
Methods
add
Adds an undo/redo command pair to the stack.
function createPerson(id, name) {
// first creation
addPerson(id, name);
// make undoable
undoManager.add({
undo: () => removePerson(id),
redo: () => addPerson(id, name)
});
}
Optionally add a groupId
to identify related command pairs. Undo and redo actions will then be performed on all adjacent command pairs with that group id.
undoManager.add({
groupId: 'auth',
undo: () => removePerson(id),
redo: () => addPerson(id, name)
});
undo
Performs the undo action.
undoManager.undo();
If a groupId
was set, the undo action will be performed on all adjacent command pairs with that group id.
redo
Performs the redo action.
undoManager.redo();
If a groupId
was set, the redo action will be performed on all adjacent command pairs with that group id.
clear
Clears all stored states.
undoManager.clear();
setLimit
Set the maximum number of undo steps. Default: 0 (unlimited).
undoManager.setLimit(limit);
hasUndo
Tests if any undo actions exist.
const hasUndo = undoManager.hasUndo();
hasRedo
Tests if any redo actions exist.
const hasRedo = undoManager.hasRedo();
setCallback
Get notified on changes. Pass a function to be called on undo and redo actions.
undoManager.setCallback(myCallback);
getIndex
Returns the index of the actions list.
const index = undoManager.getIndex();
getCommands
Returns the list of queued commands, optionally filtered by group id.
const commands = undoManager.getCommands();
const commands = undoManager.getCommands(groupId);
Use with CommonJS
npm install undo-manager
const UndoManager = require('undo-manager')
If you only need a single instance of UndoManager throughout your application, it may be wise to create a module that exports a singleton:
// undoManager.js
const undoManager = require('undo-manager'); // require the lib from node_modules
let singleton = undefined;
if (!singleton) {
singleton = new undoManager();
}
module.exports = singleton;
Then in your app:
// app.js
const undoManager = require('undoManager');
undoManager.add(...);
undoManager.undo();
Use with RequireJS
If you are using RequireJS, you need to use the shim
config parameter.
Assuming require.js
and domReady.js
are located in js/extern
, the index.html
load call would be:
<script src="js/extern/require.js" data-main="js/demo"></script>
And demo.js
would look like this:
requirejs.config({
baseUrl: "js",
paths: {
domReady: "extern/domReady",
app: "../demo",
undomanager: "../../js/undomanager",
circledrawer: "circledrawer"
},
shim: {
"undomanager": {
exports: "UndoManager"
},
"circledrawer": {
exports: "CircleDrawer"
}
}
});
require(["domReady", "undomanager", "circledrawer"], function(domReady, UndoManager, CircleDrawer) {
"use strict";
let undoManager,
circleDrawer,
btnUndo,
btnRedo,
btnClear;
undoManager = new UndoManager();
circleDrawer = new CircleDrawer("view", undoManager);
// etcetera
});