Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit 96c7b9e

Browse files
committed
Add grouping
1 parent cb15bff commit 96c7b9e

File tree

9 files changed

+197
-32
lines changed

9 files changed

+197
-32
lines changed

README.md

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Simple undo manager to provide undo and redo actions in JavaScript applications.
77
- [Installation](#installation)
88
- [Example](#example)
99
- [Methods](#methods)
10+
- [add](#add)
1011
- [undo](#undo)
1112
- [redo](#redo)
1213
- [clear](#clear)
@@ -49,8 +50,7 @@ undoManager.add({
4950
});
5051
```
5152

52-
To make an action undoable, you'd add an undo/redo pair to the undo manager:
53-
53+
To make an action undoable, you'd add an undo/redo command pair to the undo manager:
5454

5555
```js
5656
const undoManager = new UndoManager();
@@ -93,6 +93,34 @@ console.log(people); // logs: {101: "John"}
9393

9494
## Methods
9595

96+
### add
97+
98+
Adds an undo/redo command pair to the stack.
99+
100+
```js
101+
function createPerson(id, name) {
102+
// first creation
103+
addPerson(id, name);
104+
105+
// make undoable
106+
undoManager.add({
107+
undo: () => removePerson(id),
108+
redo: () => addPerson(id, name)
109+
});
110+
}
111+
```
112+
113+
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.
114+
115+
```js
116+
undoManager.add({
117+
groupId: 'auth',
118+
undo: () => removePerson(id),
119+
redo: () => addPerson(id, name)
120+
});
121+
```
122+
123+
96124
### undo
97125

98126
Performs the undo action.
@@ -101,6 +129,8 @@ Performs the undo action.
101129
undoManager.undo();
102130
```
103131

132+
If a `groupId` was set, the undo action will be performed on all adjacent command pairs with that group id.
133+
104134
### redo
105135

106136
Performs the redo action.
@@ -109,6 +139,8 @@ Performs the redo action.
109139
undoManager.redo();
110140
```
111141

142+
If a `groupId` was set, the redo action will be performed on all adjacent command pairs with that group id.
143+
112144
### clear
113145

114146
Clears all stored states.
@@ -159,10 +191,11 @@ const index = undoManager.getIndex();
159191

160192
### getCommands
161193

162-
Returns the list of queued commands.
194+
Returns the list of queued commands, optionally filtered by group id.
163195

164196
```js
165197
const commands = undoManager.getCommands();
198+
const commands = undoManager.getCommands(groupId);
166199
```
167200

168201
## Use with CommonJS

demo/index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@
3434
placeholder="Limit: 0"
3535
/>
3636
<div class="spacer"></div>
37+
<button
38+
type="button"
39+
class="btn btn-default"
40+
id="btnGroup"
41+
>
42+
Group
43+
</button>
3744
<button
3845
type="button"
3946
class="btn btn-default"

demo/js/circledrawer.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const CircleDrawer = function (canvasId, undoManager) {
1010
const circles = [];
1111
const circleId = 0;
1212
const drawingCanvas = window.document.getElementById(canvasId);
13+
let groupId;
1314

1415
if (drawingCanvas.getContext === undefined) {
1516
return;
@@ -61,6 +62,7 @@ const CircleDrawer = function (canvasId, undoManager) {
6162
circles.push(attrs);
6263
draw();
6364
undoManager.add({
65+
groupId,
6466
undo: function () {
6567
removeCircle(attrs.id);
6668
},
@@ -70,6 +72,13 @@ const CircleDrawer = function (canvasId, undoManager) {
7072
});
7173
}
7274

75+
function setGroupId(id) {
76+
groupId = id;
77+
}
78+
function clearGroupId() {
79+
groupId = undefined;
80+
}
81+
7382
drawingCanvas.addEventListener(
7483
'click',
7584
function (e) {
@@ -121,5 +130,7 @@ const CircleDrawer = function (canvasId, undoManager) {
121130

122131
return {
123132
clearAll,
133+
setGroupId,
134+
clearGroupId,
124135
};
125136
};

demo/js/demo.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ window.onload = function () {
33

44
const undoManager = new window.UndoManager();
55
const circleDrawer = new CircleDrawer('view', undoManager);
6-
76
const ctrlLimit = document.getElementById('ctrlLimit');
87
const btnUndo = document.getElementById('btnUndo');
98
const btnRedo = document.getElementById('btnRedo');
109
const btnClearMemory = document.getElementById('btnClearMemory');
1110
const btnClearScreen = document.getElementById('btnClearScreen');
11+
const btnGroup = document.getElementById('btnGroup');
1212

1313
function updateUI() {
1414
btnUndo.disabled = !undoManager.hasUndo();
@@ -37,6 +37,16 @@ window.onload = function () {
3737
updateUI();
3838
});
3939

40+
btnGroup.addEventListener('click', function () {
41+
const c = btnGroup.classList;
42+
c.toggle('active');
43+
if (c.contains('active')) {
44+
circleDrawer.setGroupId(new Date().getTime());
45+
} else {
46+
circleDrawer.clearGroupId();
47+
}
48+
});
49+
4050
function handleLimit(rawLimit) {
4151
const limit = parseInt(rawLimit, 10);
4252
if (!isNaN(limit)) {

lib/undomanager.js

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ https://github.com/ArthurClemens/JavaScript-Undo-Manager
2323
index = -1,
2424
limit = 0,
2525
isExecuting = false,
26-
callback,
27-
// functions
28-
execute;
26+
callback;
2927

3028
/**
3129
* Executes a single command.
@@ -34,7 +32,7 @@ https://github.com/ArthurClemens/JavaScript-Undo-Manager
3432
* @property {function} command.redo - Redo function
3533
* @property {string} action - "undo" or "redo"
3634
*/
37-
execute = function (command, action) {
35+
function execute(command, action) {
3836
if (!command || typeof command[action] !== 'function') {
3937
return this;
4038
}
@@ -44,14 +42,15 @@ https://github.com/ArthurClemens/JavaScript-Undo-Manager
4442

4543
isExecuting = false;
4644
return this;
47-
};
45+
}
4846

4947
return {
5048
/**
5149
* Adds a command to the queue.
52-
* @property {object} command - Command
53-
* @property {function} command.undo - Undo function
54-
* @property {function} command.redo - Redo function
50+
* @property {object} command - Command
51+
* @property {function} command.undo - Undo function
52+
* @property {function} command.redo - Redo function
53+
* @property {string} [command.groupId] - Optional group id
5554
*/
5655
add: function (command) {
5756
if (isExecuting) {
@@ -60,7 +59,6 @@ https://github.com/ArthurClemens/JavaScript-Undo-Manager
6059
// if we are here after having called undo,
6160
// invalidate items higher on the stack
6261
commands.splice(index + 1, commands.length - index);
63-
6462
commands.push(command);
6563

6664
// if limit is set, remove items from the start
@@ -76,7 +74,7 @@ https://github.com/ArthurClemens/JavaScript-Undo-Manager
7674
return this;
7775
},
7876

79-
/**
77+
/**
8078
* Pass a function to be called on undo and redo actions.
8179
* @property {function} callbackFunc - Callback function
8280
*/
@@ -92,8 +90,15 @@ https://github.com/ArthurClemens/JavaScript-Undo-Manager
9290
if (!command) {
9391
return this;
9492
}
95-
execute(command, 'undo');
96-
index -= 1;
93+
94+
const groupId = command.groupId;
95+
while (command.groupId === groupId) {
96+
execute(command, 'undo');
97+
index -= 1;
98+
command = commands[index];
99+
if (!command || !command.groupId) break;
100+
}
101+
97102
if (callback) {
98103
callback();
99104
}
@@ -108,8 +113,15 @@ https://github.com/ArthurClemens/JavaScript-Undo-Manager
108113
if (!command) {
109114
return this;
110115
}
111-
execute(command, 'redo');
112-
index += 1;
116+
117+
const groupId = command.groupId;
118+
while (command.groupId === groupId) {
119+
execute(command, 'redo');
120+
index += 1;
121+
command = commands[index + 1];
122+
if (!command || !command.groupId) break;
123+
}
124+
113125
if (callback) {
114126
callback();
115127
}
@@ -148,10 +160,11 @@ https://github.com/ArthurClemens/JavaScript-Undo-Manager
148160

149161
/**
150162
* Returns the list of queued commands.
163+
* @param {string} [groupId] - Optionally filter commands by group ID
151164
* @returns {array}
152165
*/
153-
getCommands: function () {
154-
return commands;
166+
getCommands: function (groupId) {
167+
return groupId ? commands.filter(c => c.groupId === groupId) : commands;
155168
},
156169

157170
/**

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "undo-manager",
3-
"version": "1.0.6",
3+
"version": "1.1.0",
44
"description": "Simple undo manager to provide undo and redo actions in JavaScript applications.",
55
"main": "lib/undomanager.js",
66
"scripts": {

spec/SpecRunner.html

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!-- Use this file to manually iterate on tests. -->
2+
<!DOCTYPE html>
3+
<html>
4+
<head>
5+
<title>Jasmine Spec Runner for Undo Manager</title>
6+
7+
<link
8+
rel="stylesheet"
9+
type="text/css"
10+
href="http://cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.css"
11+
/>
12+
<script
13+
type="text/javascript"
14+
src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js"
15+
></script>
16+
<script
17+
type="text/javascript"
18+
src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine-html.js"
19+
></script>
20+
21+
<script type="text/javascript" src="../lib/undomanager.js"></script>
22+
<script type="text/javascript" src="undomanager.spec.js"></script>
23+
24+
<script type="text/javascript">
25+
(function () {
26+
const jasmineEnv = jasmine.getEnv();
27+
jasmineEnv.updateInterval = 1000;
28+
29+
const htmlReporter = new jasmine.HtmlReporter();
30+
31+
jasmineEnv.addReporter(htmlReporter);
32+
33+
jasmineEnv.specFilter = function (spec) {
34+
return htmlReporter.specFilter(spec);
35+
};
36+
37+
const currentWindowOnload = window.onload;
38+
39+
window.onload = function () {
40+
if (currentWindowOnload) {
41+
currentWindowOnload();
42+
}
43+
execJasmine();
44+
};
45+
46+
function execJasmine() {
47+
jasmineEnv.execute();
48+
}
49+
})();
50+
</script>
51+
</head>
52+
53+
<body></body>
54+
</html>

0 commit comments

Comments
 (0)