Skip to content

JayRule (Ornery Otter)

Updated pdexter 2024-08-29

This version of the JayRule include is designed for use with Formbird v4.2.x and later.

This version of the documentation relates to versions 20240426 (Ornery Otter) and later; for earlier versions see previous JayRule documentation..

jaybird

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.

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()"
  • 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;

Addition:
There is a built in ntf.context.isNew also available with recent versions of Formbird (v4.1.47 at time of writing)
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

From version 20231215A (Ornery Otter) there is also a global constant object JayRule which provides several constants.

Property Description
JayRule.ANY_VALUE For use in object based ruleConditions, where properties of ntf, template, or document, are compared.
Equals the string "{{ANY_VALUE}}" used for the above purpose already.
JayRule.NOT_SET For use in object based ruleConditions, where properties of ntf, template, or document, are compared.
Equals the string "{{NOT_SET_VALUE}}" used for the above purpose already.
JayRule.ON_LOAD For use in object based ruleConditions, as a "fieldChanged" argument, for the rule to be run when the event is OnLoad.
Equals the string "ON_LOAD" used for the above purpose already.

Basic Syntax

{

#include "JayRule Ruleset Overlay JS",

/ global JayRule /

  ruleset : {

    name : 'name-of-ruleset',

    rule-name : {

      ruleCondition : rule-condition-function | boolean | rule-condition-object,

      or

      fieldChanged : string,

      or

      fieldChanged : [strings],

      ruleAction : rule-action-function

    },

    rule-name : {

      ...

  }

}

NB: This is a simplified syntax -- other optional features of the syntax are explained in later documents.

/* global JayRule */ The line /* global JayRule */ is temporarily necessary if the JayRule constants are used in the ruleCondition (object) and the Ruleset Editor Ruleset is of version or earlier v202307A. It tells the Syntax Checker to accept JayRule as a global. In later versions, this will be built in, and this line will be unnecessary.

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',

If you set this to '{{rulesetName}}', it will be set to the actual Name value of the ruleset document on saving.

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 one of the following formats:

// Original function format
ruleCondition : function(ntf) {...}

// Arrow function format
ruleCondition : ntf => {...}
// Please note - if this is used, there is no binding to "this", "arguments" or "super"

// Boolean format
ruleCondition : true | false

// ruleCondition object format
ruleCondition : {
    ...
}

Function Format

ruleCondition : function(ntf) {...}

Note: this function must not contain asynchronous processes.

Eg

ruleCondition : function(ntf) {
    return (ntf.context.fieldChanged === 'checkVenomous');
},

Arrow Function Format

ruleCondition : (ntf) => {...}

or

ruleCondition : ntf => {...}

Note: this function must not contain asynchronous processes.

Note: this function also cannot utilise "this", "arguments" or "super", as can be used under the full "function" format.

Eg

ruleCondition : function(ntf) {
    return (ntf.context.fieldChanged === 'checkVenomous');
},

Boolean Format

ruleCondition can also be assigned simply true (or false) for rules that must always (never) run.

Eg

ruleCondition : true,

ruleCondition Object Format

See next section

Object ruleCondition

From version (Licentious Lemur) of JayRule is introduced a ruleCondition as object, to make defining of conditions easier.

In this version (Ornery Otter) this has been expanded.

This does not replace the ruleCondition as function, which can be used when more complex conditions need to be calculated.

Late Note

The constants JayRule.ON_LOAD, JayRule.NOT_SET and JayRule.ANY_VALUE should not be used, as suggested in past versions of this document, particularly with server side rulesets. It somehow works in client side but not in server side rulesets -- the object JayRule remains undefined, so their use for these conditions is not advised.

Syntax

ruleCondition : {

    fieldChanged : field | [field1, field2, ....],

    newValue : value | [value1, value2,...],

oldValue : value | [value1, value2, ...],

event : ** eventName | [eventName1, eventName2, ...]

    ntf: {

        propertyName[__N] : fieldValue | fieldValueArray | { not : fieldValue } | { not : fieldValueArray }

        ...

    },

    document : {

        fieldName[__N] : fieldValue | fieldValueArray | { not : fieldValue } | { not : fieldValueArray }

        ...

    },

    template : {

        fieldName[__N] : fieldValue | fieldValueArray | { not : fieldValue } | { not : fieldValueArray }

        ...

    },

context : {

        propertyName[__N] : propertyValue | propertyValueArray | { not : propertyValue } | { not : propertyValueArray }

        ...

    },

condition : function(ntf) { ​ ...

}

}

fieldChanged

String | Array of strings

This sets which fields are to be responded to.

If an array of fields set, rule will respond to any single field change.

One can add the special field "ON_LOAD" to indicate the rule runs when there is no fieldChanged, such as onLoad.

newValue

String | Array of strings

Adds a condition for the rule to run on a certain value, or one of an array of certain values.

This can be defined as "{{ANY_VALUE}}" to indicate that it is any non null value.

Also, "{{NOT_SET_VALUE}}" conversely denotes where the value is not set, is null or undefined.

Relevant to OnFieldChange only.

oldValue

String | Array of strings

Adds a condition for the rule to run on a change away from a certain value, or one of an array of certain values.

This can be defined as "{{ANY_VALUE}}" to indicate that it is any non null value.

Also, "{{NOT_SET_VALUE}}" conversely denotes where the value is not set, is null or undefined.

Relevant to OnFieldChange only.

eventName

String | Array of strings

Adds a condition for the rule to run on specific events, in the case that the ruleset has been designed for use on multiple events.

Valid Event Names :
  • PreRender
  • OnLoad
  • OnFieldChange
  • PreSave
  • PostSaveClient
  • PreSaveServer
  • PostSave

ntf \ propertyName [__N]

String | Array of strings

Adds a condition for the rule to run if a first-level property of ntf matches a string, or one of an array of strings.

The addition of double-underscore and number (eg "__2"), allows for specifying a property to match a second (or third etc) condition. Eg a field must match one value, plus any of another set of values.

This can be defined as "{{ANY_VALUE}}", to indicate that it is any non null value.

Also, "{{NOT_SET_VALUE}}", conversely denotes where the value is not set, is null or undefined.

NB: Only one level of property is supported, ie: propertyX : 'alpha' is supported;

objectX.propertyY : 'alpha' is not supported.

Not values

One can also define values that should not match by using the "not" notation, eg

propertyX : { not : ['alpha','beta'] }

document \ field-name [ __N ]

String | Array of strings

Adds a condition for the rule to run if the given document field matches a string, or one of an array of strings.

The addition of double-underscore and number (eg "__2"), allows for specifying a field to match a second (or third etc) condition. Eg a field must match one value, plus any of another set of values.

This can be defined as "{{ANY_VALUE}}", to indicate that it is any non null value.

Also, "{{NOT_SET_VALUE}}" conversely denotes where the value is not set, is null or undefined.

NB: Only one level of field is supported, ie: fieldX : 'alpha' is supported;

objectX.fieldY : 'alpha' is not supported.

Not values

One can also define values that should not match by using the "not" notation, eg

propertyX : { not : ['alpha','beta'] }

context \ property-name [ __N ]

String | Array of strings

Adds a condition for the rule to run if the context property matches a string, or one of an array of strings.

The addition of double-underscore and number (eg "__2"), allows for specifying a property to match a second (or third etc) condition. Eg a property must match one value, plus any of another set of values.

This can be defined as "{{ANY_VALUE}}", to indicate that it is any non null value.

Also, "{{NOT_SET_VALUE}}", conversely denotes where the value is not set, is null or undefined.

NB: Only one level of field is supported, ie: propertyX : 'alpha' is supported;

objectX.propertyY : 'alpha' is not supported.

Not values

One can also define values that should not match by using the "not" notation, eg

propertyX : { not : ['alpha','beta'] }

template \ property-name [ __N ]

String | Array of strings

Adds a condition for the rule to run if the active template property matches a string, or one of an array of strings.

The active template is defined as that which is in effect during a particular document session, which may not correspond to the normal template of the document (that of systemHeader.templateId). A document may be opened with an overriding template, in which case, template here will correspond to that overriding template.

At present (2023-06-09), the template is not available to server side rulesets. This can be worked around by creating an initial rule which queries for the template (using systemHeader.createdWith for documentId) and setting ntf.context.template to the resulting document. Rules subsequent to this will then have ruleCondition.template available to exploit.

The addition of double-underscore and number (eg "__2"), allows for specifying a property to match a second (or third etc) condition. Eg a property must match one value, plus any of another set of values.

This can be defined as "{{ANY_VALUE}}", to indicate that it is any non null value.

Also, "{{NOT_SET_VALUE}}", conversely denotes where the value is not set, is null or undefined.

NB: Only one level of field is supported, ie: propertyX : 'alpha' is supported;

objectX.propertyY : 'alpha' is not supported.

Not values

One can also define values that should not match by using the "not" notation, eg

propertyX : { not : ['alpha','beta'] }

condition

Function

Adds a condition much like the original ruleCondition function, to be tested in addition to other elements of the ruleCondition object.

See Example 2 below.

Example 1

This example encapsulates a rule which should run when:

  • the field changed is either "description" or "commonName",
  • the new value is changed from "gusto" to "gusto6",
  • the document's appTags includes "dexwise",
  • the document's appTags includes "spider" or "insect"
  • the document is being shown using overlay template named "Spider Overlay 6"
  • the field "chklAttributes" (checklist) has at least one of "Venomous" and "Webmaking" checked
ruleTestObjectCondition : {
    ruleCondition : {
        fieldChanged : ['description','commonName'],
        newValue : 'gusto6',
        oldValue : 'gusto',
        document : {
            appTags : 'dexwise',
            appTags__2 : ['spider', 'insect'],
            chklAttributes : ['Venomous','Webmaking']
        },
        template : {
            name : 'Spider Overlay 6'
        }
    },

    oldRuleCondition : function(ntf) {
        return (
            (
                ntf.context.fieldChanged === 'description' 
                || ntf.context.fieldChanged === 'commonName'
            )
            && ntf.context.newValue === 'gusto6'
            && ntf.context.oldValue === 'gusto'
            && ntf.document.appTags.includes('dexwise')
            && (
                ntf.document.appTags.includes('spider')
                || ntf.document.appTags.includes('insect')
            )
            && (
                ntf.document.chklAttributes.includes('Venomous') 
                || ntf.document.chklAttributes.includes('Webmaking')
            )   
            && ntf.context.template.name === 'Spider Overlay 6'
        )
    },

    ruleAction : function(ntf) {
        alert('worked!');
    }
}

Example 2

This example encapsulates a rule which should run when:

  • the field changed is either "description" or "commonName",
  • the new value is changed from empty to "gusto6",
  • ntf.fangType is set to any defined non-null value,
  • ntf.isVenomous is true,
  • the document's appTags includes "dexwise",
  • the document's appTags includes "spider" or "insect"
  • the field "chklAttributes" has at least one of "Venomous" and "Webmaking" selected (true)
  • the user is deemed as qualified, according to a shared function "userIsQualified", defined elsewhere.
ruleTestObjectCondition2 : {
    ruleCondition : {
        fieldChanged : ['description','commonName'],
        newValue : 'gusto6',
        oldValue : '{{NOT_SET_VALUE}}', 
        ntf : {
            fangType : '{{ANY_VALUE}}', 
            isVenomous : true
        },
        document : {
            appTags : 'dexwise',
            appTags__2 : ['spider', 'insect'],
            chklAttributes : ['Venomous','Webmaking']
        },
        condition : function(ntf) {
            return ntf.scope.userIsQualified(ntf.userId);
        }
    },

    oldRuleCondition : function(ntf) {
        return (
            (
                ntf.context.fieldChanged === 'description' 
                || ntf.context.fieldChanged === 'commonName'
            )
            && ntf.context.newValue === 'gusto6'
            && !ntf.context.oldValue
            && ntf.fangType
            && ntf.isVenomous === true
            && ntf.document.appTags.includes('dexwise')
            && (
                ntf.document.appTags.includes('spider')
                || ntf.document.appTags.includes('insect')
            )
            && (
                ntf.document.chklAttributes.includes('Venomous') 
                || ntf.document.chklAttributes.includes('Webmaking')
            )   
            && ntf.scope.userIsQualified(ntf.userId)
        )
    },

    ruleAction : function(ntf) {
        alert('worked!');
    }
}

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');
}

