JayRule (Guarded Giraffe) (& later)
JayRule version v20190819 (Guarded Giraffe) & later ..
(For later version, see JayRule (Indolent Iguana) )
What is a JayRule Ruleset
A JayRule ruleset is one which defines the ruleset's behaviours in a JSON object called "ruleset". This structure is processed by a script from an included RulesetInclude called "JayRule Ruleset Overlay JS", to perform as if it were a fully scripted ruleset.
Prior to JayRule, rulesets are constructed as a JSON/javascript object, but the structure is much looser, more prone to error, and requiring a lot of common "infrastructural" scripting, which JayRule now takes care of behind the scenes.
This version of the documentation relates to versions 20190819 (Guarded Giraffe) and later; for earlier versions see the previous JayRule documentation..
Features of a JayRule Ruleset
- Formats each rule in a distinct structured block .
- Handles synchronous and asynchronous processes behind the scenes.
- Handles logging for:
- Start of ruleset
- Completion of ruleset
- Start of rule
- Completion of rule (and whether okay or in error)
- Handles the callback topology needed to maintain a correct ruleset
- Asynchronous rules only require a single "callback()"
- hasRun flags automatically set internally.
- Better error handling and logging.
- All logging (ntf.logger.info, etc) prefixed with "RSLog:" or a developer specified prefix.
Added Features
The following are ready made state properties of the ntf object
Property | Description |
---|---|
ntf.isNew | whether document is new or existing (nb -- this is incorrect for PostSave, use following in initialisation: ntf.isNew = (sh && sh.previousVersionId) ? false : true; |
ntf.isClientEvent | whether the ruleset is running as a client event or a serverside event |
ntf.userId | the documentId of the current user's account document |
Basic Syntax
{
#include "JayRule Ruleset Overlay JS",
ruleset : {
name : 'name-of-ruleset',
rule-name : {
ruleCondition : rule-condition-function | boolean,
or
fieldChanged : boolean,
ruleAction : rule-action-function
},
rule-name : {
...
}
}
NB: This is a simplified syntax -- other optional features of the syntax are explained in later documents.
ruleset
The main object. Must be named "ruleset".
name : name-of-ruleset
The name of the ruleset to use in the logging of the ruleset, eg
name : 'Acme Case - OnLoad',
rule-name
Identifier/key of the rule. Must be distinctly named. Must begin with lowercase "rule". Eg
ruleInitialiseFacts : {
Each rule will be run in the sequence as ordered within the ruleset.
rule-condition-function
A function that returns true (or non-empty/non-zero) if the rule is to proceed.
This function must be of the format
ruleCondition : function(ntf) {...}
Note: this function must not contain asynchronous processes.
Eg
ruleCondition : function(ntf) {
return (ntf.context.fieldChanged === 'checkVenomous');
},
ruleCondition can also be assigned simply true (or false) for rules that must always (never) run.
Eg
ruleCondition : true,
fieldChanged
In the case of OnFieldChange rulesets only, we can specify just the name of the field changed. This replaces ruleCondition.
Eg
fieldChanged : 'customerName',
This has the same effect as:
ruleCondition : function(ntf) {
return (ntf.context.fieldChanged === 'customerName');
}
rule-action-function
A function that performs the rule's action.
If asynchronous, the function must be of the format:
function(ntf, callback) {...}
By adding the callback argument, we are telling the ruleset that we want it to wait for an asynchronous process within this rule to complete, before going onto the next rule.
Eg
ruleAction : function(ntf,callback) {
var eqry = {"query": {"bool": {"filter": [
{"term" : {"documentId" : ntf.user.documentId}}
]}}};
ft3.findDocumentsByElastic(eqry, ntf.userId, function(err,result) {
if (result) {
var udoc = result.data.hits.hits[0]._source;
ntf.document.description = 'Email fetched: ' + udoc.email;
}
callback();
});
}
If not asynchronous, it should be of the format:
function(ntf) {...}
With this format, the ruleset will simply run the contained code through, and then proceed to the next rule.
Eg
ruleAction : function(ntf) {
var ft3 = ntf.scope;
var flag = (ntf.document.checkVenomous === true);
ft3.showField('shWarning', flag);
}
Example
The following is a simple ruleset including two non-asynchronous rules, plus one asynchronous rule.
// RuleSet: Dexwise Spider - OnFieldChange
// Updated by: peter.dexter@fieldtec.com 2018-03-03 17:23:49 +11:00
{
#include "JayRule Ruleset Overlay JS",
ruleset : {
name : "Dexwise Spider - OnFieldChange",
// ---------------------------------------------------------------------
// ruleVenomous
// Shows/hides a warning based on the checkbox "Venomous"
// ---------------------------------------------------------------------
ruleVenomous : {
fieldChanged : 'checkVenomous',
ruleAction : function(ntf) {
var flag = (ntf.document.checkVenomous === true);
ft3.showField('shWarning', flag);
}
},
// ---------------------------------------------------------------------
// rulePopGenusSpecies
// If entered description is 'abc123',
// set genus to 'Arceteuthis' and species to 'Duxiformi'
// ---------------------------------------------------------------------
rulePopGenusSpecies : {
ruleCondition : function(ntf) {
return (
ntf.context.fieldChanged === 'description'
&& ntf.document.description === 'abc123'
);
},
ruleAction : function(ntf) {
delete ntf.document.description;
ntf.document.genus = 'Arceteuthis';
ntf.document.species = 'Duxiformi';
}
},
// ---------------------------------------------------------------------
// ruleGetUserByQuery
// Queries for the user account document and outputs the email to the
// description.
// NB - we could just use the object ntf.user, but we want to demonstrate asynchronous query usage here
// ---------------------------------------------------------------------
ruleGetUserByQuery : {
ruleCondition : function(ntf) {
return (
ntf.context.fieldChanged === 'commonName'
&& ntf.context.newValue === 'ubug666'
);
},
ruleAction : function(ntf,callback) {
var eqry = {"query": {"bool": {"filter": [
{"term" : {"documentId" : ntf.user.documentId}}
]}}};
ft3.findDocumentsByElastic(eqry, ntf.userId, function(err,result) {
if (err) {
var msg = err.message || JSON.stringify(err);
ntf.logger.error('Error on query: ' + msg);
ntf.errorMessage = 'Error on query: ' + msg;
}
else if (result && result.data && result.data.hits && result.data.hits.total) {
var udoc = result.data.hits.hits[0]._source;
ntf.document.description = 'Email fetched: ' + udoc.email;
delete ntf.document.commonName ;
}
callback();
});
}
}
}
}
JayRule Extended Syntax
This document outlines the further main properties that may be set on the JayRule "ruleset" object
Syntax
{
#include "JayRule Ruleset Overlay JS",
ruleset : {
name : 'name-of-ruleset',
timeout: 'timeout-in-milliseconds',
logging: {
showUncalledRules : true| false,
debugEmail : 'email-address',
debugEmailAll : 'true|false',
logPrefix: 'logging-prefix',
},
rule-name : { ... }
}
ruleset
The main object. Must be named "ruleset".
name : name-of-ruleset
The name of the ruleset to use in the logging of the ruleset, eg
name : 'CWW Case - OnLoad',
NB -- If you set this to '{{{rulesetName}}}' (as is default for a new ruleset), then on saving, the name/object event descriptor of the ruleset will be substituted in.
logging.debugEmail : 'email-address'
When on a server side ruleset, an email containing the logger lines will be sent to the target email address on completion of each run of the ruleset. Any lines issued to the log via ntf.logger.info
or .debug
or .error
will be sent. Use for debugging purposes only, of course.
Requires that the server is configured to send emails, and is working. Depending on any syntax errors, may fail to send.
logging : {
debugEmail : 'adrian.sharples@formbird.com',
...
}
JayRule, as of v20190819 (Guarded Giraffe) and later, will only send debug emails when the ruleset is triggered by the matching user, ie the login email address must match the debugEmail setting. This was done to reduce the risk of unexpected floods of emails when done by other parties.
This can be overridden with logging.debugEmailAll (see below).
NB - the direct setting (not in logging) will still work with newer versions of JayRule.
logging.debugEmailAll : true|false
When debugEmail has been configured, restores the old feature that emails will be sent on any user triggering the ruleset, not just the specified user as per the debugEmail setting.
JayRule, as of v20190819 (Guarded Giraffe) and later, will only send debug emails when the ruleset is triggered by the matching user, ie the login email address must match the debugEmail setting. This was done to reduce the risk of unexpected floods of emails when done by other parties.
logging : {
debugEmail : 'adrian.sharples@formbird.com',
debugEmailAll : true
}
timeout: 'timeout-in-milliseconds',
Sets the maximum time that should be allowed for the whole ruleset to run, in milliseconds.
This can be useful for tracking processes that fail to complete, or where a rule requires a callback, but it has not been made. Without a timeout set, these processes typically just stop, and never return
If not set, timeout defaults to 120000 (120 seconds).
To disable timeout, set to -1.
timeout : 60000 // Set to 60 seconds
Please note - having a definite short or medium timeout can be problematic when the ruleset needs to process input from the user, eg a dialog is presented with either OK or Cancel buttons, and separate process is required on each option.
logging.logPrefix : 'logging_prefix'
Changes the general logging prefix to a custom one, in case you need to filter logging specifically to this ruleset. Default prefix is "RSLog:"
logPrefix : 'RSLog[CWX]:',
logging : {
logPrefix : 'RSLog[CWX]:',
...
}
logging.showUncalledRules : true|false
Sets whether to show a logging for rules that do not get called due to their condition not being met. Prior to this version, logging of uncalled rules was always shown.
Defaults to false from the above version (Guarded Giraffe).
JayRule - Using shared functions/objects
The ntf.scope, or ft3, now carries all the "shared" functions and objects available to rulesets, including
- the "global" functions, eg
showField(...)
- functions and objects scripted before/after the ruleset definition
- the objects from #includes
These are available in both the ruleCondition and the ruleAction.
NOTE:
In earlier versions of JayRule, it was necessary to assign ft3 = ntf.scope at the beginning of each function that needs to use it, either the ruleCondition function or the ruleAction function.
In recent and current versions, ft3 has been made a global object, so this is no longer necessary. You may however see rulesets which do this for each function block -- there is no harm done here, it still works perfectly.
Formbird Functions for Rulesets
The explicitly coded functions provided by the FormBird application to the rulesets are available through ntf.scope, or ft3, as previously practised.
Eg
{
#include "JayRule Ruleset Overlay JS",
ruleset : {
ruleDoThingA : {
ruleCondition : ...,
ruleAction : function(ntf) {
ft3.showField('dateInitiated', true);
...
}
Functions/objects from #include
Functions and objects defined within a #include rulesetInclude are available in ft3 (ntf.scope).
Eg
{
#include "JayRule Ruleset Overlay JS",
#include "Basic Functions JS", // provides object "basicFunctions"
...
ruleset : {
ruleDoThingA : {
ruleCondition : function(ntf) {
// Perform function from #include "Basic Functions
var x = ft3.basicFunctions.datePrecedes(ntf.document.date1, ntf.document.date2);
...
}
Functions/objects from the ruleset
Functions and objects defined within the main block of the script (sibling to the object "ruleset") are available in ft3 (ntf.scope).
Eg
{
#include "JayRule Ruleset Overlay JS",
getFullName : function(field) {
var name = field.firstName + ' ' + field.surname;
return name;
},
ruleset : {
ruleDoThingA : {
ruleCondition : function(ntf) {
// Test full name
var x = ft3.getFullName(ntf.document.contact);
return (x === 'Peter Dexter');
}
...
}
...
}
}
JayRule - Raising Errors
If a ruleAction process results in the ruleset needing to abort, with an error statement, eg on validating fields in PreSave, the process is:
- set ntf.errorMessage to the error you want displayed/reported
To terminate the block at that point:
- callback(), if the ruleAction has a callback in its function signature, ie is asynchronous; if not, ignore.
- return; (for either asynchronous/non-asynchronous)
Example
if (ntf.userType === 'forbidden') {
ntf.errorMessage = 'Cannot perform this action for forbidden User Type';
return;
}
That's it.
If there is no return/callback, the ruleset will terminate at the completion of that rule block.
Failing a PreSave Ruleset without dialog
(Available with JayRule (Haughty Hedgehog) & later; Formbird v3.1.5.xxx and later)
Traditionally, when a PreSave ruleset is failed (as per prior section), a dialog is presented to the user with the ntf.errorMessage text.
Often this is not desired, because the developer has already scripted a more suitable dialog, or has prompted for verification with their own dialog, after which they don't want a further dialog to click OK to.
There are two means to effect a failure of the PreSave ruleset without showing the extra dialog:
ntf.rulesetFailed = true;
or
ntf.errorMessage = '(no-display)';
ft3.ModalService.openModal({
title : 'Widget Not Ready',
text : 'This widget should have further information. Do you really want to save?',
showCancelButton : true,
type : 'warning'
})
.then (
function confirmF() {
// do nothing, continue to ruleset completion
}),
function cancelF() {
ntf.rulesetFailed = true; // Cancel save, without any "oops" dialog
}
JayRule - Ending a Ruleset (without error)
In order to end a ruleset, simply set ntf.rulesetEnd to true:
ruleNoCascade : {
ruleCondition : function(ntf) {
return (ntf.document.flagNoCascade);
},
ruleAction : function(ntf) {
delete ntf.document.flagNoCascade;
ntf.rulesetEnd = true;
}
}
The ruleset will return a success flag to the Formbird system, indicating that the ruleset ended successfully. (ala old method callbackSuccess()). No further rules will be executed.
JayRule - Running Asynchronous Rules
Rules that require to run an asynchronous process should be formatted as below:
ruleName : {
ruleCondition : function(ntf) {
...
},ruleAction : function(ntf, callback) {
...
asyncFunction(..., function(args) {
...
callback();
}
}
}
Note the salient features:
- ruleAction has two arguments -- ntf and callback
- callback() is called at the endpoint of the rule, typically within the callback of an asynchronous function
CAUTION
It is VITAL that your rule only calls callback() once in the course of a rule. It may be written several times within the rule, but only one should ever be called in any one running of the rule.
Current versions of the JayRule overlay will only process for the first callback, and prevent 2nd , 3rd, 4th, etc callbacks from proceeding. The logging will display "RSLog:!!! Multiple callbacks detected - aborting"
Example
// ---------------------------------------------------------------------
// ruleGetUserByQuery
// Queries for the user account document and outputs the email to the
// description.
// NB - we could just use the object ntf.user, but we want to
// demonstrate asynchronous query usage here
// ---------------------------------------------------------------------
ruleGetUserByQuery : {
ruleCondition : function(ntf) {
return (
ntf.context.fieldChanged === 'commonName'
&& ntf.context.newValue === 'ubug666'
);
},
ruleAction : function(ntf,callback) { // <<<<<<<<<<<<<<<<<<<<<<<<<<<<
var eqry = {"query": {"bool": {"filter": [
{"term" : {"documentId" : ntf.user.documentId}}
]}}};
ft3.findDocumentsByElastic(eqry, ntf.userId, function(err,result) {
if (err) {
ntf.errorMessage = 'Error on query: ' + err.message;
}
else if (result && result.data && result.data.hits && result.data.hits.total) {
var udoc = result.data.hits.hits[0]._source;
ntf.document.description = 'Email fetched: ' + udoc.email;
delete ntf.document.commonName ;
}
callback(); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
});
}
}