-
Notifications
You must be signed in to change notification settings - Fork 0
/
ModelFlow.js
167 lines (124 loc) · 3.79 KB
/
ModelFlow.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
(function() {
var root = this;
var ModelFlow;
if (typeof exports !== 'undefined') {
ModelFlow = exports;
} else {
ModelFlow = root.ModelFlow = {};
}
var Backbone = root.Backbone;
var _ = root._;
if (!Backbone && (typeof require !== 'undefined')) Backbone = require('backbone');
if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
ModelFlow.getBackbone = function() {
return Backbone;
};
function createStateModel() {
var states = {},
currentStates = {},
constraints = {},
self = this,
disableConstraints = false;
//extract these values on mixin
if (!this.states) this.states = {};
if (!this.constraints) this.constraints = [];
function modelChange() {
var inStates = {},
outStates = {};
_.each(states, function(stateObj, state) {
if (_.all(_.keys(stateObj), function(key) {
return (self.get(key) === stateObj[key]);
})) {
if (!currentStates[state]) inStates[state] = stateObj;
} else if (currentStates[state]) {
outStates[state] = stateObj;
}
});
_.each(inStates, function(stateObj, state) {
//check constraints
if (!disableConstraints && constraints[state]
&& !_.all(constraints[state], function(outState) {
return outStates[outState];
})) return;
currentStates[state] = stateObj;
self.trigger("state:->"+state, stateObj);
_.each(outStates, function(outObj, outState) {
self.trigger("state:"+outState+"->"+state, stateObj);
});
});
_.each(outStates, function(stateObj, state) {
delete currentStates[state];
self.trigger("state:"+state+"->", stateObj);
});
}
function initialize() {
_.each(this.states, function(stateObj, key) {
this.createState(key, stateObj, true);
}, this);
_.every(this.constraints, function(constraint) {
self.addConstraint(constraint, true);
});
modelChange();
}
this.createState = function(name, props, silent) {
states[name] = props || {};
if (!silent) modelChange();
return this;
};
this.setState = function(stateName, ignoreContraints) {
var stateChanged = true;
disableConstraints = (ignoreContraints === true);
//I realize backbone docs say don't access this
//but its the easiest way to snapshop the attributes
//and provide a rollback
var previousState = _.clone(this.attributes);
//by setting all the keys and values
//to the model, a subsequent state change will occur
this.set(states[stateName], {
//make this silent if constraits are disabled
silent : disableConstraints
});
//if it didn't work, revert
if (!disableConstraints && !this.inState(stateName)) {
this.attributes = previousState;
stateChanged = false;
}
disableConstraints = false;
return stateChanged;
};
this.currentStates = function() {
return _.keys(currentStates);
};
this.inState = function(state) {
return _.any(this.currentStates(), function(active) {
return (active === state);
});
};
this.addConstraint = function(constraint, silent) {
var steps = constraint.split('->');
if (steps.length === 0) throw Error("Y U give no constraint");
var outState = steps.shift();
while(steps.length>0) {
var inState = steps.shift();
if (!constraints[inState]) constraints[inState] = [];
constraints[inState].push(outState);
outState = inState;
}
if (!silent) modelChange();
return this;
};
this.bind('change', modelChange.bind(this));
//alias node style events
this.on = this.bind;
this.emit = this.trigger;
initialize.call(this);
}
ModelFlow.StateModel = Backbone.Model.extend({
initialize : function(options) {
Backbone.Model.prototype.initialize.call(this, options);
createStateModel.call(this);
},
states : {},
constraints : []
});
}).call(this);