We can also nominate multiple fields (Indolent Iguana & later)

fieldChanged : [
    'customerName', 
    'status'
],

rule-action-function

A function that performs the rule's action.

If asynchronous, the function must be one of the formats:

function(ntf, callback) {...}
async function(ntf, callback) {...}
(ntf, callback) => {...}
async (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 ft3 = ntf.scope;
    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) {...}
or
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);
    }

Using async/await

ruleAction can be defined to use async/await functionality. The ruleAction function must be declared as:

ruleAction : async function(ntf, callback) {

then one can use await within the ruleAction.

Example: See ruleGetUserByQuery in the Example code below.

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 2021-12-08 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 ft3 = ntf.scope;
                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 : {
                fieldChanged : 'description',
                newValue : '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 : {
                fieldChanged : 'commonName',
                newValue : 'ubug666'
            },

            ruleAction : async function(ntf,callback) {
                var ft3 = ntf.scope;
                var eqry = {"query": {"bool": {"filter": [
                    {"term" : {"documentId" : ntf.user.documentId}}    
                ]}}};

                var docs = await ft3.getDocsByEqry(ntf, eqry);
                var udoc = docs?.[0];

                if (udoc) {
                    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[Seconds|Minutes]: 'timeout in milliseconds / seconds / minutes',

    logging: {

      showUncalledRules : true| false,

      debugEmail : 'email-address' || 'login-email>redirect-email' || [array-of-recipients],

      debugEmailAll : 'true|false',

      debugEmailStopDate : 'date-time',

      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(es) 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.

One login/recipient can be specified as a single string, or multiple in an array.

To login with one email id and have debug emails sent to another email address, use the "target>recipient" notation (see example below).

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',
    ...
}
// debug emails will be sent to adrian.sharples@formbird.com, when a ruleset runs under that user login id

or

logging : {
    debugEmail : [
        'adrian.sharples@formbird.com>dave.galt@devco.com.au',
        'bill.bailey@fieldtec.com'
    ]
    ...
}
// debug emails will be sent to dave.galt@devco.com.au, when a ruleset runs under the user login id 'adrian.sharples@formbird.com'
// debug emails will be sent to bill.bailey@fieldtec.com, when a ruleset runs under that same user login id.

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
}

logging.debugEmailStopDate : date-time

Sets an expiry date-time for the sending of debug emails.

date-time can be defined as any distinct and unambiguous date string, eg "04 Jul 2021".

For better accuracy, use an ISO date string, eg "2021-09-28T14:00:00.000Z"

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]:',
    ...
}

