PageRenderTime 37ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/README.md

https://github.com/caseygrun/Backbone.Undo.js
Markdown | 457 lines | 302 code | 155 blank | 0 comment | 0 complexity | 9904657854fc749c63f33c50de0bcffe MD5 | raw file
  1. Backbone.Undo.js
  2. ================
  3. An extremely simple undo manager for Backbone.js
  4. **Go to [backbone.undojs.com](http://backbone.undojs.com) for demos and a video tutorial!**
  5. ***
  6. #### Advantages of Backbone.Undo.js
  7. * **The Drop-In Manager**
  8. In comparison to undo managers that implement the memento pattern you don't have to modify your models and collections to use Backbone.Undo.js. Just drop in Backbone.Undo.js and register the objects whose actions you want to undo. That way it's not only easy to include Backbone.Undo.js, but also to exclude it in case you don't want to use it any longer at some point.
  9. * **Ready To Go: Based on Backbone-Events**
  10. You don't have to manually call methods to `store()` or `restore()` certain states. To detect an undoable action, Backbone.Undo.js listens to the events Backbone triggeres automatically. You don't have to do anything.
  11. * **Magic Fusion**
  12. In a more complex web application the click of a button might trigger several changes which dispatch several events which in Backbone.Undo.js are turned into several undoable actions. If the user wants to undo what he caused with his click he wants to undo all of those actions. Backbone.Undo.js has an internal feature called *Magic Fusion* that detects actions that were created in one flow and undoes or redoes all of them.
  13. #### Who should use Backbone.Undo.js
  14. Backbone.Undo.js is a simple undo manager that should be used for rather simple web applications. It has mechanisms that make it extensible and suitable for more complex applications. However, it might not be adequate for very large-scale applications with vast amounts of lines of code.
  15. ## Getting started
  16. Backbone.Undo.js depends on Backbone.js which depends on Underscore.js (or Lo-Dash.js). Make sure to include these two files before you include Backbone.Undo.js:
  17. <script src="underscore.js"></script>
  18. <script src="backbone.js"></script>
  19. <!-- Backbone.Undo.js is included *after* Backbone and Underscore -->
  20. <script src="Backbone.Undo.js"></script>
  21. #### Backbone Version
  22. Backbone.Undo.js was developed for Backbone 1.0.0 or higher.
  23. #### Underscore Version
  24. Backbone.Undo.js was developed for Underscore 1.4.4 or higher.
  25. ## Setting up your UndoManager
  26. In order to set up your UndoManager you have to do the following steps:
  27. 1. __Instantiate__ your UndoManager
  28. var myUndoManager = new Backbone.UndoManager();
  29. 2. __Register__ the models and collections you want to observe
  30. var model = new Backbone.Model,
  31. collection = new Backbone.Collection;
  32. myUndoManager.register(model, collection); // You can pass several objects as arguments
  33. // You can prepare your objects here. Changes won't be tracked yet.
  34. model.set("foo", "bar");
  35. collection.add([{"something": "blue"}]);
  36. // These changes can't be undone.
  37. 3. __Start tracking__ the changes
  38. myUndoManager.startTracking(); // Every change that happens to the model and the collection can now be undone
  39. __Shorthand__: If you already have the objects you want to observe at hand when you instantiate the undo manager or if you don't need to prepare them you can pass them on instantiation:
  40. // Shorthand
  41. var myUndoManager = new Backbone.UndoManager({
  42. track: true, // changes will be tracked right away
  43. register: [model, collection] // pass an object or an array of objects
  44. })
  45. ## Backbone.Undo.js methods
  46. Methods you can call on an instance of `Backbone.UndoManager`:
  47. #### Constructor `new Backbone.UndoManager([object])`
  48. The constructor can be called with an object of attributes as an optional argument. Each attribute is optional and has a default value.
  49. var undoManager = new Backbone.UndoManager; // possible, because the argument is optional
  50. var undoManager = new Backbone.UndoManager({
  51. maximumStackLength: 30, // default: Infinity; Maximum number of undoable actions
  52. track: true, // default: false; If true, changes will be tracked right away
  53. register: myObj // default: undefined; Pass the object or an array of objects that you want to observe
  54. });
  55. #### register `undoManager.register(obj, [obj, ...])`
  56. Your undo manager must know the objects whose actions should be undoable/redoable. Therefore you have to register these
  57. objects:
  58. var model = new Backbone.Model;
  59. var collection = new Backbone.Collection;
  60. undoManager.register(model, collection);
  61. The register-method doesn't check whether the object is an instance of Backbone.Model or Backbone.Collection. That makes
  62. it possible to bind other objects as well. However, make sure they have an `on()` and an `off()` method and trigger an `"all"` event in the fashion of Backbone's `"all"` event.
  63. #### unregister `undoManager.unregister(obj, [obj, …])`
  64. Previously registered objects can be unregistered using the `unregister()` method. Changes to those objects can't be
  65. undone after they have been unregistered.
  66. var myModel = new Backbone.Model;
  67. undoManager.register(myModel);
  68. undoManager.startTracking();
  69. myModel.set("foo", "bar"); // Can be undone
  70. undoManager.unregister(myModel);
  71. myModel.set("foo", "baz"); // Can't be undone
  72. #### unregisterAll `undoManager.unregisterAll()`
  73. Unregister all objects that have been registered at this undoManager so far.
  74. #### startTracking `undoManager.startTracking()`
  75. Changes must be tracked in order to create UndoActions. You can either set `{track: true}` on instantiation or call `startTracking()` later.
  76. var myModel = new Backbone.Model;
  77. undoManager.register(myModel);
  78. myModel.set("foo", "bar"); // Can't be undone because tracking didn't start yet
  79. undoManager.startTracking();
  80. myModel.set("foo", "baz"); // Can be undone
  81. #### stopTracking `undoManager.stopTracking();`
  82. If you want to stop tracking changes for whatever reason, you can do that by calling `stopTracking()`.
  83. myModel.set("foo", 1);
  84. undoManager.startTracking();
  85. myModel.set("foo", 2);
  86. undoManager.stopTracking();
  87. myModel.set("foo", 3);
  88. undoManager.undo(); // "foo" is 1 instead of 2, because the last change wasn't tracked
  89. #### undo `undoManager.undo([magic]);`
  90. The method to undo the last action is `undo()`.
  91. myModel.get("foo"); // => 1
  92. myModel.set("foo", 2);
  93. undoManager.undo();
  94. myModel.get("foo"); // => 1
  95. Pass `true` to activate *Magic Fusion*. That way you undo the complete last set of actions that happened at once.
  96. #### redo `undoManager.redo([magic])`
  97. The method to redo the latest undone action is `redo()`.
  98. myModel.set("foo", 2);
  99. undoManager.undo();
  100. myModel.get("foo"); // => 1
  101. undoManager.redo();
  102. myModel.get("foo"); // => 2
  103. Like with `undo()` you can pass `true` to activate *Magic Fusion* and to redo the complete last set of actions that were undone.
  104. #### isAvailable `undoManager.isAvailable(type)`
  105. This method checks if there's an UndoAction in the stack that can be undone / redone. Pass `"undo"` or `"redo"` as the argument.
  106. undoManager.isAvailable("undo") // => true; You can undo actions
  107. If you use undo- and redo-buttons in your gui this method is helpful for determining whether to display them in an enabled or disabled state.
  108. #### merge `undoManager.merge(otherManager1, [otherManager2, …])`
  109. This is a feature for the advanced use of Backbone.Undo.js. Using the UndoTypes-API (see below) for specific instances of `Backbone.UndoManager` you can create undo managers with special behavior for special cases. But as having several undo managers side by side doesn't make any sense you need a way to combine them. That's what `merge` is for.
  110. The method `merge` sets the stack-reference of other undo managers to its stack.
  111. var mainUndoManager = new Backbone.UndoManager,
  112. specialUndoManager = new Backbone.UndoManager;
  113. // Implementing special behavior
  114. specialUndoManager.addUndoType()
  115. // Making both write on one stack
  116. mainUndoManager.merge(specialUndoManager);
  117. mainUndoManager.stack === specialUndoManager.stack // => true
  118. You can pass one or more undo managers or an array with one or more undo managers when calling this function.
  119. #### addUndoType `undoManager.addUndoType(type, fns)`
  120. This adds an UndoType that only works for this specific undo manager and won't affect other instances of Backbone.UndoManager. See the UndoTypes-API for a more thorough documentation on this function.
  121. #### changeUndoType `undoManager.changeUndoType(type, fns)`
  122. This changes an UndoType only on this specific undo manager and won't affect other instances of Backbone.UndoManager. See the UndoTypes-API for a more thorough documentation on this function.
  123. #### removeUndoType `undoManager.removeUndoType(type)`
  124. This removes an UndoType only from from this specific undo manager. See the UndoTypes-API for a more thorough documentation on this function.
  125. ***
  126. Methods you can call on the object `Backbone.UndoManager`:
  127. #### defaults `Backbone.UndoManager.defaults(obj)`
  128. Extend or overwrite the default values of an undo manager.
  129. Backbone.UndoManager.defaults({
  130. track: true
  131. });
  132. var undoManager = new Backbone.UndoManager; // tracking has now already started
  133. #### addUndoType `Backbone.UndoManager.addUndoType(type, fns)`
  134. This adds an UndoType that works for all undo managers whether they've already been instantiated or not. See the UndoTypes-API for a more thorough documentation on this function.
  135. #### changeUndoType `Backbone.UndoManager.changeUndoType(type, fns)`
  136. This changes an UndoType for all undo managers whether they've already been instantiated or not. See the UndoTypes-API for a more thorough documentation on this function.
  137. #### removeUndoType `Backbone.UndoManager.removeUndoType(type)`
  138. This removes an UndoType from all undo managers whether they've already been instantiated or not. See the UndoTypes-API for a more thorough documentation on this function.
  139. ## Supported Events
  140. Backbone.Undo.js uses Backbone's events to generate UndoActions. It has built-in support for the following events
  141. * `add` When a model is added to a collection
  142. * `remove` When a model is removed from a collection
  143. * `reset` When a collection is reset and all models are replaced by new models (or no models) at once
  144. * `change` When a model's attribute is changed or set
  145. ### Supporting other events and modifying built-in behavior
  146. Backbone.Undo.js has an API to extend and modify the generation of UndoActions. In order to use the API it's important to understand the concept of creating UndoActions:
  147. #### UndoTypes
  148. Backbone.Undo.js retrieves the data of the undoable states from the events Backbone triggers and their arguments. However, different events have different arguments and thus need different approaches in retrieving the necessary data. Additionally, different types of actions require different behavior to undo and redo them.
  149. That's what the *UndoTypes* are for. An *UndoType* is an object of functions for a specific type of event. The functions retrieve the data necessary to create an UndoAction and are able to undo an action of this type and redo it.
  150. An *UndoType* needs to have the following functions:
  151. * **on** `([…])`
  152. This function is called when the event this UndoType is made for was triggered on an observed object. It gets all the arguments that were triggered with the event. The `"on"`-function must return an object with the properties `object`, `before`, `after` and optionally `options`.
  153. return {
  154. "object": // The object the event was triggered on
  155. "before": // The object's state before the concerning action occured
  156. "after": // The object's current state, after the concerning action occured
  157. "options": // Optionally: Some 'options'-object
  158. }
  159. * **undo** `(obj, before, after, options)`
  160. The `undo` function is called when the action this UndoType is made for should be undone. The data returned by the `"on"` function is passed to `"undo"` as arguments:
  161. * `obj` is the model, collection or other kind of object that should be acted on
  162. * `before` is the the data before the action occured and defines the state that should be created within this function
  163. * `after` is the data after the action had occured and represents obj's current state
  164. * `options` are the options the `on` function returned
  165. * **redo** `(obj, before, after, options)`
  166. The `redo` function is called when the action this UndoType is made for should be redone. As with `"undo"` the data returned by the `"on"` function is passed to `"redo"` as arguments
  167. * `obj` is the model, collection or other kind of object that should be acted on
  168. * `before` is the the data before the action occured and represents the current state as the action was previously undone
  169. * `after` is the data after the action had occured and is the state wich should be recreated
  170. * `options` are the options the `"on"` function returned
  171. It can have an optional property:
  172. * **condition** `([…])`
  173. `"condition"` can be a function or a boolean value that defines whether an UndoAction should be created or not. If it's false or if it returns false `"on"` won't be called and no UndoAction is created. If it's not set, condition is always `true`.
  174. ##### UndoType example
  175. This is an example of an UndoType for the `"reset"` event.
  176. {
  177. "reset": {
  178. "condition": true, // This isn't necessary as condition is true by default
  179. "on": function (collection, options) {
  180. // The 'on'-method gets the same arguments a listener for the
  181. // Backbone 'reset'-event would get: collection.on("reset", listener)
  182. // The 'on'-method has to return an object with the properties
  183. // 'object', 'before', 'after' and optionally 'options'
  184. return {
  185. object: collection,
  186. before: options.previousModels,
  187. after: _.clone(collection.models)
  188. }
  189. },
  190. "undo": function (collection, before, after) {
  191. // To restore the previous state we just reset the collection
  192. // with the previous models
  193. collection.reset(before);
  194. }
  195. "redo": function (collection, before, after) {
  196. // To restore the subsequent state we reset the collection to
  197. // the 'after'-array of models
  198. collection.reset(after);
  199. }
  200. }
  201. }
  202. #### UndoTypes API
  203. To create your own UndoTypes for custom events or for extending the support of Backbone-events or if you just want to modify the built-in behavior, you can either do that on a global level to affect all current and future instances of Backbone.UndoManager or do that per instance to change only a specific undo manager.
  204. Either way you have three methods to extend or change the UndoTypes. Below the functions for global changes are presented:
  205. #### addUndoType
  206. Backbone.Undo.addUndoType(type, callbacks);
  207. // or
  208. Backbone.Undo.addUndoType(types);
  209. With the `addUndoType()` method you can add or overwrite one or more UndoTypes. You can call it with the two arguments `type` and `callbacks` or with an object in which all keys are `type`s and their values `callbacks` to perform a bulk action.
  210. * `type` The name of the event this UndoType is made for. In terms of Backbone events: `"add"`, `"remove"`, `"reset"`, `"change"`, etc.
  211. * `callbacks` An object with the funcitions `"on"`, `"undo"`, `"redo"` and optionally `"condition"`
  212. *Example*: If we want to overwrite the UndoType `"reset"` with the functions defined in the example above we can do the following:
  213. Backbone.Undo.addUndoType("reset", {
  214. "on": function (collection, options) {
  215. },
  216. "undo": function (collection, before, after) {
  217. },
  218. "redo": function (collection, before, after) {
  219. }
  220. });
  221. You can also define several UndoTypes at once by passing an object to `addUndoType`
  222. Backbone.Undo.addUndoType({
  223. "reset": {
  224. "on":
  225. "undo":
  226. "redo":
  227. },
  228. "add": {
  229. "on":
  230. "undo":
  231. "redo":
  232. },
  233. "customevent": {
  234. "on":
  235. "undo":
  236. "redo":
  237. }
  238. });
  239. #### changeUndoType
  240. Backbone.Undo.changeUndoType(type, callbacks);
  241. // or
  242. Backbone.Undo.changeUndoType(types);
  243. If you want to change just one or more functions of an already added or built-in UndoType `changeUndoType` is the way to go. It works just like `addUndoType` with the difference that there must already be an UndoType for the specified `type` and you don't have to pass all `callbacks` functions.
  244. Backbone.Undo.changeUndoType("reset", {
  245. "condition":
  246. })
  247. Pass an object to perform a bulk action:
  248. Backbone.Undo.changeUndoType({
  249. "reset": {
  250. "condition":
  251. },
  252. "add": {
  253. "on":
  254. "undo":
  255. },
  256. "customevent": {
  257. "redo":
  258. }
  259. })
  260. #### removeUndoType
  261. Backbone.Undo.removeUndoType(type);
  262. // or
  263. Backbone.Undo.removeUndoType(types);
  264. Call `removeUndoType` to remove an existing UndoType. Pass the type of the UndoType you want to remove as the argument or pass an array of types if you want to remove several at once.
  265. Backbone.Undo.removeUndoType("reset");
  266. Pass an array to perform a bulk action:
  267. Backbone.Undo.removeUndoType(["reset", "add", "customevent"]);
  268. If you just want to suspend an UndoType for a limited amount of time, making use of the `"condition"` property might be more adequate:
  269. Backbone.Undo.changeUndoType("reset", {"condition": false});
  270. #### Using the UndoTypes API per instance
  271. As stated above you can also add, change and remove UndoTypes for a specific instance of Backbone.Undo without affecting other instances. The methods and arguments are exactly the same.
  272. var undoManager = new Backbone.UndoManager;
  273. undoManager.addUndoType("reset", {
  274. "on":
  275. "undo":
  276. "redo":
  277. })
  278. undoManager.changeUndoType("reset", {
  279. "undo":
  280. })
  281. undoManager.removeUndoType("reset");
  282. Please note that removing an UndoType on a per instance level just causes a fallback to the global UndoTypes and won't take away the support for this type. You have to overwrite the type with an UndoType of empty functions to accomplish that.
  283. Using the UndoTypes-API for a specific instance is especially useful if you have several undo managers.
  284. var undoA = new Backbone.UndoManager,
  285. undoB = new Backbone.UndoManager,
  286. undoC = new Backbone.UndoManager;
  287. undoA.addUndoType(); // behavior A
  288. undoB.addUndoType(); // behavior B
  289. undoC.addUndoType(); // behavior C
  290. However, several undo managers cause the problem that you don't know on which undo manager you should call `undo()` and `redo()`.
  291. That's what the `merge()` function is for: It merges several undo managers by making them write on a single stack.
  292. var mainUndo = new Backbone.UndoManager;
  293. mainUndo.merge(undoA, undoB, undoC)
  294. Now, you just need to call `undo()` and `redo()` on the main undo manager.
  295. ## License (MIT License)
  296. Copyright (c) 2013 Oliver Sartun
  297. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
  298. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  299. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.