Last active
December 28, 2015 11:29
-
-
Save jhorman/7493652 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/////////////////////////// | |
// JAVASCRIPT RULE ENGINE | |
/////////////////////////// | |
/*global define*/ | |
define([ | |
'lodash.noconflict' | |
], function (_) { | |
'use strict'; | |
function Rules(options) { | |
var self = this, | |
rules = [], | |
facts = options && options.facts || {}, | |
chain = {}, | |
inFire = false, | |
queueFire = false; | |
this.rules = rules; | |
this.facts = facts; | |
function fire() { | |
// if a rule fire asserts more facts, queue fire until all of the | |
// current rules are processed. | |
if (inFire) { | |
queueFire = true; | |
} else { | |
inFire = true; | |
try { | |
_.each(rules, function (rule) { | |
var result = rule.condition(facts); | |
// only fire a rule if its condition result changes | |
if (result && result !== chain[rule.id]) { | |
self.fireRule(rule); | |
} | |
chain[rule.id] = result; | |
}); | |
} finally { | |
inFire = false; | |
} | |
var shouldFire = queueFire; | |
queueFire = false; | |
if (shouldFire) { | |
fire(); | |
} | |
} | |
} | |
this.fireRule = function (rule) { | |
rule.fire(this, facts); | |
}; | |
this.add = function (newRules) { | |
rules = rules.concat(newRules); | |
_.each(rules, function (rule, index) { | |
rule.id = index; | |
}); | |
return this; | |
}; | |
this.fact = function (name, value) { | |
if (facts[name] !== value) { | |
var oldFacts = _.clone(facts); // probably needs to be a deep clone | |
facts[name] = value; | |
try { | |
fire(); | |
return true; | |
} catch (e) { | |
// if asserting a fact throws, reset state | |
facts = oldFacts; | |
this.facts = oldFacts; | |
throw e; | |
} | |
} | |
return false; | |
}; | |
// add the rules passed into the construct. we use ".add" since it | |
// id's the rules, which we need for tracking them in "chain". | |
if (options && options.rules) { | |
this.add(options.rules); | |
} | |
} | |
function resolveLeft(facts, left) { | |
return typeof(left) === 'function' ? left(facts) : facts[left]; | |
} | |
function resolveRight(facts, right) { | |
return typeof(right) === 'function' ? right(facts) : right; | |
} | |
Rules.fact = function (name) { | |
return function (facts) { | |
return facts[name]; | |
}; | |
}; | |
Rules.and = function (f1, f2) { | |
return function (facts) { | |
return f1(facts) && f2(facts); | |
}; | |
}; | |
Rules.or = function (f1, f2) { | |
return function (facts) { | |
return f1(facts) || f2(facts); | |
}; | |
}; | |
Rules.eq = function (fact, value) { | |
return function (facts) { | |
return resolveLeft(facts, fact) === resolveRight(facts, value); | |
}; | |
}; | |
Rules.neq = function (fact, value) { | |
return function (facts) { | |
return resolveLeft(facts, fact) !== resolveRight(facts, value); | |
}; | |
}; | |
Rules.gt = function (fact, value) { | |
return function (facts) { | |
return facts[fact] !== undefined && facts[fact] > value; | |
}; | |
}; | |
Rules.lt = function (fact, value) { | |
return function (facts) { | |
return facts[fact] !== undefined && facts[fact] < value; | |
}; | |
}; | |
Rules.gte = function (fact, value) { | |
return function (facts) { | |
return facts[fact] !== undefined && facts[fact] >= value; | |
}; | |
}; | |
Rules.lte = function (fact, value) { | |
return function (facts) { | |
return facts[fact] !== undefined && facts[fact] <= value; | |
}; | |
}; | |
Rules.setFact = function (name, value) { | |
return function (rules) { | |
rules.fact(name, value); | |
}; | |
}; | |
return Rules; | |
}); | |
/////////////////////////////// | |
// HTML5 Video Rules | |
/////////////////////////////// | |
var rules = new Rules({ | |
facts: { | |
readyState: 0, | |
duration: null, | |
lastDuration: null, | |
progressAmount: 0, | |
event: null | |
} | |
}); | |
rules.add([ | |
{ | |
name: 'duration change', | |
condition: function (facts) { | |
return facts.duration !== facts.lastDuration; | |
}, | |
fire: function (facts) { | |
console.log('durationchange ' + facts.duration); | |
rules.fact('lastDuration', facts.duration); | |
} | |
}, | |
{ | |
name: 'readyState gt 0', | |
condition: rules.gt('readyState', 0), | |
fire: function () { | |
console.log('loadedmetadata'); | |
} | |
}, | |
{ | |
name: 'readyState gt 1', | |
condition: rules.gt('readyState', 1), | |
fire: function () { | |
console.log('loadeddata'); | |
} | |
}, | |
{ | |
name: 'readyState gt 2', | |
condition: rules.gt('readyState', 2), | |
fire: function () { | |
console.log('canplay'); | |
} | |
}, | |
{ | |
name: 'readyState gt 3', | |
condition: rules.gt('readyState', 3), | |
fire: function () { | |
console.log('canplaythrough'); | |
} | |
}, | |
{ | |
name: 'duration set', | |
condition: rules.gt('duration', 0), | |
fire: rules.setFact('readyState', 1) | |
}, | |
{ | |
name: 'progress', | |
condition: rules.eq('event', 'progress'), | |
fire: rules.setFact('readyState', 2) | |
}, | |
{ | |
name: 'canplay', | |
condition: rules.eq('event', 'canplay'), | |
fire: rules.setFact('readyState', 3) | |
}, | |
{ | |
name: 'canplaythrough', | |
condition: rules.eq('event', 'canplaythrough'), | |
fire: rules.setFact('readyState', 4) | |
}, | |
{ | |
name: 'progressAmount >= duration', | |
condition: function (facts) { | |
return facts.progressAmount !== undefined && facts.duration !== undefined && facts.duration > 0 && | |
facts.progressAmount >= facts.duration; | |
}, | |
fire: rules.setFact('readyState', 4) | |
} | |
]); | |
// TEST, DURATION 1 should fire durationchange and loadedmetadata | |
rules.fact('duration', 1); | |
// TEST, SHOULD ONLY FIRE DURATION CHANGE | |
rules.fact('duration', 2); | |
// TEST, SHOULD ONLY FIRE DURATION CHANGE | |
rules.fact('duration', 3); | |
// TEST, SHOULD FIRE LOADEDDATA, CANPLAY, CANPLAYTHROUGH | |
rules.fact('progressAmount', 3); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment