A JavaScript library for building out the logic and UI for business rules.
When you're working in a business environment (especially an "enterprisy" environment), business rules make the world go 'round. Business-level decision makers need to be able to alter application behavior and logic with minimal technical invasiveness.
Usually, business rule engines come packaged as $100K Java installations and a gaggle of business consultants. Let's just call this a lighter-weight solution.
The goal of this library is to:
Try out the demo here.
You can build rules based on the defined variables and operators (they're hard-coded in this demo, but they'd normally be sent dynamically from the server or rendered server-side). When you click
submitthe json document containing the rules you just defined gets printed to
console.log.
The demo code is just the
examplesdirectory which is a great place to get started, it demonstrates all the features pretty well!
If you want to play around with the example code and try out making changes, just fork the project and make some changes in the
gh-pagesbranch. When you push it they'll propagate to
.github.io/business-rules-ui/examples/
The
$.fn.conditionsBuilder()method has two forms, the first being:
$("#myDiv").conditionsBuilder({fields: [...], data: {...});
The first form creates a
ConditionsBuilderobject for the given DOM element with the passed fields and data. The
fieldsparam is an array of objects that define the factors that can be used in conditional statements. Each has a
label,
nameand an array of
operators. It may also have an
optionsarray of objects with
labeland
name.
Each operator is an object with
label,
nameand
fieldType. The
fieldTypecan be:
"none"- The operator does not require further data entry (ie
"present",
"blank").
"text"- User will be presented with an input of
type=text.
"textarea"- User will be presented with a textarea.
"select"- User will be presented with a select dropdown populated with the parent field's
optionsarray.
The
dataparam is an object that will be used to initially populate the UI (ie if business rules have already been created and the user wants to edit them). If the
dataoption is not passed, the UI will be generated without any initial conditions.
The object passed as
datashould be a "conditional object", meaning it has a single key of
allor
anyand a value of an array of nodes. These nodes can either be rule objects or nested conditional objects.
A rule object has
name,
operatorand
valuestrings. The
nameshould match a field's
nameproperty, the
operatorshould match an operator's
nameproperty, and the
valueis an arbitrary string value entered by the user in the UI.
Once the UI has been built by the
ConditionsBuilderand the user has entered information, the data can be retrieved by using the second form of the
conditionsBuildermethod:
var data = $("#myDiv").conditionsBuilder("data");
This will serialize the entered conditionals into a data object. This object can be persisted and then later used to create a new
ConditionsBuilderfor editing. This data object will also be used to instantiate a
BusinessRules.RuleEngineobject for running the conditional logic.
The
$.fn.actionsBuilderhas an identical API to
$.fn.conditionsBuilder, but it uses a different data structure. The
fieldsproperty should be an array of action objects. Each action object has a
labeland
name. An action object may have a
fieldsproperty that is an array of action objects, allowing for nested action data. All action objects that are not "top level" should also have a
fieldTypeof
text,
textareaor
select.
Here's an example of what a "Send Email" action could look like:
$("#myDiv").actionsBuilder({fields: [ {label: "Send Email", value: "sendEmail", fields: [ {label: "To", name: "to", fieldType: "text"}, {label: "CC", name: "cc", fieldType: "text"}, {label: "BCC", name: "bcc", fieldType: "text"}, {label: "Subject", name: "subject", fieldType: "text"}, {label: "Body", name: "body", fieldType: "textarea"} ]} ]});
Action objects with a
fieldTypeof
selectshould not have a
fieldsproperty -- rather they have an
optionsproperty with a
labeland
namefor each option. That option object, however, can have a
fieldsproperty. This allows you to specify nested fields that will only be displayed if the given option has been selected.
Building on the last example, this allows the user to specify an email template, or use a custom Subject and Body:
$("#myDiv").actionsBuilder({fields: [ {label: "Send Email", value: "sendEmail", fields: [ {label: "To", name: "to", fieldType: "text"}, {label: "CC", name: "cc", fieldType: "text"}, {label: "BCC", name: "bcc", fieldType: "text"}, {label: "Email Template", name: "template", fieldType: "select", options: [ {label: "Welcome Email", name: "welcomeEmail"}, {label: "Followup Email", name: "followupEmail"}, {label: "Custom Email", name: "customEmail", fields: [ {label: "Subject", name: "subject", fieldType: "text"}, {label: "Body", name: "body", fieldType: "textarea"} ]} ]} ]} ]});
In this example, the "Subject" and "Body" fields will only be displayed if the user has selected the "Custom Email" template option.
To get the data out of the UI, run the
actionsBuildermethod with
"data":
var data = $("#myDiv").actionsBuilder("data");
Each action data object has a
namethat matches the corresponding field's
value, and a
valueproperty with the user-entered value. It may also have a
fieldsarray of nested action data objects, which correspond to the nested field structure of the builder.
While the
ConditionsBuilderand
ActionsBuildergive us a UI to build business rule configurations, we still need something to interpret the configuration, apply the logic and conditionally run the actions. This is where the
BusinessRules.RuleEnginecomes in.
The
RuleEngineis initialized with a
conditionsobject and an
actionsarray, just as they would be when fetched from the UI using
conditionsBuilder("data")and
actionsBuilder("data"). This would be a common way of instantiating a
RuleEngine:
var engine = new BusinessRules.RuleEngine({ conditions: $("#myConditions").conditionsBuilder("data"), actions: $("#myActions").actionsBuilder("data") });
Once your engine has been instantiated, you can use the
#runmethod to apply the conditional logic to a set of data, and then conditionally run the actions. Since the engine is only responsible for running logic and shouldn't have to be aware of the actual data, you need to pass in an object that represents the context to run conditionals on, and another object with functions that map to the actions. For example:
var engine = new BusinessRules.RuleEngine({ conditions: {all: [{name: "name", operator: "present", value: ""}, {name: "age", operator: "greaterThanEqual", value: "21"}]}, actions: [{name: "action-select", value: "giveDrink", fields: [{name: "drinkType", value: "martini"}]}] });var conditionsAdapter = {name: "Joe", age: 22}; var actionsAdapter = {giveDrink: function(data) { alert("Gave user a " + data.find("drinkType")); } };
engine.run(conditionsAdapter, actionsAdapter);
Values used in the
conditionsAdaptercan be simple strings and numbers, but it can also be a function that will be lazily executed. For example, this adapter would pull the name and age from fields on the page (a more likely scenario than hard coded values):
var conditionsAdapter = { name: function() { $("#nameField").val(); }, age: function() { $("#ageField").val(); } };
It is also possible to use asynchronous functions in your conditionsAdapter. To do so, have your function accept a callback function and call it when you have your value.
var conditionsAdapter = { logoVisible: function(done) { // Cannot determine if logo is visible until DOM ready $(function() { var visible = $("#logo").is(":visible"); done(visible); }); } };
The
BusinessRules.RuleEngineobject can be used in either the browser or in a server environment (ie Node.js). It could also be ported to another language simply enough, or run inside a JavaScript runtime within Ruby, Java, etc.
The
RuleEnginecomes with the following standard operators that can be used inside conditionals:
Custom operators can be added to a
RuleEngineusing the
addOperatorsmethod:
var engine = new BusinessRules.RuleEngine({ conditions: {all: [{name: "password", operator: "longerThan", value: "6"}]}, actions: [] });engine.addOperators({ longerThan: function(actual, length) { return actual.length > parseInt(length, 10); } });
It is also possible to create asynchronous operators if your logic cannot be run synchronously. To do so, simply have your operator function accept a third callback param and call it when you have your result:
engine.addOperators({ delayedOperator: function(actual, target, done) { setTimeout(function() { done(true); }, 1000); } });
The
addOperatorsmethod also allows you to override the standard operators if, heaven forbid, you find that necessary.
When a function on your
actionsAdapterobject is called, it is passed a
Finderobject. The
Finderobject has a
dataproperty that will return the action's data structure so that you can traverse it yourself. This data structure can look something like:
[{name: "drinkType", value: "martini", fields: [ {name: "oliveCount", value: "3"}, {name: "shaken", value: "yes"} ]}]
While you certainly can traverse this structure, chances are you just want to quickly access the values. This is why the
Findergives you the
findconvenience method, which takes one or more names and returns the matching value:
var actionsAdapter = { giveDrink: function(data) { var drinkType = data.find("drinkType"), oliveCount = data.find("drinkType", "oliveCount"), shaken = data.find("drinkType", "shaken"); console.log(drinkType, oliveCount, shaken); // "martini", "3", "yes" } };
MIT - see the LICENSE.txt file.
This is from a fork of https://github.com/chrisjpowers/business-rules, the semantics have been modified a bit and we have split off the UI code so it can be used with a RESTful API to let the business logic run on the server.