![Build Status](https://travis-ci.org/pocesar/js-chain-commander.png?branch=master)
Chain Commander
Chain commander is a library based on Q library, to encapsulate
business rules logic in form of javascript objects or JSON.
Since it uses promises, it can keep going until no more "then"s are found, while mutating the value it started with.
You can store your commands in JSON or in a POJO (plain old javascript object), and execute them whenever you need,
asynchronously, associating them with a context.
You can change the business logic in your database/json definition files, and don't ever have to touch your frontend code,
it all depends on the context and your defined functions.
Usage
Install from NPM:
npm install chain-commander
Require it in your node:
var
Q = require('q'),
cc = require('chain-commander')(Q);
or browser, after Q:
<script src="q.js"></script>
<script src="chain-commander.js"></script>
Business rules
Let's pretend you have a lot of users and each user have their own triggers and conditional, so they can
execute their own "code" in your app, like give instructions to your app to execute code in order they are meant to.
The rules are meant to be "non verbose", to be easy to write and adapt easily, when testing with new rules on the database for example.
var cmds = [
{
if: {
check: [
['check', 'first'],
['check', 'second']
],
exec : [
['multiply', 10]
],
else : {
if : {
check: [
['check', 'third']
],
exec : [
['alert', {msg: 'aww yeah'}]
]
},
exec: [
['add', 1],
['alert', {msg: 'else'}]
]
}
}
},
{
exec: [
['alert', {msg: 'Doodidoo'}]
]
},
{
exec: [
['startTimer']
]
},
{
exec: [
['goOn']
]
}
];
Given the above object/JSON, you will create a new function that will execute everything in order:
var instance = {
myvalue : 3,
check : function (type, value){
if (type === 'first') {
return value > 0;
} else if (type === 'second') {
return value < 30;
}
return true;
},
getValue : function (){
return this.myvalue === 3;
},
multiply : function (number, value){
return value * number;
},
add : function (number, value){
return value + number;
},
log : function (args, value){
console.log(args.msg + ': ' + value);
return value;
},
goOn : function (value){
if (!this.secondPass) {
this.secondPass = true;
return cc.execute(value, instance);
}
},
startTimer: function (args, value){
var d = Q.defer();
if (this.secondPass === false) {
setTimeout(function (){
d.resolve('Oops');
}, 1000);
} else {
d.reject();
}
return d.promise;
}
};
cc = CC(cmds);
cc.execute(10, instance).done(function (value){
console.log(value);
done();
});
The above is a silly example, below is a more serious usage, in a "build your plan with combos and many activities" type of app:
The below example is a directive in AngularJS:
app.directive('selectItem', function($timeout){
return {
restrict: 'A',
controller: function($scope){
var allItems = function(ids, state, where){
for(var i = 0, len = $scope.items.length; i < len; i++) {
if (ids.indexOf($scope.items[i].id) !== -1) {
$scope.items[i][where] = state;
}
}
};
this.select = function(ids){
allItems(ids, $scope.item.selected, 'selected');
};
this.isSelected = function(){
return $scope.item.selected;
};
this.toggle = function(ids){
allItems(ids, !$scope.item.selected, 'visible');
};
this.isCombo = function(ids){
if (ids.indexOf(scope.item.id) !== -1){
return true;
}
return false;
};
this.sum = function(){
value = 0;
for(var i = 0, len = $scope.items.length; i < len; i++) {
if ($scope.items[i].selected) {
value += $scope.items[i].price;
}
}
return value;
}
},
link: function(scope, element, attrs, controller){
element.on('click', function(){
scope.item.definition(0, controller).done(function(value){
$timeout(function(){
scope.value = value;
});
});
});
}
};
});
app.controller('SelectCtrl', function($scope, $http){
$scope.value = 0;
$scope.items = [];
$http.get('/items.json').success(function(items){
$scope.items = items;
for(var i = 0, len = $scope.items.length; i < len; i++){
$scope.items[i].definition = new ChainCommander($scope.items.definition);
}
});
});
This is items.json
:
[
{
"name":"Sky diving",
"selected":false,
"visible":true,
"id":1,
"price":2900,
"definitions":[]
},
{
"name":"Snowboarding",
"selected":false,
"visible":true,
"id":2,
"combo":[4],
"price":780,
"definitions":[
{"if": {
"conditions":[
["isSelected"]
],
"exec":[
["toggle",[1,3]]
]
}},
{"if": {
"conditions":[
["isCombo"]
],
"exec":[
["discount"],
["sum"]
]
}}
]
},
{
"name":
"Scuba Diving",
"selected":false,
"visible":true,
"id":3,
"price":250,
"definitions":[]
},
{
"name":"Trail Bike",
"selected":false,
"visible":true,
"id":4,
"price":1250,
"definitions":[
{"if": {
"conditions":[
["isSelected"]
],
"exec":[
["toggle",[1,2,3]]
]
}}
]
}
]
Debug
The definition and the executing code is "forgiving", it means, if you try to use a function that doesnt exists,
it will fail silently and continue in the chain. You can disable this by setting the instance "throws" to true
You can watch the flow in a verbose mode setting the
var cc = ChainCommander(cmds, {debug: true, throws: true});
then watch your console