npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@urmd/undo-manager

v1.1.1

Published

Simple undo manager to provide undo and redo actions in JavaScript applications.

Downloads

2

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