For the OnFieldChange rulesets, one can also insert a token for the field changed, using {{fieldChanged}} , eg

logging : {
    logPrefix : 'RSLog:[Case-OnFieldChange]({{fieldChanged}}):',
    // example result : "RSLog:[Case-OnFieldChange](customerName):" 
    ...
}


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 version Guarded Giraffe).

timeout: 'timeout',

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 60000 milliseconds, or 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.

Timeout can also be set in either seconds or minutes, eg

timeoutSeconds : 30,    // 30 seconds
timeoutMinutes : 5,     // 5 minutes
A note on PreSaveServer

Practically, available timeout in PreSaveServer rules is constrained by the limit of the http request timeout, which is around two minutes. Setting the timeout in the ruleset to higher will cause the browser to present a 408 error, even though the ruleset may be carrying on in the background.

It is a good idea to confine PreSaveServer rulesets to the standard 120s (or even 60s); if something more involved is required, it may have to be performed under PostSave, or via a System Action.

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.

ft3 = ntf.scope must be declared for any code-block that requires to use ft3. For blocks of only one or two lines, it's probably simpler to just use ntf.scope.functionName.

const ft3 = ntf.scope;

A NOTE ON THE ERSTWHILE GLOBAL FT3

In past versions of JayRule, a global ft3 was defined, which removed the need to declare ft3 for every block.

