• Stars
    star
    441
  • Rank 98,861 (Top 2 %)
  • Language
    JavaScript
  • License
    Other
  • Created about 14 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Simple JavaScript undo and redo independent of other libraries

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
});

More Repositories

1

polythene

Material Design component library for Mithril and React
JavaScript
590
star
2

primer_live

An implementation of GitHub's Primer Design System using Phoenix LiveView
Elixir
176
star
3

mithril-template-converter

Mithril HTML to JavaScript converter
JavaScript
91
star
4

mithril-infinite

Infinite scroll for Mithril
JavaScript
84
star
5

dialogic

Dialogic: manage dialogs and notifications
TypeScript
56
star
6

mithril-hooks

Use hooks in Mithril
TypeScript
41
star
7

mithril-slider

Content slider for Mithril for mobile and desktop
JavaScript
30
star
8

mithril-starter-rollup

Mithril Starter Project with Rollup, Babel, LiveReload, Jest and ESLint
JavaScript
26
star
9

mithril-page-slider

Page Slider for Mithril
JavaScript
23
star
10

VisDoc

VisDoc generates html documentation from ActionScript 2.0, 3.0 and Java class files
Perl
17
star
11

mmsvg

Material Design SVG icons as Mithril elements.
JavaScript
17
star
12

mithril-ts-vite-starter

Mithril with TypeScript starter template for Vite
TypeScript
17
star
13

polythene-mithril-setup

Minimal setup example how to create a Polythene app.
JavaScript
16
star
14

use-stream

A React Hook for working with streams.
TypeScript
16
star
15

mithril-vite-starter

Mithril starter template for Vite
JavaScript
13
star
16

mithril-icon-builder

Converts SVG icons to Mithril elements.
JavaScript
10
star
17

mithril-hookup

Hooks for Mithril
JavaScript
10
star
18

cyano

Make code portable to both Mithril and React
TypeScript
9
star
19

polythene-mithril-material-components-web

Combining Polythene and material-components-web (MDC-Web)
JavaScript
8
star
20

jquery-page-scroll-plugin

Finegrained control over page scrolling
JavaScript
7
star
21

AnimationController

AnimationController is a Cocoa class to create a sequence of Core Animations in code
Objective-C
6
star
22

jquery-fetch-responsive-plugin

Mediator between user interface and webserver, with the goal to get images sized to match the current state of the interface.
JavaScript
6
star
23

glissando

A simple content slider for anything that needs to move.
TypeScript
6
star
24

mithril-jest

Lets Mithril work with Jest
JavaScript
5
star
25

SoundField

Visual audio mixer. The graphical representation of sounds brings concepts to audio that are traditionally more related to other fields, such as proximity, boundary, overlap and placing. Written with OpenFrameworks.
C++
4
star
26

mod_yaml_import

Zotonic module to import YAML data to create new Zotonic Pages
JavaScript
3
star
27

mithril-webpack-starter

Mithril Webpack Starter
JavaScript
2
star
28

polythene-mithril-typescript-setup

Polythene for Mithril with TypeScript setup example
TypeScript
2
star
29

jquery-sticky-plugin

Keep one, two or more elements stuck to the page when scrolling up and down.
HTML
2
star
30

mod_scraper

Fetch data, feed to Zotonic pages
Erlang
2
star
31

primer-live

JavaScript and CSS for primer_live.
CSS
1
star
32

dialogic-js

Control the opening and closing of dialogs using HTML and vanilla JavaScript.
HTML
1
star
33

polythene-react-setup

Polythene for React setup example
JavaScript
1
star
34

mod_responsive_image

Automatically generate images at the right size for each screen dimension.
Erlang
1
star
35

uce-svelte-typescript

Example project of uce with Svelte and TypeScript
JavaScript
1
star
36

write-j2c

Write j2c to CSS files
JavaScript
1
star
37

mod_blog

Blog module for Zotonic
Smarty
1
star
38

test-bind-this

Svelte tests for bind:this
JavaScript
1
star