|
1 | 1 | # Undo Manager |
2 | 2 |
|
3 | | -Simple undo manager to provide undo and redo actions in JavaScript applications. |
4 | | - |
5 | | - |
6 | | -- [Demos](#demos) |
7 | | -- [Installation](#installation) |
8 | | -- [Example](#example) |
9 | | -- [Updating the UI](#updating-the-ui) |
10 | | -- [Methods](#methods) |
11 | | - - [add](#add) |
12 | | - - [undo](#undo) |
13 | | - - [redo](#redo) |
14 | | - - [clear](#clear) |
15 | | - - [setLimit](#setlimit) |
16 | | - - [hasUndo](#hasundo) |
17 | | - - [hasRedo](#hasredo) |
18 | | - - [setCallback](#setcallback) |
19 | | - - [getIndex](#getindex) |
20 | | - - [getCommands](#getcommands) |
21 | | -- [Use with CommonJS](#use-with-commonjs) |
22 | | -- [Use with RequireJS](#use-with-requirejs) |
23 | | - |
24 | | - |
25 | | -## Demos |
26 | | - |
27 | | -* [CodeSandbox with RGB color slider](https://codesandbox.io/s/undo-manager-color-sliders-z4myoj) |
28 | | -* [Undo Manager with canvas drawing](https://arthurclemens.github.io/JavaScript-Undo-Manager/) |
29 | | -* [JSBin demo, also with canvas](http://jsbin.com/tidibi/edit?js,output) |
30 | | - |
31 | | - |
32 | | -## Installation |
33 | | - |
34 | | -``` |
35 | | -npm install undo-manager |
36 | | -``` |
37 | | - |
38 | | - |
39 | | -## Example |
40 | | - |
41 | | -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: |
42 | | - |
43 | | -```js |
44 | | -const undoManager = new UndoManager(); |
45 | | -undoManager.add({ |
46 | | - undo: function() { |
47 | | - // ... |
48 | | - }, |
49 | | - redo: function() { |
50 | | - // ... |
51 | | - } |
52 | | -}); |
53 | | -``` |
54 | | - |
55 | | -To make an action undoable, you'd add an undo/redo command pair to the undo manager: |
56 | | - |
57 | | -```js |
58 | | -const undoManager = new UndoManager(); |
59 | | -const people = {}; |
60 | | - |
61 | | -function addPerson(id, name) { |
62 | | - people[id] = name; |
63 | | -}; |
64 | | - |
65 | | -function removePerson(id) { |
66 | | - delete people[id]; |
67 | | -}; |
68 | | - |
69 | | -function createPerson(id, name) { |
70 | | - // first creation |
71 | | - addPerson(id, name); |
72 | | - |
73 | | - // make undoable |
74 | | - undoManager.add({ |
75 | | - undo: () => removePerson(id), |
76 | | - redo: () => addPerson(id, name) |
77 | | - }); |
78 | | -} |
79 | | - |
80 | | -createPerson(101, "John"); |
81 | | -createPerson(102, "Mary"); |
82 | | - |
83 | | -console.log(people); // logs: {101: "John", 102: "Mary"} |
84 | | - |
85 | | -undoManager.undo(); |
86 | | -console.log(people); // logs: {101: "John"} |
87 | | - |
88 | | -undoManager.undo(); |
89 | | -console.log(people); // logs: {} |
90 | | - |
91 | | -undoManager.redo(); |
92 | | -console.log(people); // logs: {101: "John"} |
93 | | -``` |
94 | | - |
95 | | -## Updating the UI |
96 | | - |
97 | | -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. |
98 | | - |
99 | | -Let's say we have an update function that conditionally disables the undo and redo buttons: |
100 | | - |
101 | | -```js |
102 | | -function updateUI() { |
103 | | - btn_undo.disabled = !undoManager.hasUndo(); |
104 | | - btn_redo.disabled = !undoManager.hasRedo(); |
105 | | -} |
106 | | -``` |
107 | | - |
108 | | -You might be inclined to call the update in the undo/redo command pair: |
109 | | - |
110 | | -```js |
111 | | -// wrong approach, don't copy |
112 | | -const undoManager = new UndoManager(); |
113 | | -const states = []; |
114 | | - |
115 | | -function updateState(newState) { |
116 | | - states.push(newState); |
117 | | - updateUI(); |
118 | | - |
119 | | - undoManager.add({ |
120 | | - undo: function () { |
121 | | - states.pop(); |
122 | | - updateUI(); // <= this will lead to inconsistent UI state |
123 | | - }, |
124 | | - redo: function () { |
125 | | - states.push(newState); |
126 | | - updateUI(); // <= this will lead to inconsistent UI state |
127 | | - } |
128 | | - }); |
129 | | -} |
130 | | -``` |
131 | | - |
132 | | -Instead, pass the update function to `setCallback`: |
133 | | - |
134 | | -```js |
135 | | -// recommended approach |
136 | | -const undoManager = new UndoManager(); |
137 | | -undoManager.setCallback(updateUI); |
138 | | - |
139 | | -const states = []; |
140 | | - |
141 | | -function updateState(newState) { |
142 | | - states.push(newState); |
143 | | - updateUI(); |
144 | | - |
145 | | - undoManager.add({ |
146 | | - undo: function () { |
147 | | - states.pop(); |
148 | | - }, |
149 | | - redo: function () { |
150 | | - states.push(newState); |
151 | | - } |
152 | | - }); |
153 | | -} |
154 | | -``` |
155 | | - |
156 | | -## Methods |
157 | | - |
158 | | -### add |
159 | | - |
160 | | -Adds an undo/redo command pair to the stack. |
161 | | - |
162 | | -```js |
163 | | -function createPerson(id, name) { |
164 | | - // first creation |
165 | | - addPerson(id, name); |
166 | | - |
167 | | - // make undoable |
168 | | - undoManager.add({ |
169 | | - undo: () => removePerson(id), |
170 | | - redo: () => addPerson(id, name) |
171 | | - }); |
172 | | -} |
173 | | -``` |
174 | | - |
175 | | -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. |
176 | | - |
177 | | -```js |
178 | | -undoManager.add({ |
179 | | - groupId: 'auth', |
180 | | - undo: () => removePerson(id), |
181 | | - redo: () => addPerson(id, name) |
182 | | -}); |
183 | | -``` |
184 | | - |
185 | | - |
186 | | -### undo |
187 | | - |
188 | | -Performs the undo action. |
189 | | - |
190 | | -```js |
191 | | -undoManager.undo(); |
192 | | -``` |
193 | | - |
194 | | -If a `groupId` was set, the undo action will be performed on all adjacent command pairs with that group id. |
195 | | - |
196 | | -### redo |
197 | | - |
198 | | -Performs the redo action. |
199 | | - |
200 | | -```js |
201 | | -undoManager.redo(); |
202 | | -``` |
203 | | - |
204 | | -If a `groupId` was set, the redo action will be performed on all adjacent command pairs with that group id. |
205 | | - |
206 | | -### clear |
207 | | - |
208 | | -Clears all stored states. |
209 | | - |
210 | | -```js |
211 | | -undoManager.clear(); |
212 | | -``` |
213 | | - |
214 | | -### setLimit |
215 | | - |
216 | | -Set the maximum number of undo steps. Default: 0 (unlimited). |
217 | | - |
218 | | -```js |
219 | | -undoManager.setLimit(limit); |
220 | | -``` |
221 | | - |
222 | | -### hasUndo |
223 | | - |
224 | | -Tests if any undo actions exist. |
225 | | - |
226 | | -```js |
227 | | -const hasUndo = undoManager.hasUndo(); |
228 | | -``` |
229 | | - |
230 | | -### hasRedo |
231 | | - |
232 | | -Tests if any redo actions exist. |
233 | | - |
234 | | -```js |
235 | | -const hasRedo = undoManager.hasRedo(); |
236 | | -``` |
237 | | - |
238 | | -### setCallback |
239 | | - |
240 | | -Get notified on changes. Pass a function to be called on undo and redo actions. |
241 | | - |
242 | | -```js |
243 | | -undoManager.setCallback(myCallback); |
244 | | -``` |
245 | | - |
246 | | -### getIndex |
247 | | - |
248 | | -Returns the index of the actions list. |
249 | | - |
250 | | -```js |
251 | | -const index = undoManager.getIndex(); |
252 | | -``` |
253 | | - |
254 | | -### getCommands |
255 | | - |
256 | | -Returns the list of queued commands, optionally filtered by group id. |
257 | | - |
258 | | -```js |
259 | | -const commands = undoManager.getCommands(); |
260 | | -const commands = undoManager.getCommands(groupId); |
261 | | -``` |
262 | | - |
263 | | -## Use with CommonJS |
264 | | - |
265 | | -```bash |
266 | | -npm install undo-manager |
267 | | -``` |
268 | | - |
269 | | -```js |
270 | | -const UndoManager = require('undo-manager') |
271 | | -``` |
272 | | - |
273 | | -If you only need a single instance of UndoManager throughout your application, it may be wise to create a module that exports a singleton: |
274 | | - |
275 | | -```js |
276 | | -// undoManager.js |
277 | | -const undoManager = require('undo-manager'); // require the lib from node_modules |
278 | | -let singleton = undefined; |
279 | | - |
280 | | -if (!singleton) { |
281 | | - singleton = new undoManager(); |
282 | | -} |
283 | | - |
284 | | -module.exports = singleton; |
285 | | -``` |
286 | | - |
287 | | -Then in your app: |
288 | | - |
289 | | -```js |
290 | | -// app.js |
291 | | -const undoManager = require('undoManager'); |
292 | | - |
293 | | -undoManager.add(...); |
294 | | -undoManager.undo(); |
295 | | -``` |
296 | | - |
297 | | - |
298 | | -## Use with RequireJS |
299 | | - |
300 | | -If you are using RequireJS, you need to use the `shim` config parameter. |
301 | | - |
302 | | -Assuming `require.js` and `domReady.js` are located in `js/extern`, the `index.html` load call would be: |
303 | | - |
304 | | -```html |
305 | | -<script src="js/extern/require.js" data-main="js/demo"></script> |
306 | | -``` |
307 | | - |
308 | | -And `demo.js` would look like this: |
309 | | - |
310 | | -```js |
311 | | -requirejs.config({ |
312 | | - baseUrl: "js", |
313 | | - paths: { |
314 | | - domReady: "extern/domReady", |
315 | | - app: "../demo", |
316 | | - undomanager: "../../js/undomanager", |
317 | | - circledrawer: "circledrawer" |
318 | | - }, |
319 | | - shim: { |
320 | | - "undomanager": { |
321 | | - exports: "UndoManager" |
322 | | - }, |
323 | | - "circledrawer": { |
324 | | - exports: "CircleDrawer" |
325 | | - } |
326 | | - } |
327 | | -}); |
328 | | - |
329 | | -require(["domReady", "undomanager", "circledrawer"], function(domReady, UndoManager, CircleDrawer) { |
330 | | - "use strict"; |
331 | | - |
332 | | - let undoManager, |
333 | | - circleDrawer, |
334 | | - btnUndo, |
335 | | - btnRedo, |
336 | | - btnClear; |
337 | | - |
338 | | - undoManager = new UndoManager(); |
339 | | - circleDrawer = new CircleDrawer("view", undoManager); |
340 | | - |
341 | | - // etcetera |
342 | | -}); |
343 | | -``` |
| 3 | +Migrated to https://codeberg.org/ArthurClemens/JavaScript-Undo-Manager |
0 commit comments