Permalink
Browse files

Add automatic generation of state predicates to StateMachine.

 - This means that as states are defined using the class level
   @transitions macro, functions and accessors are availble to ask the
state machine if it is in that particular state:

@sm.get('isOn')
@sm.isOn()
data-bind="model.lifecycle.isLoading"
  • Loading branch information...
1 parent 025b88b commit b13bf30632aab04c4e4286fdfb3054a0ee5520d9 @airhorns airhorns committed May 21, 2012
Showing with 96 additions and 23 deletions.
  1. +37 −11 lib/batman.js
  2. +37 −11 lib/dist/batman.node.js
  3. +15 −0 src/state_machine.coffee
  4. +7 −1 tests/batman/utilities/state_machine_test.coffee
View
48 lib/batman.js
@@ -5663,7 +5663,8 @@
(function() {
var __hasProp = {}.hasOwnProperty,
- __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ __slice = [].slice;
Batman.StateMachine = (function(_super) {
@@ -5676,7 +5677,7 @@
StateMachine.InvalidTransitionError.prototype = new Error;
StateMachine.transitions = function(table) {
- var k, object, transitions, v, _ref,
+ var definePredicate, fromState, k, object, predicateKeys, toState, transitions, v, _fn, _ref,
_this = this;
for (k in table) {
v = table[k];
@@ -5694,17 +5695,41 @@
table[k] = object;
}
this.prototype.transitionTable = Batman.extend({}, this.prototype.transitionTable, table);
+ predicateKeys = [];
+ definePredicate = function(state) {
+ var key;
+ key = "is" + (Batman.helpers.capitalize(state));
+ if (_this.prototype[key] != null) {
+ return;
+ }
+ predicateKeys.push(key);
+ return _this.prototype[key] = function() {
+ return this.get('state') === state;
+ };
+ };
_ref = this.prototype.transitionTable;
+ _fn = function(k) {
+ return _this.prototype[k] = function() {
+ return this.startTransition(k);
+ };
+ };
for (k in _ref) {
transitions = _ref[k];
- if (!this.prototype[k]) {
- (function(k) {
- return _this.prototype[k] = function() {
- return this.startTransition(k);
- };
- })(k);
+ if (!(!this.prototype[k])) {
+ continue;
+ }
+ _fn(k);
+ for (fromState in transitions) {
+ toState = transitions[fromState];
+ definePredicate(fromState);
+ definePredicate(toState);
}
}
+ if (predicateKeys.length) {
+ this.accessor.apply(this, __slice.call(predicateKeys).concat([function(key) {
+ return this[key]();
+ }]));
+ }
return this;
};
@@ -9373,7 +9398,7 @@
options = {};
}
path = this.rootPath + options.path;
- name = this.rootName + options.as;
+ name = this.rootName + Batman.helpers.camelize(options.as, true);
delete options.as;
delete options.path;
klass = options.callback ? Batman.CallbackActionRoute : Batman.ControllerActionRoute;
@@ -9390,12 +9415,13 @@
};
RouteMapBuilder.prototype._nestingPath = function() {
- var nestingParam;
+ var nestingParam, nestingSegment;
if (!this.parent) {
return "";
} else {
nestingParam = ":" + Batman.helpers.singularize(this.baseOptions.controller) + "Id";
- return "" + (this.parent._nestingPath()) + "/" + this.baseOptions.controller + "/" + nestingParam + "/";
+ nestingSegment = Batman.helpers.underscore(this.baseOptions.controller);
+ return "" + (this.parent._nestingPath()) + "/" + nestingSegment + "/" + nestingParam + "/";
}
};
View
48 lib/dist/batman.node.js
@@ -5663,7 +5663,8 @@
(function() {
var __hasProp = {}.hasOwnProperty,
- __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ __slice = [].slice;
Batman.StateMachine = (function(_super) {
@@ -5676,7 +5677,7 @@
StateMachine.InvalidTransitionError.prototype = new Error;
StateMachine.transitions = function(table) {
- var k, object, transitions, v, _ref,
+ var definePredicate, fromState, k, object, predicateKeys, toState, transitions, v, _fn, _ref,
_this = this;
for (k in table) {
v = table[k];
@@ -5694,17 +5695,41 @@
table[k] = object;
}
this.prototype.transitionTable = Batman.extend({}, this.prototype.transitionTable, table);
+ predicateKeys = [];
+ definePredicate = function(state) {
+ var key;
+ key = "is" + (Batman.helpers.capitalize(state));
+ if (_this.prototype[key] != null) {
+ return;
+ }
+ predicateKeys.push(key);
+ return _this.prototype[key] = function() {
+ return this.get('state') === state;
+ };
+ };
_ref = this.prototype.transitionTable;
+ _fn = function(k) {
+ return _this.prototype[k] = function() {
+ return this.startTransition(k);
+ };
+ };
for (k in _ref) {
transitions = _ref[k];
- if (!this.prototype[k]) {
- (function(k) {
- return _this.prototype[k] = function() {
- return this.startTransition(k);
- };
- })(k);
+ if (!(!this.prototype[k])) {
+ continue;
+ }
+ _fn(k);
+ for (fromState in transitions) {
+ toState = transitions[fromState];
+ definePredicate(fromState);
+ definePredicate(toState);
}
}
+ if (predicateKeys.length) {
+ this.accessor.apply(this, __slice.call(predicateKeys).concat([function(key) {
+ return this[key]();
+ }]));
+ }
return this;
};
@@ -9373,7 +9398,7 @@
options = {};
}
path = this.rootPath + options.path;
- name = this.rootName + options.as;
+ name = this.rootName + Batman.helpers.camelize(options.as, true);
delete options.as;
delete options.path;
klass = options.callback ? Batman.CallbackActionRoute : Batman.ControllerActionRoute;
@@ -9390,12 +9415,13 @@
};
RouteMapBuilder.prototype._nestingPath = function() {
- var nestingParam;
+ var nestingParam, nestingSegment;
if (!this.parent) {
return "";
} else {
nestingParam = ":" + Batman.helpers.singularize(this.baseOptions.controller) + "Id";
- return "" + (this.parent._nestingPath()) + "/" + this.baseOptions.controller + "/" + nestingParam + "/";
+ nestingSegment = Batman.helpers.underscore(this.baseOptions.controller);
+ return "" + (this.parent._nestingPath()) + "/" + nestingSegment + "/" + nestingParam + "/";
}
};
View
15 src/state_machine.coffee
@@ -15,9 +15,24 @@ class Batman.StateMachine extends Batman.Object
table[k] = object
@::transitionTable = Batman.extend {}, @::transitionTable, table
+
+ predicateKeys = []
+ definePredicate = (state) =>
+ key = "is#{Batman.helpers.capitalize(state)}"
+ return if @::[key]?
+ predicateKeys.push key
+ @::[key] = -> @get('state') == state
+
for k, transitions of @::transitionTable when !@::[k]
do (k) =>
@::[k] = -> @startTransition(k)
+
+ for fromState, toState of transitions
+ definePredicate(fromState)
+ definePredicate(toState)
+
+ if predicateKeys.length
+ @accessor predicateKeys..., (key) -> @[key]()
@
constructor: (startState) ->
View
8 tests/batman/utilities/state_machine_test.coffee
@@ -10,13 +10,19 @@ QUnit.module "Batman.StateMachine"
test "should start in the inital state given", ->
equal 'on', @sm.get('state')
+test "should define accessors and function state predicates", ->
+ ok @sm.isOn()
+ ok @sm.get('isOn')
+ ok !@sm.isOff()
+ ok !@sm.get('isOff')
+
test "should accept a transition table in the constructor", ->
equal @sm.get('state'), 'on'
@sm.switch()
equal @sm.get('state'), 'off'
@sm.switch()
equal @sm.get('state'), 'on'
- @sm.startTransition('switch') # transitions can be called using strings as well
+ @sm.startTransition('switch')
equal @sm.get('state'), 'off'
ok @sm.canStartTransition('switch')

0 comments on commit b13bf30

Please sign in to comment.