• Stars
    star
    169
  • Rank 224,453 (Top 5 %)
  • Language
    JavaScript
  • License
    Other
  • Created about 13 years ago
  • Updated over 11 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A JavaScript library for building out the logic and UI for business rules.

Business Rules for JavaScript

VIEW THE DEMO NOW

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:

  1. Give JavaScript developers drop-in jQuery UI widgets for building business rule interfaces.
  2. Give developers a rule engine that can be ported to any server-side language for running business rules built with the UI tools.

Examples

Checkout the examples directory if you just want to dive in, it demonstrates all the features pretty well!

$.fn.conditionsBuilder()

The $.fn.conditionsBuilder() method has two forms, the first being:

$("#myDiv").conditionsBuilder({fields: [...], data: {...});

The first form creates a ConditionsBuilder object for the given DOM element with the passed fields and data. The fields param is an array of objects that define the factors that can be used in conditional statements. Each has a label, name and an array of operators. It may also have an options array of objects with label and name.

Each operator is an object with label, name and fieldType. The fieldType can 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 options array.

The data param 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 data option is not passed, the UI will be generated without any initial conditions.

The object passed as data should be a "conditional object", meaning it has a single key of all or any and a value of an array of nodes. These nodes can either be rule objects or nested conditional objects.

A rule object has name, operator and value strings. The name should match a field's name property, the operator should match an operator's name property, and the value is an arbitrary string value entered by the user in the UI.

Once the UI has been built by the ConditionsBuilder and the user has entered information, the data can be retrieved by using the second form of the conditionsBuilder method:

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 ConditionsBuilder for editing. This data object will also be used to instantiate a BusinessRules.RuleEngine object for running the conditional logic.

$.fn.actionsBuilder()

The $.fn.actionsBuilder has an identical API to $.fn.conditionsBuilder, but it uses a different data structure. The fields property should be an array of action objects. Each action object has a label and name. An action object may have a fields property that is an array of action objects, allowing for nested action data. All action objects that are not "top level" should also have a fieldType of text, textarea or 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 fieldType of select should not have a fields property -- rather they have an options property with a label and name for each option. That option object, however, can have a fields property. 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 actionsBuilder method with "data":

var data = $("#myDiv").actionsBuilder("data");

Each action data object has a name that matches the corresponding field's value, and a value property with the user-entered value. It may also have a fields array of nested action data objects, which correspond to the nested field structure of the builder.

BusinessRules.RuleEngine

While the ConditionsBuilder and ActionsBuilder give 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.RuleEngine comes in.

The RuleEngine is initialized with a conditions object and an actions array, 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 #run method 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 conditionsAdapter can 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.RuleEngine object 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.

Conditional Operators

The RuleEngine comes with the following standard operators that can be used inside conditionals:

  • present
  • blank
  • equalTo
  • notEqualTo
  • greaterThan
  • greaterThanEqual
  • lessThan
  • lessThanEqual
  • includes
  • matchesRegex

Custom operators can be added to a RuleEngine using the addOperators method:

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 addOperators method also allows you to override the standard operators if, heaven forbid, you find that necessary.

Action Functions

When a function on your actionsAdapter object is called, it is passed a Finder object. The Finder object has a data property 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 Finder gives you the find convenience 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"
  }
};

More Repositories

1

iterm_window

Control your terminal windows in iTerm with Ruby -- great for automation scripting!
Ruby
26
star
2

air-drop

Node.js Connect middleware for compiling, concatenating and delivering your source code to the browser.
JavaScript
21
star
3

flexible_csv

Import CSV files when you don't know exactly what the headers will be called.
Ruby
15
star
4

dynamic_forms

Rails plugin to allow your users to build their own forms in your app.
Ruby
11
star
5

web_sockets_demo

A demo application to accompany my talk about HTML5 Web Sockets
JavaScript
10
star
6

js_refactoring_demo

This is the companion code to my "7 Steps to Better Javascript" presentation.
JavaScript
8
star
7

feat

A Node.js framework for organizing and loading code by feature with built-in rollout support.
CoffeeScript
7
star
8

jasmine_demo

Javascript TDD Demo with Jasmine
JavaScript
6
star
9

multiserver_whenever

Provides the multiserver_whenever command for generating host-specific crontabs with role-based whenever files.
Ruby
5
star
10

rdoc_metric

RdocMetric analyzes the Rdoc coverage of your Ruby files.
Ruby
4
star
11

solid-in-60

Code examples to the "SOLID Principles in 60 Minutes" talk
JavaScript
3
star
12

coding-stage

JavaScript
3
star
13

kona

Kona - A JavaScript Bindings Library
JavaScript
2
star
14

coding-dojo

Resources for running a Coding Dojo, including katas and constraints
2
star
15

chrisjpowers.github.com

Personal homepage & blog
JavaScript
2
star
16

fire_up

The FireUp gem gives you the fire_up command to automatically open the files and start the processes you need to work on your Rails project.
Ruby
1
star
17

advent-of-code-2021

Advent of Code 2021
Python
1
star
18

euchre

Euchre card game implementation in Elixir
Elixir
1
star
19

chrome-extension-demo

Simple demo of building a Chrome extension and app
CoffeeScript
1
star
20

backup-atom

Scripts and repo for backing up Atom config
CSS
1
star
21

sharing-js-slides

JavaScript
1
star