Experience has shown that on occasion, multiple rulesets may run at the same time, switching back and forth depending on when asynchronous processes were running, each one assigning to the shared global object ft3. Thus one ruleset may reinitialise ft3 at the expense of added functionality from another ruleset, which then errors.

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) {
                var ft3 = ntf.scope;
                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) {
            var ft3 = ntf.scope;
            // 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) {
                var ft3 = ntf.scope;
                // 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. 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 : {
            fieldChanged : 'commonName',
            newValue : 'ubug666'
        },

        ruleAction : function(ntf,callback) {        // <<<<<<<<<<<<<<<<<<<<<<<<<<<<
            var ft3 = ntf.scope;
            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();    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
            });
        }
    } 

Dialogs

Versions of Formbird later than 3.1.x (currently 3.1.12) no longer support much of the functionality of SweetAlert dialogs that was used in earlier Formbird versions.

It is now necessary to initialise the SweetAlert dialogs for use in rulesets, if complex (eg user interactive) features are used.

To do this,

  • Ensure that your system has the Ruleset Include "SweetAlert Dialog".
  • Include this ruleset include into the top of your ruleset, eg
#include "SweetAlert Dialog",

Recent versions of the SweetAlert Dialog include now allow for using a new object "dialog", eg

ft3.dialog('Case Save', 'The case cannot be saved.', 'error');
// Same as ft3.ModalService.openModal( ... );

JQuery, Moment, SweetAlert

The "Angular 2" versions of Formbird (v3.3.x onward) no longer natively provide JQuery, the Moment library, nor the SweetAlert library (as ntf.scope.ModalService.openModal).

These can be instated for a ruleset via the following #include lines:

#include "JQuery",
#include "Moment.js",
#include "SweetAlert Dialog",

NB

Moment.js is coming to end of life soon. A future version of ECMAScript (Javascript) is likely to have its own date library features, so Moment.js should still be used for the short term future.