Daniel@0
|
1 "use strict";
|
Daniel@0
|
2
|
Daniel@0
|
3 App.module("ContextModule", function(ContextModule, App, Backbone, Marionette, $, _, Logger) {
|
Daniel@0
|
4
|
Daniel@0
|
5 // Define private variables
|
Daniel@0
|
6 var logger = null;
|
Daniel@0
|
7
|
Daniel@0
|
8 ContextModule.addInitializer(function(options){
|
Daniel@0
|
9
|
Daniel@0
|
10 // logger = Logger.get("ContextModule.StateBookmark");
|
Daniel@0
|
11 // logger.setLevel(Logger.DEBUG);
|
Daniel@0
|
12
|
Daniel@0
|
13 /**
|
Daniel@0
|
14 * StateHistory stores a history of a serialized state
|
Daniel@0
|
15 * and provides an API to switch between these states
|
Daniel@0
|
16 *
|
Daniel@0
|
17 * A user simply updates the value of currentSerializedState attribute
|
Daniel@0
|
18 * and calls undo(), redo() or reset() to move along the history
|
Daniel@0
|
19 * There is no direct access to undo and redo stacks, but it is possible
|
Daniel@0
|
20 * to know about them by calling canUndo() and canRedo()
|
Daniel@0
|
21 *
|
Daniel@0
|
22 * serialize / unserialize deal with the object of the following structure
|
Daniel@0
|
23 * {
|
Daniel@0
|
24 * undoStack: [{recent}, {less recent}, ... {the oldest}]
|
Daniel@0
|
25 * redoStack: [{next}, {the following}, ... {the last}]
|
Daniel@0
|
26 * currentSerializedState: {}
|
Daniel@0
|
27 * }
|
Daniel@0
|
28 *
|
Daniel@0
|
29 * attribute compoundChangeDetector is a function that helps decide if the changes between
|
Daniel@0
|
30 * the given three serialized states can be treated as a single change.
|
Daniel@0
|
31 * This is needed to avoid too many undo states on simple actions like text typing
|
Daniel@0
|
32 * compoundChangeDetector returns true if the change between the first, the second
|
Daniel@0
|
33 * and the third object can be considered as a single compound change
|
Daniel@0
|
34 * (i.e. there is no need to create an extra undo action)
|
Daniel@0
|
35 */
|
Daniel@0
|
36 ContextModule.StateHistory = Backbone.Model.extend({
|
Daniel@0
|
37 defaults: {
|
Daniel@0
|
38 maxStackSize: 50,
|
Daniel@0
|
39 currentSerializedState: undefined,
|
Daniel@0
|
40 compoundChangeDetector: undefined
|
Daniel@0
|
41 },
|
Daniel@0
|
42
|
Daniel@0
|
43 _NONE: 0,
|
Daniel@0
|
44 _UNDO: 1,
|
Daniel@0
|
45 _REDO: 2,
|
Daniel@0
|
46 _UNSERIALIZE: 3,
|
Daniel@0
|
47
|
Daniel@0
|
48 /**
|
Daniel@0
|
49 * @memberOf App.ContextModule.StateBookmark
|
Daniel@0
|
50 */
|
Daniel@0
|
51 initialize: function() {
|
Daniel@0
|
52 this._undoStack = [];
|
Daniel@0
|
53 this._redoStack = [];
|
Daniel@0
|
54 this._currentSerializedStateChangeMode = this._NONE;
|
Daniel@0
|
55
|
Daniel@0
|
56 this.on("change:maxStackSize", this._trimStacks, this);
|
Daniel@0
|
57 this.on("change:currentSerializedState", this._registerNewSerializedState, this);
|
Daniel@0
|
58 },
|
Daniel@0
|
59
|
Daniel@0
|
60 _registerNewSerializedState: function() {
|
Daniel@0
|
61 var previousSerialisedState = this.previous("currentSerializedState");
|
Daniel@0
|
62 if (this._currentSerializedStateChangeMode == this._UNDO) {
|
Daniel@0
|
63 this._redoStack.unshift(previousSerialisedState);
|
Daniel@0
|
64 } else if (this._currentSerializedStateChangeMode == this._REDO) {
|
Daniel@0
|
65 this._undoStack.unshift(previousSerialisedState);
|
Daniel@0
|
66 } else if (this._currentSerializedStateChangeMode == this._UNSERIALIZE) {
|
Daniel@0
|
67 } else {
|
Daniel@0
|
68 if (!_.isEqual(previousSerialisedState, this.attributes.currentSerializedState)) {
|
Daniel@0
|
69 if (this._undoStack.length == 0
|
Daniel@0
|
70 || !_.isFunction(this.attributes.compoundChangeDetector)
|
Daniel@0
|
71 || !this.attributes.compoundChangeDetector.call(currentSerializedState, previousSerialisedState, this._undoStack[0]))
|
Daniel@0
|
72 {
|
Daniel@0
|
73 this._undoStack.unshift(previousSerialisedState);
|
Daniel@0
|
74 }
|
Daniel@0
|
75 this._redoStack = [];
|
Daniel@0
|
76 }
|
Daniel@0
|
77 }
|
Daniel@0
|
78 this._trimStacks();
|
Daniel@0
|
79 },
|
Daniel@0
|
80
|
Daniel@0
|
81 getCurrentSerializedState: function() {
|
Daniel@0
|
82
|
Daniel@0
|
83 },
|
Daniel@0
|
84
|
Daniel@0
|
85 undo: function() {
|
Daniel@0
|
86 if (this._undoStack.length) {
|
Daniel@0
|
87 this._currentSerializedStateChangeMode = this._UNDO;
|
Daniel@0
|
88 this.set("currentSerializedState", this._undoStack.shift());
|
Daniel@0
|
89 this._currentSerializedStateChangeMode = this._NONE;
|
Daniel@0
|
90 } else {
|
Daniel@0
|
91 throw "Undo was called when undo stack was empty";
|
Daniel@0
|
92 }
|
Daniel@0
|
93 },
|
Daniel@0
|
94
|
Daniel@0
|
95 redo: function() {
|
Daniel@0
|
96 if (this._redoStack.length) {
|
Daniel@0
|
97 this._currentSerializedStateChangeMode = this._REDO;
|
Daniel@0
|
98 this.set("currentSerializedState", this._redoStack.shift());
|
Daniel@0
|
99 this._currentSerializedStateChangeMode = this._NONE;
|
Daniel@0
|
100 } else {
|
Daniel@0
|
101 throw "Redo was called when undo stack was empty";
|
Daniel@0
|
102 }
|
Daniel@0
|
103 },
|
Daniel@0
|
104
|
Daniel@0
|
105 reset: function() {
|
Daniel@0
|
106 var stacksWereNotEmpty = this._undoStack.length || this._redoStack.length;
|
Daniel@0
|
107 this._undoStack = [];
|
Daniel@0
|
108 this._redoStack = [];
|
Daniel@0
|
109 if (stacksWereNotEmpty) {
|
Daniel@0
|
110 this.trigger("change");
|
Daniel@0
|
111 }
|
Daniel@0
|
112 },
|
Daniel@0
|
113
|
Daniel@0
|
114 canUndo: function() {
|
Daniel@0
|
115 return !!this._undoStack.length;
|
Daniel@0
|
116 },
|
Daniel@0
|
117
|
Daniel@0
|
118 canRedo: function() {
|
Daniel@0
|
119 return !!this._redoStack.length;
|
Daniel@0
|
120 },
|
Daniel@0
|
121
|
Daniel@0
|
122 serialize: function() {
|
Daniel@0
|
123 // logger.debug("method called: StateHistory::serialize");
|
Daniel@0
|
124
|
Daniel@0
|
125 var result = {
|
Daniel@0
|
126 currentSerializedState: this.attributes.currentSerializedState,
|
Daniel@0
|
127 undoStack: _.clone(this._undoStack),
|
Daniel@0
|
128 redoStack: _.clone(this._redoStack),
|
Daniel@0
|
129 };
|
Daniel@0
|
130
|
Daniel@0
|
131 return result;
|
Daniel@0
|
132 },
|
Daniel@0
|
133
|
Daniel@0
|
134 unserialize: function(serializedAttributes) {
|
Daniel@0
|
135 this._currentSerializedStateChangeMode = this._UNSERIALIZE;
|
Daniel@0
|
136
|
Daniel@0
|
137 var fixedSerializedAttributes = serializedAttributes;
|
Daniel@0
|
138 if (!_.isSimpleObject(serializedAttributes)) {
|
Daniel@0
|
139 fixedSerializedAttributes = {};
|
Daniel@0
|
140 }
|
Daniel@0
|
141
|
Daniel@0
|
142 this._undoStack = _.isArray(fixedSerializedAttributes.undoStack) ?
|
Daniel@0
|
143 fixedSerializedAttributes.undoStack : [];
|
Daniel@0
|
144 this._redoStack = _.isArray(fixedSerializedAttributes.redoStack) ?
|
Daniel@0
|
145 fixedSerializedAttributes.redoStack : [];
|
Daniel@0
|
146 this._trimStacks();
|
Daniel@0
|
147
|
Daniel@0
|
148 this.set("currentSerializedState", fixedSerializedAttributes.currentSerializedState);
|
Daniel@0
|
149
|
Daniel@0
|
150 this._currentSerializedStateChangeMode = this._NONE;
|
Daniel@0
|
151 },
|
Daniel@0
|
152
|
Daniel@0
|
153 _trimStacks: function() {
|
Daniel@0
|
154 if (this._undoStack.length > this.attributes.maxStackSize) {
|
Daniel@0
|
155 this._undoStack = this._undoStack.slice(0, this.attributes.maxStackSize);
|
Daniel@0
|
156 }
|
Daniel@0
|
157 if (this._redoStack.length > this.attributes.maxStackSize) {
|
Daniel@0
|
158 this._redoStack = this._redoStack.slice(0, this.attributes.maxStackSize);
|
Daniel@0
|
159 }
|
Daniel@0
|
160 }
|
Daniel@0
|
161 });
|
Daniel@0
|
162
|
Daniel@0
|
163 });
|
Daniel@0
|
164 }, Logger);
|