Rules are probably best described by using examples. This chapter contains numerous real world samples.
...
Table of Contents | ||
---|---|---|
|
Calculate a Total
You have a form with three controls and you have assigned them Names N1, N2 and T respectively. When the user enters a value in either N1 or N2 you want to set the value of T to the sum of N1 and N2. The rule would be written as
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
T.value = N1.value + N2.value; |
...
Code Block | ||
---|---|---|
| ||
if (!(MainTab.selected)) { Submit.visible = false; Cancel.visible = false; } else { Submit.visible = true; Cancel.visible = true; } |
...
Show/Hide Manager Approval
...
You have a form with two controls and you have assigned them Names B and Q respectively. B is a checkbox with a single option - Yes. . If checked the user is a smoker and you wish to ask an additional question in Q. The rule would be written as
...
language | javascript |
---|
flow and the first form has a Section for manager approval. The Section is hidden by default. Here is an example of a rule that makes the section visible in the second step of the flow which is a linked activity assigned to the manager role.
Code Block |
---|
if (form.load) { var an Q.enabled = = _data.getParameter ("flow.activity.name"); if (an === 'Manager' || an === 'VP'){ ManagerApproval.visible = true; } else { QManagerApproval.enabledvisible = false; } |
This rule will automatically fire whenever the user checks or unchecks B and will enable/disable the question in Q. Again, you could use any legal JavaScript expression to compute the enabled property of Q as long as it evaluates to a boolean true or false value. In this example, you would typically set the checkbox B to be initially unchecked and the control Q to be initially disabled.
Compute Subtotals for Repeating Items
This rule is an example of working with repeating items. Let's say, you have a form with a repeating section representing an Item that the user may purchase. Each section has a Price (with Name P), a Quantity (Name Q) and a Subtotal (Name S). There are multiple items on the page and the number of items on any given page is unknown. The price field is filled in automatically. When the user enters a value in the quantity field for any item, you wish to compute the subtotal.
} |
Enable/disable a question
You have a form with two controls and you have assigned them Names B and Q respectively. B is a checkbox with a single option - Yes. . If checked the user is a smoker and you wish to ask an additional question in Q. The rule would be written as:
Code Block | ||
---|---|---|
| ||
forif (var i = 0; i < S.value.length; i++B[0].value === 'Yes') { if (Q[i].valueenabled >= 0)true; { } else { S[i].value = Q[i].value * P[i].value; } else { S[i].value.enabled = 0false; } } |
This rule will automatically fire whenever the user enters a value in the quantity field for any item. It will compute the subtotal for each item, for which the quantity is greater than 0 and fill in the subtotal field for that item with the computed value. If a particular item does not have a quantity, the subtotal is not computed.
Compute an Invoice Total
Consider the same form as the example above. Let's say you have a control named Total with Name T. You want to set the value of Total to be the total invoice price, which is the sum of all the computed subtotals above. This checks or unchecks B and will enable/disable the question in Q. Again, you could use any legal JavaScript expression to compute the enabled property of Q as long as it evaluates to a boolean true or false value. In this example, you would typically set the checkbox B to be initially unchecked and the control Q to be initially disabled.
Compute Subtotals for Repeating Items
This rule is an example of working with repeating items. Let's say, you have a form with a repeating section representing an Item that the user may purchase. Each section has a Price (with Name P), a Quantity (Name Q) and a Subtotal (Name S). There are multiple items on the page and the number of items on any given page is unknown. The price field is filled in automatically. When the user enters a value in the quantity field for any item, you wish to compute the subtotal.
The rule would be written as:
Code Block | language | javascript
---|
var tot = 0; for (var i = 0; i < S.value.length; i++) { tot = tot + Sif (Q[i].value; > }0) { T.value = tot; |
This rule will fire whenever a subtotal is updated, for example, when it is updated via the rule above. It will add the values of all the subtotals to arrive at an invoice total. Note that you must use a temporary variable to compute the total. If you write the rule as:
Code Block | ||
---|---|---|
| ||
T S[i].value = 0; for (var i = 0; i < S.value.length; i++) { T.value = T.value +Q[i].value * P[i].value; } else { S[i].value = 0; } } |
it will not work correctly. This is due to internal limitations in the way rules are evaluated. You can read more about that here
...
This rule will automatically fire whenever the user enters a value in the quantity field for any item. It will compute the subtotal for each item, for which the quantity is greater than 0 and fill in the subtotal field for that item with the computed value. If a particular item does not have a quantity, the subtotal is not computed.
Compute an Invoice Total
Consider the same form as the example above. Let's say you have a control named Total with Name T. You want to set the value of Total to be the total invoice price, which is the sum of all the computed subtotals above. This rule would be written as:
Code Block | ||
---|---|---|
| ||
if (ExpenseRepeat.itemRemoved) {var x;} |
Textarea Max Length
In html there is no way to set a maxLength on a textarea control. This is why the textarea control does not have a maxlength property like the text control does. It is possible to do this via a business rule. This example form has a textarea control named 'Desc' where the user can enter up to a 500 character description. On this control we also set the ErrorMsg property to the string 'You must limit your description to 500 characters'. This message is automatically displayed when the description control is set to invalid by the following business rule.
Code Block |
---|
if (Desc.value.length > 500) {
Desc.valid = false;
} else {
Desc.valid = true;
} |
You can even customize the error message by adding this line to your rule. Now the error message will tell the user how many characters they are over the maximum allowed.
Code Block |
---|
Desc.status = 'Invalid. Max 20 chars allowed and you have ' + Desc.value.length; |
Textarea newline vs break
Users typically enter multi-line text into textarea controls. If you want to display that text in an html context, for example on a web page or in an html formatted email or in your form's Form Action display message you will need to replace newlines with html breaks. This caused by the fact that line breaks entered into a web form textarea are represented by a single newline character \n while line breaks in an html context are represented by the html break characters.
Our example has a textarea control named Description and a hidden control named DF. The user types into the visible control named Description and a business rules converts the newline characters \n into html breaks.
Code Block | ||
---|---|---|
| ||
var x = Description.value;
x = x.replace(/\\r/g,"");
x = x.replace(/\\n/g,"<br/>"); DF.value = x; |
Dropdown Options
This example automatically sets the option selected in one dropdown based on the option selected in another. This is often useful when you have a form with choices that were dynamically populated. For example, imagine product choices which are descriptive text. When the user selects a product, your form needs to perform an action based on a product ID rather than the descriptive product text. A nice way to do this is to have the rule that dynamically populates the product choices dropdown also populate a product ID dropdown which remains an invisible control in the form. The product choices dropdown control was named Products and the product ID dropdown control was named PID
The 1st rule "Load Products" populates both the visible and hidden dropdowns with options from a database.
Code Block |
---|
/*member description productId resultSet */
var x;
if (form.load) {
eval('x=' + http.get('http://localhost:8082/database/products'));
var opts1 = [];
var opts2 = [];
for (var i=0; i < x.resultSet.length; i++) {
if (x.resultSet[i]) {
opts1[i] = x.resultSet[i].description;
opts2[i] = x.resultSet[i].productId;
}
}
Products.options = opts1;
PID.options = opts2;
Products.value = opts1[0]; // default to 1st product option
PID.value = opts2[0];
} |
Finding a Selected Options Index
The 2nd rule Select Product ID keeps the hidden PID dropdown syncronized with the visible Products dropdown.
Code Block | ||
---|---|---|
| ||
if (Products.value.length > 0) { var i;var tot = 0; for (var i = 0; i < S.value.length; i++) { tot = tot + S[i].value; } T.value = tot; |
This rule will fire whenever a subtotal is updated, for example, when it is updated via the rule above. It will add the values of all the subtotals to arrive at an invoice total. Note that you must use a temporary variable to compute the total. If you write the rule as:
Code Block | ||
---|---|---|
| ||
T.value = 0;
for (var i = 0; i < S.value.length; i++) {
T.value = T.value + S[i].value;
} |
it will not work correctly. This is due to internal limitations in the way rules are evaluated. You can read more about that here
Tip |
---|
Note that this rule is working with controls inside a repeat control. To handle the case of a item being deleted from the repeat you need the following addition assuming that the repeat control is named ExpenseRepeat. Table controls are repeats with a different layout. Thus the same applies to the table controls. If your table is named Expense then the repeat is automatically named ExpenseRepeat. In general the table repeat is named <TableName>Repeat. |
Code Block | ||
---|---|---|
| ||
if (ExpenseRepeat.itemRemoved) {var x;} |
Formatting money values to display in a Message Control
Let's say you have calculated a sum in a Number control in your form and you want to display it in a Message control and format it as a money value with commas and decimal places. You will need a Number control named Num and a message control named Message in your form. This rule will display the number entered in the number control with commas. If the user enters 5600.44 in the number field then the result in the message control would look like this:"5,600.44".
Code Block | ||
---|---|---|
| ||
var x, x1, x2;
if (Num.value > 0) {
var nStr = Num.value.toFixed(2);
nStr += '';
x = nStr.split('.');
x1 = x[0];
x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
Message.value = x1 + x2;
} |
Textarea Max Length
In html there is no way to set a maxLength on a textarea control. This is why the textarea control does not have a maxlength property like the text control does. It is possible to do this via a business rule. This example form has a textarea control named 'Desc' where the user can enter up to a 500 character description. On this control we also set the ErrorMsg property to the string 'You must limit your description to 500 characters'. This message is automatically displayed when the description control is set to invalid by the following business rule.
Code Block |
---|
if (Desc.value.length > 500) {
Desc.valid = false;
} else {
Desc.valid = true;
} |
You can even customize the error message by adding this line to your rule. Now the error message will tell the user how many characters they are over the maximum allowed.
Code Block |
---|
Desc.status = 'Invalid. Max 20 chars allowed and you have ' + Desc.value.length; |
Textarea newline vs break
Users typically enter multi-line text into textarea controls. If you want to display that text in an html context, for example on a web page or in an html formatted email or in your form's Form Action display message you will need to replace newlines with html breaks. This caused by the fact that line breaks entered into a web form textarea are represented by a single newline character \n while line breaks in an html context are represented by the html break characters.
Our example has a textarea control named Description and a hidden control named DF. The user types into the visible control named Description and a business rules converts the newline characters \n into html breaks.
Code Block | ||
---|---|---|
| ||
var x = Description.value;
x = x.replace(/\\r/g,"");
x = x.replace(/\\n/g,"<br/>"); DF.value = x; |
Dropdown Options
This example automatically sets the option selected in one dropdown based on the option selected in another. This is often useful when you have a form with choices that were dynamically populated. For example, imagine product choices which are descriptive text. When the user selects a product, your form needs to perform an action based on a product ID rather than the descriptive product text. A nice way to do this is to have the rule that dynamically populates the product choices dropdown also populate a product ID dropdown which remains an invisible control in the form. The product choices dropdown control was named Products and the product ID dropdown control was named PID
The 1st rule "Load Products" populates both the visible and hidden dropdowns with options from a database.
Code Block |
---|
/*member productCode, productName, resultSet*/ var x; if (form.load) { eval('x=' + http.get('https://app.frevvo.com/database/BIRT/allProducts')); var opts1 = []; var opts2 = []; for (var i=0; xi in< Products.optionsx.resultSet.length; i++) { if (Products.value === Products.options[xx.resultSet[i]) { opts1[i] = Products.options.indexOf(Products.options[x]); } PID.value = PID.options[i] + ''; } |
In v4 rules using hidden dropdowns to keep descriptive option labels visible to the user while keeping cryptic database values hidden are often no longer necessary. Dropdown options have values distinct from the human visible option labels. The above can now be achieved with a single simpler rule:
Code Block |
---|
/*member description productId resultSet */
var x, opts1;
for (var i=0; i < x.resultSet.length; i++) {
if (x.resultSet[i]) {
opts1[i] = x.resultSet[i].productId+ '=' + x.resultSet[i].description;
}
}
Rdocnum.options = opts1; |
Here is another rule that dynamically populates both the product choices and product ID dropdowns. This rule calls a REST Service which returns an object rather than the resultset returned by the database connector as shown above. See the section on dynamic content for more details.
Code Block |
---|
/*member ids products */ var x; if (Sx.resultSet[i].productName; opts2[i] = x.resultSet[i].productCode; } } Products.options = opts1; PID.options = opts2; Products.value = opts1[0]; // default to 1st product option PID.value = opts2[0]; } |
Finding a Selected Options Index
The 2nd rule Select Product ID keeps the hidden PID dropdown syncronized with the visible Products dropdown.
Code Block | ||
---|---|---|
| ||
if (Products.value.length > 0) { eval('x=' + http.get('http://localhost:8182/products/?category=' + S.value)); P.options = x.products;var i; for (var x in Products.options) { ID.options = x.ids; } |
Synchronized Selects
The Product Search example above is often used in conjunction with a hidden select control. Imagine that your database table contains a list of products. Each product has product description also a unique product ID. The user needs to select a product from a dropdown on your form. You want to populate the dropdown with the product descriptions. The users do not need to see or know the product IDs but you need to use the ID as the key into the database for other selects. To do this add another hidden dropdown to the form and populate it with the IDs. This example has a visible dropdown name Products and an invisible dropdown named PID. See the rule above that populates these dropdowns dynamically from the database.
This rule below keeps the PID selected option in sync with the selected Product.
Code Block |
---|
var i, x;
for (x in Products.options) {
// Determine the index of the selected product in the Products dropdown options
if (Products.value === Products.options[x])
i = Products.options.indexOf(Products.options[x]);
}
// Changed the selected PID to match the selected Product
PID.value = PID.options[i] + ''; |
Clearing Dropdown Options
This sample resets a dropdown option to the automatically added blank option. For dropdowns added from palette controls and from schema, automatically adds a blank option so the dropdown initially shows no choice by default. To reset the dropdown, set the dropdown control's value to null not the empty string. The empty string will not work since the empty string is not a valid option. This form resets the dropdown named size whenever the value of the product option changes.
Code Block |
---|
if (product if ((Products.value + '=' + Products.value) === Products.options[x]){ i = Products.options.indexOf(Products.options[x]); } } PID.value = PID.options[i].split('=')[0]; } |
In v4 rules using hidden dropdowns to keep descriptive option labels visible to the user while keeping cryptic database values hidden are often no longer necessary. Dropdown options have values distinct from the human visible option labels. The above can now be achieved with a single simpler rule:
Code Block |
---|
/*member description productId resultSet */
var x, opts1;
for (var i=0; i < x.resultSet.length; i++) {
if (x.resultSet[i]) {
opts1[i] = x.resultSet[i].productId+ '=' + x.resultSet[i].description;
}
}
Rdocnum.options = opts1; |
Here is another rule that dynamically populates both the product choices and product ID dropdowns. This rule calls a REST Service which returns an object rather than the resultset returned by the database connector as shown above. See the section on dynamic content for more details.
Code Block |
---|
/*member ids products */ var x; if (S.value.length > 0) { eval('x=' + http.get('http://localhost:8182/products/?category=' + sizeS.value)); = null; } |
Default Option
When your options are set dynamically as shown below in a business rule, you cannot set a default in on the form designer. You need to set the default in the rule. If your options have <value>=<label> where value is different from label, make sure you set the <control>.value to <value> not <label> and not <value>=<label>
Code Block |
---|
if (form.load) { var cc = ['R=Red', 'B=Blue', 'G=Green']; Colors P.options = x.products; ID.options = cc; Colors.value = 'B'; } |
Checkbox Options - Assigning Color to Checkbox Choices
...
x.ids;
} |
Synchronized Selects
The Product Search example above is often used in conjunction with a hidden select control. Imagine that your database table contains a list of products. Each product has product description also a unique product ID. The user needs to select a product from a dropdown on your form. You want to populate the dropdown with the product descriptions. The users do not need to see or know the product IDs but you need to use the ID as the key into the database for other selects. To do this add another hidden dropdown to the form and populate it with the IDs. This example has a visible dropdown name Products and an invisible dropdown named PID. See the rule above that populates these dropdowns dynamically from the database.
This rule below keeps the PID selected option in sync with the selected Product.
Code Block |
---|
var choices = ''i, x; for (var i = 0; i < colorPalette.value.length; i++) { choices = choices + colorPalette[i].value; } colorChoice.value = choices; |
Notice that similar to repeat controls, due to an internal evaluation limitation, you must collect the choices in a variable inside the for loop. And then assign that control Name.value to that variable outside the for loop.
...
x in Products.options) {
// Determine the index of the selected product in the Products dropdown options
if (Products.value === Products.options[x])
i = Products.options.indexOf(Products.options[x]);
}
// Changed the selected PID to match the selected Product
PID.value = PID.options[i] + ''; |
Clearing Dropdown Options
This sample resets a dropdown option to the automatically added blank option. For dropdowns added from palette controls and from schema, automatically adds a blank option so the dropdown initially shows no choice by default. To reset the dropdown, set the dropdown control's value to null not the empty string. The empty string will not work since the empty string is not a valid option. This form resets the dropdown named size whenever the value of the product option changes.
Code Block |
---|
if (colorPaletteproduct.value.length > 0) { { colorChoicesize.value = 'Thank you for choosing colors'; } else { colorChoice.value = 'Please choose colors...'; } |
Checkbox Options - Making a Control Visible/Invisible Based on Checkbox Choices
This rule makes visible/invisible a control based on which checkbox options a user selects. This form contains a multi select checkbox named Structures. If the user selects the option "Detached Garage" or "House", we want to make visible a text field named Details.
Again since a checkbox is multi select, it is handled as an array. The array will contain all selected (checked) options.
It is important to note that when a checkbox is added to the form from the palette and its options are multiple words containing spaces, the option array has converted each space character to the '_' character. We must make the comparison as shown below. Checkbox controls from schema do not have space replaced with '_'.
Code Block |
---|
var found = false; null; } |
Default Option
When your options are set dynamically as shown below in a business rule, you cannot set a default in on the form designer. You need to set the default in the rule. If your options have <value>=<label> where value is different from label, make sure you set the <control>.value to <value> not <label> and not <value>=<label>
Code Block |
---|
if (form.load) {
var cc = ['R=Red', 'B=Blue', 'G=Green'];
Colors.options = cc;
Colors.value = 'B';
} |
Checkbox Options - Assigning Color to Checkbox Choices
Checkbox controls are different from all other palette controls in that they are multi-select. Therefore the way to write rules with checkbox controls are in many ways similar to rules with repeat controls. This rule has a checkbox controls with name colorPalette with the options: purple, green, blue, yellow, orange. The form also contains a text control with name colorChoice. This rule assigns colorChoice the choices selected from colorPalette.
Code Block |
---|
var choices = ''; for (var i = 0; i < StructurescolorPalette.value.length; i++) { choices = choices if+ (StructurescolorPalette[i].value; === 'Detached_Garage' || Structures[i].value === 'House') } colorChoice.value = choices; |
Notice that similar to repeat controls, due to an internal evaluation limitation, you must collect the choices in a variable inside the for loop. And then assign that control Name.value to that variable outside the for loop.
This rule is another example showing how checkbox controls are array types.
Code Block |
---|
if (colorPalette.value.length > 0) { colorChoice.value = found'Thank =you true;for break; } } if (found === true)choosing colors'; } else { DetailscolorChoice.visiblevalue = true;'Please } else { Details.visible = false; Details.value = nullchoose colors...'; } |
Note that when we hide Details we also clear its value. This is because the user may have selected one of the Structures checkboxes that made Details visible AND entered a value into Details. And then they may have changed their minds and uncheck the option that caused Details to become visible. If you don't want the value entered into Details to be in your form submission, clear the value when hiding it.
Many Checkbox Comments
This rule makes an associated comment input control visible and required when a checkbox is checked. The for loop determines which checkboxes are checked and sets an appropriately named variable to true. Depending on the value of checkbox the associated input control will be made visible and required via the if/else and will be hidden and not-required when it is un-checked again. This is a very common rule design pattern.
You can style this form so the comment input controls align with the checkbox options. See details and download a working sample form here.
Code Block |
---|
var heartProblem = false;
var foodAllergy = false;
var rashes = false;
var jointInjury = false;
var asthma = false;
var moodiness |
Checkbox Options - Making a Control Visible/Invisible Based on Checkbox Choices
This rule makes visible/invisible a control based on which checkbox options a user selects. This form contains a multi select checkbox named Structures. If the user selects the option "Detached Garage" or "House", we want to make visible a text field named Details.
Again since a checkbox is multi select, it is handled as an array. The array will contain all selected (checked) options.
It is important to note that when a checkbox is added to the form from the palette and its options are multiple words containing spaces, the option array has converted each space character to the '_' character. We must make the comparison as shown below. Checkbox controls from schema do not have space replaced with '_'.
Code Block |
---|
var found = false; for (var i = 0; i < MedicalIssuesStructures.value.length; i++) { if (MedicalIssuesStructures[i].value === 'heartDetached_problemGarage') {|| heartProblem = true; } else if (MedicalIssuesStructures[i].value === 'food_allergyHouse') { foodAllergyfound = true; } else if (MedicalIssues[i].value === 'rashes') { break; rashes = true; } } else if (MedicalIssues[i].valuefound === 'joint_injury'true) { jointInjury = true; } else if (MedicalIssues[i].value === 'asthma') { asthma Details.visible = true; } else if (MedicalIssues[i].value === 'moodiness') { moodiness = true; } } if (heartProblem === true) { heartProblemDetails.visible = true; heartProblemDetails.required = true; } else { heartProblemDetailsDetails.visible = false; heartProblemDetails.required = false; //heartProblemDetailsDetails.value = null; } if (foodAllergy === true) { foodAllergyDetails.visible = true; foodAllergyDetails.required = true; } else { foodAllergyDetails.visible = false; foodAllergyDetails.required = false; //foodAllergyDetails.value = null; } if (rashes === true) { rashesDetails.visible = true; rashesDetails.required = true; } else { rashesDetails.visible } |
Note that when we hide Details we also clear its value. This is because the user may have selected one of the Structures checkboxes that made Details visible AND entered a value into Details. And then they may have changed their minds and uncheck the option that caused Details to become visible. If you don't want the value entered into Details to be in your form submission, clear the value when hiding it.
Many Checkbox Comments
This rule makes an associated comment input control visible and required when a checkbox is checked. The for loop determines which checkboxes are checked and sets an appropriately named variable to true. Depending on the value of checkbox the associated input control will be made visible and required via the if/else and will be hidden and not-required when it is un-checked again. This is a very common rule design pattern.
You can style this form so the comment input controls align with the checkbox options. See details and download a working sample form here.
Code Block |
---|
var heartProblem = false; var foodAllergy = false; var rashes = false; var jointInjury = false; var asthma = false; var rashesDetails.requiredmoodiness = false; //rashesDetails.valuefor (var i = null0; }i if (jointInjury< MedicalIssues.value.length; i++) { if (MedicalIssues[i].value === true'heart_problem') { jointInjuryDetails.visibleheartProblem = true; jointInjuryDetails.required = true; } else { jointInjuryDetails.visible = false; jointInjuryDetails.required = false; //jointInjuryDetails.value = null; } if (asthmaif (MedicalIssues[i].value === 'food_allergy') { foodAllergy = true; } else if (MedicalIssues[i].value === true'rashes') { asthmaDetails.visible rashes = true; asthmaDetails.required = true; } else { asthmaDetails.visible = false; asthmaDetails.required = false; //asthmaDetails.value = null; } if (moodiness if (MedicalIssues[i].value === 'joint_injury') { jointInjury = true; } else if (MedicalIssues[i].value === 'asthma') { asthma = true; } else if (MedicalIssues[i].value === 'moodiness') { moodiness = true; } } if (heartProblem === true) { moodinessDetailsheartProblemDetails.visible = true; moodinessDetailsheartProblemDetails.required = true; } else { moodinessDetailsheartProblemDetails.visible = false; moodinessDetailsheartProblemDetails.required = false; //moodinessDetailsheartProblemDetails.value = null; } |
Checkbox Initialization
Since checkbox options are multi-select, in order to select multiple options via a rule you must use this syntax. In this example CB is the name of a checkbox controls with the following options: red, green, blue. This rule selects all of the options.
Code Block |
---|
CB.value = ['red', 'green', 'blue']; |
To clear all checked option in the control named CB:
Code Block |
---|
CB.value = []; |
Displaying Selected Checkbox Labels
In this example, the rule displays the labels of the checkboxes the user selects.
Code Block |
---|
var x; var selectedcolors = ''; for (var i = 0; i < RGB.value.length; i++) { var v = RGB[i].value; for (x in RGB.optionsif (foodAllergy === true) { foodAllergyDetails.visible = true; foodAllergyDetails.required = true; } else { foodAllergyDetails.visible = false; foodAllergyDetails.required = false; //foodAllergyDetails.value = null; } if (rashes === true) { rashesDetails.visible = true; var opt = RGB.options[x]; var val= opt.split('=')[0]; var lab= opt.split('=')[1]; if (v === val) { selectedcolors = selectedcolors + ' ' + lab; } } } |
T/F Boolean
T/F controls are simplified checkbox controls with only a single visible option.To test if the T/F control named agree is checked and make the control s visible if checked and make s invisible if not checked.
Code Block |
---|
if (agree[0].value === 'true') { s.visiblerashesDetails.required = true; } else { rashesDetails.visible = false; rashesDetails.required = false; //rashesDetails.value = null; } if (jointInjury === true) { jointInjuryDetails.visible = true; jointInjuryDetails.required = true; } else { jointInjuryDetails.visible = false; jointInjuryDetails.required = false; //jointInjuryDetails.value = null; } if (asthma === true) { asthmaDetails.visible = true; asthmaDetails.required = true; } else { asthmaDetails.visible = false; asthmaDetails.required = false; //asthmaDetails.value = null; } if (moodiness === true) { moodinessDetails.visible = true; moodinessDetails.required = true; } else { smoodinessDetails.visible = false; } |
To clear a checkmark from a T/F control you must set the value to null and ensure that the control is not required as follows:
Code Block |
---|
agree moodinessDetails.required = false; agree//moodinessDetails.value = null; } |
Repeating Checkboxes
Checkboxes inside repeat controls must be treated as an array (each checkbox control's values) of checkbox option values which is inside another array (the repeating checkbox control itself). This form example has a repeating section containing two controls -- Message which is a text control and AreYouAttending which is a checkbox control with a single option 'yes'. To access the selected options the syntax is:
AreYouAttending[i].value[0] === 'yes'
...
Checkbox Initialization
Since checkbox options are multi-select, in order to select multiple options via a rule you must use this syntax. The correct way to initialize the checkbox control value is to collect all required options in an array and then assign that array as the value of your checkbox control.
In this example CB is the name of a checkbox controls with the following options: red, green, blue. This rule selects all of the options.
Code Block |
---|
CB.value = ['red', 'green', 'blue']; |
To clear all checked options in the control named CB:
Code Block |
---|
CB.value = []; |
Displaying Selected Checkbox Labels
In this example, the rule displays the labels of the checkboxes the user selects.
Code Block |
---|
var x; var selectedcolors = ''; for (var i = 0; i < RGB.value.length; i++) { var if (AreYouAttendingv = RGB[i].value[0] === 'yes'; for (x in RGB.options) { var opt Message[i].value = Name.value += RGB.options[x]; var val= opt.split('=')[0]; ' is attendingvar event #' + i;lab= opt.split('=')[1]; } if } |
String Concatenation
Message controls can be used in business rules to create summary information on your form from values entered into earlier form fields. This rule uses javascript variables to concatenate form field values, text strings and html to format a nice summary page:
Code Block |
---|
var totalAssets = TotalAutoValue.value + TotalBankValue.value + TotalRealEstateValue.value; BasicSummaryMsg.value = "<b>Name:</b> " + FirstName.value + " " + LastName.value + "<br/>" + "<b>Phone:</b> " + Phone.value + "<br/>" + "<b>Email:</b> " + EmailAddress.value; if (MilitaryOrCivilian.value === 'Military') { //Military DetailedSummaryMsg.value = "<b>Military Info:</b><br/>" + "Military ID: " + MilitaryID.value + "<br/>" + "Rank: " + Rank.value + "<br/>" + "CurrentTitle: " + CurrentTitle.value + "<br/>" + "Years of Service: " + YearsOfService.value + "<br/>"; } else if (MilitaryOrCivilian.value === 'Civilian') { //Civilian DetailedSummaryMsg.value = "<b>Civilian Info:</b><br/>" + "SSN: " + SSN.value + "<br/>" + "Current Employer: " + CurrentEmployer.value + "<br/>" + "Current Title: " + CurrentTitle2.value + "<br/>" + "Start Date: " + StartDate.value + "<br/>"; } FinancialSummaryMsg.value = "<b>Total Assets:</b> $" + totalAssets + "<br/>" + "Total Bank Assets: $" + TotalBankValue(v === val) { selectedcolors = selectedcolors + ' ' + lab; } } } |
T/F Boolean
T/F controls are simplified checkbox controls with only a single visible option.To test if the T/F control named agree is checked and make the control s visible if checked and make s invisible if not checked.
Code Block |
---|
if (agree[0].value === 'true') {
s.visible = true;
} else {
s.visible = false;
} |
To clear a checkmark from a T/F control you must set the value to null and ensure that the control is not required as follows:
Code Block |
---|
agree.required = false;
agree.value = null; |
Repeating Checkboxes
Checkboxes inside repeat controls must be treated as an array (each checkbox control's values) of checkbox option values which is inside another array (the repeating checkbox control itself). This form example has a repeating section containing two controls -- Message which is a text control and AreYouAttending which is a checkbox control with a single option 'yes'. To access the selected options the syntax is:
AreYouAttending[i].value[0] === 'yes'
Code Block |
---|
for (var i = 0; i < AreYouAttending.value.length; i++)
{
if (AreYouAttending[i].value[0] === 'yes') {
Message[i].value = Name.value +
' is attending event #' + i;
}
} |
String Concatenation
Message controls can be used in business rules to create summary information on your form from values entered into earlier form fields. This rule uses javascript variables to concatenate form field values, text strings and html to format a nice summary page:
Code Block |
---|
var totalAssets = TotalAutoValue.value + TotalBankValue.value + TotalRealEstateValue.value; BasicSummaryMsg.value = "<b>Name:</b> " + FirstName.value + " " + LastName.value + "<br/>" + "<b>Phone:</b> " + Phone.value + "<br/>" + "<b>Email:</b> " + EmailAddress.value; if (MilitaryOrCivilian.value === 'Military') { //Military DetailedSummaryMsg.value = "<b>Military Info:</b><br/>" + "Military ID: " + MilitaryID.value + "<br/>" + "Rank: " + Rank.value + "<br/>"Total Real+ Estate Value"CurrentTitle: $" + TotalRealEstateValueCurrentTitle.value + "<br/>" + "TotalYears Autoof ValueService: $" + TotalAutoValueYearsOfService.value + "<br/>"; |
Note when using field values from repeat controls you must use a javascript var and assign the concatenation to the var and then the var to the message control value. For example imagine you have a message control named Summary and a repeat control named Account:
Code Block |
---|
var acctSummary = ''; for (var i = 0; i < Account.value.length; i++ } else if (MilitaryOrCivilian.value === 'Civilian') { //Civilian if (Q[i]DetailedSummaryMsg.value >= 0) { "<b>Civilian Info:</b><br/>" + "SSN: " acctSummary = acctSummary + 'Account #' + i + ': ' + Account[i]+ SSN.value + "<br/>" + "Current Employer: " + CurrentEmployer.value + '"<br/>';" + "Current Title: " } } Summary+ CurrentTitle2.value = acctSummary; |
Dynamic Labels, Help, Hints
You can set the value of control labels, help and hint dynamically in a rule. For example imagine you do not know the label, help or hint at design time but would rather set it dynamically when a user opens your form.
Code Block |
---|
if (form.load)
{
Text99.label = 'New Label';
Text99.hint = 'New Hint';
Text99.help = 'New Help';
} |
In the above example the label, help and hint is still hard-coded. It's just being set from the rule rather than in the form designer controls' properties. To make this more useful you can initialize these properties from _data parameters:
Code Block |
---|
if (form.load)
{
Text99.label = _data.getParameter('label');
Text99.hint = _data.getParameter('hint');
Text99.help = _data.getParameter('help');
} |
Since _data.getParameter enables access to values passed to the form that are not bound to actual controls this is often a very useful pattern.
Visible/Invisible
This rule makes the message control nickNameThankYou visible when the user enters a value into the nickName input text control. And then hides the message control if the user deletes the value in nickName.
Code Block |
---|
if (nickName.value.length > 0 )
{
nickNameThankYou.visible = true;
}
else
{
nickNameThankYou.visible = false;
} |
Visible/Invisible Section
Often section controls contain many inner controls. For example imagine a form that contains a person's medical history. One of the questions on the form asks if the patient uses a hearing aid. If they answer yes, then you want to collect more details on their hearing aid usage such as left ear, right ear, bilateral; hearing aid brand; etc. If they answer no then you want to hide all the questions specific to hearing aids. Also when the answer is yes you want to require them to answer all the hearing aid detailed questions.
Info |
---|
Avoid using message controls, images & video controls inside a section that contains other controls that you may want to make invisible. Since a these three control types always contains a value, it can cause a section, or other controls in a section, to become required, and this can disable the form's Submit button. If you must include these controls, place them outside the section. Another alternative is to write rules for the individual controls within a section to set them to visible/invisible or required/not required |
Imagine this example form has a section named HearingAid. By default HearingAid visible is set to false in the form designer.
When they answer yes, you must set HearingAid.visible=true AND also each required field inside the section to field.required = true. If they then change the answer to no then another rule makes the HearingAid.visible=false AND all the field.required=false. If the HearingAid section contains many child controls this rule becomes very long and tedious to write
We can simplify this by using the required property for sections. In the designer default all controls that must be answered inside HearingAid to required. Default the HearingAid section to not required and not visisble. Your rule can be much simpler. By setting HearingAid.required=false all the inner controls recursively also become required=false.
Code Block |
---|
if (useAid.value === 'no') { // Hide HearingAid.visible = false; HearingAid.required = false; } else { // Show+ "<br/>" + "Start Date: " + StartDate.value + "<br/>"; } FinancialSummaryMsg.value = "<b>Total Assets:</b> $" + totalAssets + "<br/>" + "Total Bank Assets: $" + TotalBankValue.value + "<br/>" + "Total Real Estate Value: $" + TotalRealEstateValue.value + "<br/>" + "Total Auto Value: $" + TotalAutoValue.value + "<br/>"; |
Note when using field values from repeat controls you must use a javascript var and assign the concatenation to the var and then the var to the message control value. For example imagine you have a message control named Summary and a repeat control named Account:
Code Block |
---|
var acctSummary = '';
for (var i = 0; i < Account.value.length; i++) {
if (Q[i].value > 0) {
acctSummary = acctSummary + 'Account #' + i + ': ' + Account[i].value + '<br/>';
}
}
Summary.value = acctSummary; |
Dynamic Labels, Help, Hints
You can set the value of control labels, help and hint dynamically in a rule. For example imagine you do not know the label, help or hint at design time but would rather set it dynamically when a user opens your form.
Code Block |
---|
if (form.load)
{
Text99.label = 'New Label';
Text99.hint = 'New Hint';
Text99.help = 'New Help';
} |
In the above example the label, help and hint is still hard-coded. It's just being set from the rule rather than in the form designer controls' properties. To make this more useful you can initialize these properties from _data parameters:
Code Block |
---|
if (form.load)
{
Text99.label = _data.getParameter('label');
Text99.hint = _data.getParameter('hint');
Text99.help = _data.getParameter('help');
} |
Since _data.getParameter enables access to values passed to the form that are not bound to actual controls this is often a very useful pattern.
Visible/Invisible
This rule makes the message control nickNameThankYou visible when the user enters a value into the nickName input text control. And then hides the message control if the user deletes the value in nickName.
Code Block |
---|
if (nickName.value.length > 0 )
{
nickNameThankYou.visible = true;
}
else
{
nickNameThankYou.visible = false;
} |
Visible/Invisible Section
Often section controls contain many inner controls. For example imagine a form that contains a person's medical history. One of the questions on the form asks if the patient uses a hearing aid. If they answer yes, then you want to collect more details on their hearing aid usage such as left ear, right ear, bilateral; hearing aid brand; etc. If they answer no then you want to hide all the questions specific to hearing aids. Also when the answer is yes you want to require them to answer all the hearing aid detailed questions.
Info |
---|
Avoid using message controls, images & video controls inside a section that contains other controls that you may want to make invisible. Since these three control types always contains a value, it can cause a section, or other controls in a section, to become required, and this can disable the form's Submit button. If you must include these controls, place them outside the section. Another alternative is to write rules for the individual controls within a section to set them to visible/invisible or required/not required |
Imagine this example form has a section named HearingAid. By default HearingAid visible is set to false in the form designer.
When they answer yes, you must set HearingAid.visible=true AND also each required field inside the section to field.required = true. If they then change the answer to no then another rule makes the HearingAid.visible=false AND all the field.required=false. If the HearingAid section contains many child controls this rule becomes very long and tedious to write
We can simplify this by using the required property for sections. In the designer default all controls that must be answered inside HearingAid to required. Default the HearingAid section to not required and not visisble. Your rule can be much simpler. By setting HearingAid.required=false all the inner controls recursively also become required=false.
Code Block |
---|
if (useAid.value === 'no') { // Hide HearingAid.visible = truefalse; HearingAid.required = truefalse; } else { // Show HearingAid.visible = true; HearingAid.required = true; } |
Info |
---|
It is not currently possible to effect required for controls from XSD schema. This functionality will be added in a future release of . See the documentation for Data Sources and Schemas for details on implementing a Show/Hide rule with XSD controls. |
...
Now replace the hard coded list of coffee shops with a rule that invokes an http.get. This must return an X-JSON header which contains a JSON object. The object is evaluated and assigned to the variable x. In this case the JSON object contains an options field of type array. See the section on dynamic content for more details.
Code Block | |
---|---|
javascript | var x; if (search.clicked) { eval('x=' + http.get('http://<webhost>(your webhost)/getCoffeeShopList')); coffeeShopList.options = x.options; } |
Tip |
---|
Triggers do not work in repeating items. |
...
This rule executes when the user enters a value into the Username text field. It uses the built-in isUniqueUserId() method that returns false if the user already exists. If the user already exists this rule then sets the value of a message control, makes that message control visible on the form and sets the Username valid property to false so that Username field displays as invalid to guide the user to make a correction. See the section on dynamic content for more details.
Code Block | language | javascript
---|
if (U.value.length > 0) { if (frevvo.isUniqueUserId(user.value, tenant.value) === false) { M.value = 'User: ' + U.value + ' already exists'; M.visible = true; U.valid = false; } else { M.visible = false; } } |
Digital Signature
This form uses a rule to pass a username and password to a LDAP Active Directory authentication service. If authentication fails the form makes an error message control visible. If authentication succeeds the form disables the username form field and replaces the password field with a date field set to the current date. The form contains a trigger control named sign, username and password fields named u and p respectively, a date field named d and a message field named m.
Code Block | language | javascript
---|
/*member auth */ var x; if (sign.clicked) { // passwords may contain characters that need url encoding var p_encode = encodeURIComponent(p.value); eval('x=' + http.get('http://<webhost>(your webhost)/authServices/signForm?username=' + u.value + '&password=' + p_encode)); if if (x.auth) { var dt = new Date(); var day = dt.getDate(); var month = dt.getMonth() + 1; var year = dt.getFullYear(); d.value = month + '-' + day + '-' + year; d.visible = true; u.enabled = false; p.visible = false; sign.visible = false; m.visible = false; } else { m.visible = true; } } |
The authService is an example HTTP servlet that returns a JSON response.
...
Change the Date_Time.value = "2011-1-23T20:20:20"; to Date_Time.value = "2011-01-23T20:20:20"; in the rule or the xml document for successful initialization.
Warning |
---|
Rules initializing dates & time will not work in a form.load rule unless you specify a timezone on the form's Url via the _formTz Url parameter. This is because the form server needs to know the timezone in which to return the date and time. If you do not specify a _formTz the methods will return null and the control values will remain blank. The timezone strings can be found here. For example, to specify Eastern time: &_formTz=America/NewYork. |
...
Age
This form initializes the hospital discharge date using a rule, and when the user enters the admission date a 2nd rule calculates the number of days the patient stayed in the hospital.example form automatically determines today's date and then calculates the person's age in the control named 'Age' when they enter their birth date into the control named 'BirthDate'.
Code Block | ||
---|---|---|
| ||
if (ABirthDate.value !== '' && D.value !== '') .length > 0) { var da = A.value.split('-'); var Ams today = new Date(da[0],da[1],da[2]); var dadob = DBirthDate.value.split('-'); var Dmsdob2 = new Date(dadob[0],dadob[1]-1,dadob[2]); var oneDayage = 24*60*60*1000 today.getFullYear() - dob2.getFullYear(); if (today.getMonth(Ams) > Dms) { Days.value = 'Discharge date must be after Admission Date'; } else { var diffDays = Math.round(Math.abs((Ams.getTime() - Dms.getTime())/(oneDay))); Days.value = diffDays + ' days'; } } |
Today's Date and Time
Use 's built-in date and time methods to set your date, time, and date/time controls to the current date and time in the user's local timezone.
Code Block |
---|
if (form.load) {
Tm.value = frevvo.currentTime(form);
Dt.value = frevvo.currentDate(form);
DtTm.value = frevvo.currentDateTime(form);
} |
Note |
---|
The currentTime(), currentDate() and currentDateTime() will not work in a form.load rule unless you specify a timezone on the form's Url via the _formTz Url parameter. This is because the form server needs to know the timezone in which to return the date and time. If you do not specify a formTz the methods will return null and the control values will remain blank. For example &formTz=America/New_York will set the control values to the current date and time in the eastern timezone. |
Date/Time Stamp
This rule sets a control named Signature to the value of a control named Name plus a date/time stamp. Note that it is better to use the built-in date and time methods if you want to set the date to the user's local timezone. The following method will set the date to the form server's timezone.
Code Block | ||
---|---|---|
| ||
var today=new Date();
var m = today.getMonth() + 1;
var d = today.getDate();
var y = today.getFullYear();
var h = today.getHours(); var min = today.getMinutes(); var todayStr = m + '-' + d + '-' + y + ' ' + h + ':' + min; Signature.value = 'Signed by ' + Name.value + ' on ' + todayStr; |
Invalid if Before Today
This rule makes the date control invalid if the date entered isn't before today's date.
Code Block |
---|
var today = new Date();
var bd = DOB.value.split('-');
var bd_date = new Date(bd[0],bd[1]-1,bd[2]);
if (bd_date.getTime() > today.getTime()) { MyMsg.value = 'Birth Date must be earlier than today!!!!'; DOB.valid = false; } else { MyMsg.value = 'This is a good Birth Date: ' + DOB.value; DOB.valid = true; } |
Date no more then 14 days from Today
This rule checks that the date entered into a control named AppointmentDate is no more than 14 days greater than today's date.
Code Block |
---|
var date1 = DateUtil.today();
var date2 = Appointment.value;
date1 = date1.split("-");
date2 = date2.split("-");
var sDate = new Date(date1[0]+"/"+date1[1]+"/"+date1[2]);
var eDate = new Date(date2[0]+"/"+date2[1]+"/"+date2[2]);
var DaysApart = Math.round((eDate-sDate)/86400000);
if (DaysApart > 14) { Appointment.status = "Date should be within 14 days from today's date."; } else if (DaysApart < 0) { Appointment.status = "Date should not be earlier to today's date."; } |
Date no more then 30 days ago
This rule checks that the date entered into a control named EventStartDate is not more then 30 days ago.
Code Block |
---|
if (EventStartDate.value !== "") {
var date1 = DateUtil.today();
var date2 = EventStartDate.value;
date1 = date1.split("-");
date2 = date2.split("-");
var sDate = new Date(date1[0]+"/"+date1[1]+"/"+date1[2]);
var eDate = new Date(date2[0]+"/"+date2[1]+"/"+date2[2]);
var days = Math.round((eDate-sDate)/86400000);
if (!eval(parseInt(days,10) > parseInt(-30,10))) { EventStartDate.valid = false; EventStartDate.status = "The date entered can only go back a maximum of 30 days from the current date. Please try again."; } else { EventStartDate.valid = true; } } |
Add # of Years to a Date
Here is a rule that will add 3 years to a given date. For example, to calculate the expiration date of a three year contract by adding three years to the starting date, your form could have two date controls, one used to enter the starting date and the other to show the contract expiration date. This rule will take the date from the StartingDate field, add 3 years to it and populate the result in a field named ExpirationDate.
Code Block |
---|
if(StartingDate.value.length > 0) { var md = StartingDate.value.split('-'); var today=new Date(); var hour = today.getHours(); var oldDate = new Date(md[0],md[1]-1,md[2],hour); var year = parseInt(oldDate.getFullYear(), 10); var month = parseInt(oldDate.getMonth(), 10); var date = parseInt(oldDate.getDate(), 10); var newDate = new Date(year + 3, month, date, hour); var m = newDate.getMonth()+1; var d = newDate.getDate(); var y = newDate.getFullYear(); var newDateStr = m + '-' + d + '-' + y; ExpirationDate.value = newDateStr; } |
Change the "3" in this line to the number of years that you want to add - var newDate = new Date(year + 3, month, date, hour);
Central Timezone adjusted for Daylight Savings
This rule adjust today's date in UTC timezone to Central timezone and adjust for daylight savings time. This additional conversion is most commonly needed for Online users as the javascript Date() and ' DateUtil.today() both return today's date in UTC timezone.
Code Block |
---|
// Converts UTC to either CST or CDT
if (form.load)
{
var today = new Date();
var DST = 1; // If today falls outside DST period, 1 extra hr offset
var Central = 5; // Minimum 5 hr offset from UTC
// Is it Daylight Savings Time?
//
var yr = today.getFullYear();
// 2nd Sunday in March can't occur after the 14th
var dst_start = new Date("March 14, "+yr+" 02:00:00");
// 1st Sunday in November can't occur after the 7th
var dst_end = new Date("November 07, "+yr+" 02:00:00");
var day = dst_start.getDay(); // day of week of 14th
// Calculate 2nd Sunday in March of this year
dst_start.setDate(14-day); day = dst_end.getDay(); // day of the week of 7th
// Calculate first Sunday in November of this year dst_end.setDate(7-day);
// Does today fall inside of DST period?
if (today >= dst_start && today < dst_end) { DST = 0; } // Adjust Date for Central Timezone today.setHours(today.getHours() - Central - DST); var m = today.getMonth() + 1; var d = today.getDate(); var da = today.getDay(); var y = today.getFullYear(); var h = today.getHours(); var min = today.getMinutes(); if (min < 10) { min = '0' + min;} var timezone = ['CDT', 'CST']; var dom = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; var todayStr = dom[da] + ' ' + m + '-' + d + '-' + y + ' ' + h + ':' + min + ' ' + timezone[DST]; DateTime.value = todayStr; } |
Hours
>= 4 and <= 6 Apart
This rule makes sure the end time is at least 4 hours great then the start time but no more then 6 hours later then the start time. Also start time must be on or after 1:00. The times must be entered in military units. TS is the name of the Time Start control and TE is the name of the Time End control.
- Use Text controls for the start and end times
- Use this pattern in the control to ensure valid military times 1:00 or greater: ([1-9]|1[0-9]|2[0-4]):([0-5][0-9])
Code Block |
---|
if (TS.value.length > 0 && TE.value.length > 0) { var sTime = TS.value.split(':'); var sHour = sTime[0]; var sMin = sTime[1]; var sMins = sHour * 60 + parseInt(sMin,10); var eTime = TE.value.split(':'); var eHour = eTime [0]; var eMin = eTime [1]; var eMins = eHour * 60 + parseInt(eMin,10); if ((eMins - sMins) < 4*60 || (eMins - sMins) > 6*60) { TE.valid = false; } else { TE.valid = true; } } |
Times
The date control can be set to either just a date, just a time, or a combined date/time. Here are several examples of initializing a time control name Tm;
Code Block |
---|
/ Both set the control name Tm to the same time Tm.value = '8:30 pm'; // uses am/pm notation Tm.value = ' 20:00'; // uses military time // Initialize a date/time uses a "T" to separate the values DtTm.value = "5/15/2012T4:20"; // Copying values Tm2.value = Tm.value; Dt2.value = Dt.value; DtTm2.value = DtTm.value; |
Tenants, Roles, Users
have several built-in methods that enable you to access information about your form server such as the list of tenants; users in a tenant; and roles in a tenant. See built-in methods for the complete list. Some of these methods return a boolean true/false value. Others return a JSON string. Here is a sample of how to use these methods.
Code Block |
---|
*member roles tenants users */ var x; if(form.load) { eval('x=' + frevvo.listTenants()); Tenants.options = x.tenants; // Init a dropdown list of all tenants on this form server } if(tenant.value.length > 0) { // Check if a tenant already exists if (frevvo.isUniqueRoleId(role.value, tenant.value) === false) { ErrMsg.value = 'The role ' + role.value + ' already exists in tenant ' + tenant.value; } // Init dropdown with all tenant users eval('x=' + frevvo.listUsers(tenant.value)); Users.options = x.users; // Init dropdown with all tenant roles eval('x=' + frevvo.listRoles(tenant.value)); Roles.options = x.roles; // Init dropdown will all tenant roles except admin roles eval('x=' + frevvo.listRoles(tenant.value, false)); RolesNA.options = x.roles; } |
Code Block |
---|
// Verify that a role already exists in the tenant if(role.value.length > 0) { t.value = frevvo.isUniqueRoleId(role.value, tenant.value); if (frevvo.isUniqueRoleId(role.value, tenant.value) === false) { ErrMsg.value = 'The role ' + role.value + ' already exists in tenant ' + tenant.value; } } |
Here is an example of populating a dropdown list of all tenants on the form server with value is set to tenant Id and label is set to the tenant description returned in the JSON string. The frevvo.listTenants() method returns a string like this: {"tenants":["Default tenant","My Test Tenant"],"ids":["d","nancy.com"]}. The eval function parses that string into an object that we can iterate as follows:
Code Block |
---|
/*member tenants ids */ if(form.load) { TA.value = frevvo.listTenants(); var x; eval('x=' + frevvo.listTenants()); var opts = []; for (var i = 0; i < x.tenants.length; i++) { opts[i] = x.ids[i] + '=' + x.tenants[i]; } // Initialize dropdown of all tenants on the form server TenantList.options = opts; } |
Repeat Item Added
This rule executes when a new item is added to a repeat. Imagine your form contains a repeating section named Employee with name Erepeat. NOTE: that the name Erepeat is set on the Repeat control and not on the Section control. The Employee section control contains many controls such as Name, Phone, etc.. and a dropdown control labeled Manager and named M. It also contains a radio control labeled Employee Shift named ES whose options have been set to 'Day' and 'Evening'.
will execute this rule each time a user clicks "+" on the repeat to add a new item. Here we want to default the Employee Shift ES to the value 'Day', and populate the Manager dropdown dynamically with values from the Database Connector.
Usually your form will have a form.load rule to initialize dropdown options for the 1st repeat item visible on your form by default.
Code Block | ||
---|---|---|
| ||
/*member firstname lastname options resultSet value */ var x; if (form.load) { var baseURL = 'http://www.myhost.com:8080/database/'; // Manager Dropdown eval('x=' + http.get(baseURL + 'Manager')); var opts = ['']; // default 1st option to blank for (var i=0; i < x.resultSet.length; i++) { if (x.resultSet[i]) { opts[i+1] = x.resultSet[i].lastname+ ", " + x.resultSet[i].firstname; } } // Repeat item index 0 is on the form by default M[0].options = opts; ES[0].value = 'Day'; // default the employee shift to day } |
Now when a new item gets added when a user clicks the "+" icon we can save the overhead of going back to the database to retrieve dynamic options.
Code Block |
---|
if (Erepeat.itemAdded) { var index = Erepeat.itemIndex; // which item is this in the list // No need to go back to the database for options. // We already retrieved them in form.load rule for repeat item index 0 M[index].options = M[0].options; ES[index].value = 'Day'; // default the employee shift to day } |
Info |
---|
Tables are repeats. So the same rule can be written for a table control. The name of a table's repeat is always <TableName>Repeat. For example if you name your table Children. Then the repeat is named ChildrenRepeat. |
Repeat Item Added - Collapse Other Items
This rule executes when a new item is added to a repeat. This form contains a repeating section with a templatized label. It is nice to collapse the other items when adding a new item to keep the repeat list small and grid-like. Medrepeat is the name of the repeat control. Medication is the name of the section control inside the repeat.
Code Block |
---|
if (Medrepeat.itemAdded) { var index = Medrepeat.itemIndex; for (var i = 0; i < Medication.value.length; i++) { if (i !== index) { Medication[i].expanded = false; } else { Medication[i].expanded = true; } } } |
Repeat Dynamic Min/Max
It is not currently possible to change a repeat control's min and max properties in a business rule. This feature may be added in a future release. One solution is to use hidden section controls rather than a repeat control. For example imagine an airline reservation form where the number of traveler detail rows displayed and required is based on the number of airline tickets purchased. This example form allows a maximum of 3 ticket purchases. So, first add 3 section controls for traveler details. It is important to make sure that you name the travel detail section controls uniquely -- Traveler1 is the name of the first section, Traveler2 is the name of the second section, etc.. Next uncheck the visible and required property on all 3 section controls. Then add a business rule that makes the number of sections visible and required based on the number of purchased airline tickets.
Code Block |
---|
Traveler1.visible = false; Traveler2.visible = false; Traveler3.visible = false; Traveler1.required = false; Traveler2.required = false; Traveler3.required = false; for (var i=0; i < NumTickets.value; i++) { if (i >= 0) { Traveler1.visible = true; Traveler1.required = true; } if (i >= 1) { Traveler2.visible = true; Traveler2.required = true; } if (i >= 2) { Traveler3.visible = true; Traveler3.required = true; } } |
Tables
Tables are identical to repeat controls when referenced in business rules. Tables are a grid layout of repeating items. All the rule examples in this chapter that discuss repeats apply also to tables. The one important note is that you cannot explicitly name the repeat control inside your table. The repeat control inside a table is automatically named as <TableName>Repeat. For example a table named Expense automatically has a repeat named ExpenseRepeat. The rule ExpenseRepeat.itemAdded and ExpenseRepeat.itemIndex references an item added to your table and that item's index respectively.
One special consideration for tables is that rules which dynamically set the column header label, hint and help mesages must use the array syntax. This is somewhat counter-intuitive since each column appears to have only a single label, help and hint. Here is the rule to dynamically set these three properties:
Code Block |
---|
FirstName[0].label = 'blah label'; FirstName[0].hint = 'blah hint'; FirstName[0].help = 'blah help'; |
Note | ||
---|---|---|
A section in a repeat will behave differently than a table row when using a rule to set the valid property. For Example, drag and drop a table control into your form. Name it T. Then drag and drop a section into the same form. Name it S. Add a control to the section. Name the control C. Drop the section into a repeat named R. Add this rule to the form.
The section header shows "Invalid value" as a message while the table row shows no such indication signifying an invalid row. |
form.load
Rules can be used to initialize field values. This is a very useful feature and is often used to dynamically populate dropdown options from a database. Rules using form.load are triggered when a form first loads and when a workflow is loaded from a task list.
Rules using itemAdded only execute for repeat items added when the user clicks +, and for those added from an initial instance document (See Document URIs). It does '''not''' execute for those items that you have added to your form in the Form Designer. You can either add defaults directly via the form designer or add a 2nd rule to your form as follows.
These two rules together initialize the dropdown fields inside a repeat that are already in the form via the Form Designer, as well as those added each time a user clicks "+" on the repeat to add a new item & via initial documents. These controls are initialized based on a value set in another field.
Code Block |
---|
//1st Rule - Default Items if (form.load) { // The form contains two repeat items by default. if (department.value === 'Marketing') { Managers[0].options = ['Joe', 'Mary', 'Stan', 'Cindy']; Managers[1].options = ['Joe', 'Mary', 'Stan', 'Cindy']; } if (department.value === 'Sales') { Managers[0].options = ['Lou', 'George', 'Wendy']; Managers[1].options = ['Lou', 'George', 'Wendy']; } } //2nd Rule - Items Added if (Erepeat.itemAdded) { var index = Erepeat.itemIndex; // which item is this in the list ES[index].value = 'Day'; // default the employee shift to day // Use options already selected in form.load rule Managers[index].options = Managers[0].options; } |
This rule is useful in a workflow where you want to make a the tab named Review visible only for the workflow activity named Manager Review. See Built-in Data for more details on _data.getParameter and flow.activity.name.
Code Block |
---|
if (form.load) { if (_data.getParameter('flow.activity.name') === 'Manager Review') { Review.visible = true; } } |
form.unload
Rules can be used to finalize field values after the users clicks the form's submit button but prior to the Form and Doc Action execution. Rules using form.unload are triggered when the form user clicks the submit button and for workflows when the user clicks continue to go to the next activity or finish to complete the flow.
One common example use is for an order form order number. You may only want to assign a unique sequential order number to each order submission. You could initialize the form's order number when the form loads using form.load. However if someone start filling in the order form but never submit it you do not want to consume the next order number in sequence if it will never be used. Using form.unload you can assign the number after the submit button click but before the form data is submitted.
Here OrderNum is the name of a invisible control.
Code Block |
---|
/*member num */ var x; if (form.unload) { eval('x=' + http.get('http://<webhost>/json/getNextOrdernum')); OrderNum.value = x.num; } |
Geo Location
Rules can be used to save a snapshot of location information of any form. For example, an insurance company may want to capture the GPS location of their representatives filling out a form used for audit purposes. The designer can use the Geo Location feature in conjunction with rules like the ones shown in the examples below to accomplish this. When the form loads in the browser, the user will be prompted by to allow/disallow the use of location services. If permission is granted, the position will be calculated and server will be updated via an Ajax which causes the rule to fire. If the user denies permission or there is a timeout, the server will get an Ajax with an error.
Use the special rule identifier, form.positionUpload, to set up a rule in a form that will execute every time the form location is updated. See Rules Position Data for the complete list of available position.* data that you can access from your rules.
Code Block |
---|
if (form.positionUpdated) { Latitude.value = _data.getParameter ("position.latitude"); Longitude.value = _data.getParameter ("position.longitude"); Accuracy.value = _data.getParameter ("position.accuracy"); PositionError.value = _data.getParameter ("position.error.message"); } |
Here is an example of a location tab on a Police Report form.
Checking the Detailed Loc checkbox in the Form Property pane and implementing a rule like the one shown will go to Google Maps to get additional information and a map associated with the location.
Code Block |
---|
if (form.positionUpdated) { Latitude.value = _data.getParameter ("position.latitude"); Longitude.value = _data.getParameter ("position.longitude"); Accuracy.value = _data.getParameter ("position.accuracy"); PositionError.value = _data.getParameter ("position.error.message"); StreetNumber.value = _data.getParameter ("position.street_number"); StreetLine1.value = _data.getParameter ("position.route"); City.value = _data.getParameter ("position.locality"); State.value = _data.getParameter ("position.administrative_area_level_1"); County.value = _data.getParameter ("position.administrative_area_level_2"); Country.value = _data.getParameter ("position.country"); ZipCode.value = _data.getParameter ("position.postal_code"); CompleteStreetAddress.value = _data.getParameter ("position.street_address"); } |
When you run the form (unless you get a timeout or error) the address will automatically get filled in and the Google map will display.
Sequential Numbers
The Google Spreadsheet connector can also be used to generate unique sequential numbers. This technique should not be used if you have multiple people using the form at the same time as it's possible that two people simultaneously opening the same form could get the same sequential number.
In this example have a checkbook form where we want to initialize a field named CheckNum with the next sequential check book number when the form loads. Here is our Google spreadsheet:
Here is the rule that reads the next sequential number from the spreadsheets and updates plus 1. See Connecting to Google Spreadsheets for more details.
Code Block |
---|
/*member nextid results */ var x; if (form.load) { var readmethod = 'http://www.frevvo.com/google/spreadsheets/query/u/<google username>/p/<google password>/s/SequentialNumberGenerator/w/Sheet1?media=json&query=formname="Checkbook"'; var updatemethod = 'http://www.frevvo.com/google/spreadsheets/update/u/<google username>/p/<google password>/s/SequentialNumberGenerator/w/Sheet1?media=json&query=formname="Checkbook"'; eval('x=' + http.get(readmethod)); var id = 'unknown'; if (x.results === null) { CheckNum.value = 'error'; } else if (x.results.length === 0) { CheckNum.value = 'No Match'; } else { id = x.results[0].nextid; CheckNum.value = id; id = id*1 + 1; } if (id !== 'unknown') { eval('x=' + http.get(updatemethod + '&updates=nextid=' + id + '&_method=put')); } } |
Unique ID
Forms such as invoices, bills of lading, etc often need to be stamped with a unique ID. The Sequential Number example is one approach, however it has some limitations. One is that you must guarantee that only one person at a time is filling out your form. This is because there is no mutex around the read and update of the Google spreadsheet cell.
Here is a simpler method if your unique IDs do not need to be sequential. The data named form.id is guaranteed to be unique for every new form instance. You can just use it as follows:
Code Block |
---|
if (form.load) { InvoiceNum.value = _data.getParameter('form.id'); } |
Repeat Item Initialization
The rule above was one example of initializing a newly added repeating control with a default list of options. This same concept is useful if you want to initialize a repeating control's value. When you add a repeat to your form in the Form Designer you can set a default value in any of those repeating controls visible in the designer. However when the user clicks "+" while using the form to add an additional item the default entered in the Form Designer is not automatically used in this new item. In order to accomplish this you can add a simple rule as follows:
This rule initializes the value of one of the fields in the repeat to a default of '0'. RepeatTrack is the name of the repeat control containing the input control named albumOnly. This rule will execute each time a new RepeatTrack item is added when the user clicks "+".
Note |
---|
ItemAdded and itemIndex are properties of the Repeat control. |
Code Block |
---|
if (RepeatTrack.itemAdded) { var index = RepeatTrack.itemIndex; albumOnly[index].value = 0; } |
Again, to initialize repeat items already on your form in the Form Designer by default, either enter your initial default values directly into the in the Form Designer or add this rule.
Code Block |
---|
if (form.load) { for (var i=0; i < albumOnly.value.length; i++) { albumOnly[i].value = 0; } } |
This rule takes into account a repeat where min > 1. If min is 0 or 1, you can simplify this further by removing the for loop and simply have albumOnly[0].value = 0 inside the if (form.load).
Repeat ItemAdded by Init Doc
ItemAdded also executes when adds items found in an init doc. You may want to only initialize items added when the user clicks "+" on the form. And not those added from an initial document. This form contains a Mailing Label that repeats. Each label needs a unique ID assigned. However once the form is submitted the assigned IDs are saved in the database via the form's Doc URI. When the form loads it adds items automatically from rows in the database. They already have assigned Ids. We only need to assign new Ids in the sequence when the user manually adds a new Mailing Label by clicking the "+" button on the form. MLrepeat is the name of the Mailing Label repeat. MLmid is the name of the ID field in the repeat.
Code Block |
---|
if (MLrepeat.itemAdded) { var index = MLrepeat.itemIndex; // This rule is fired both when the user clicks "+" // and when frevvo adds items found in the init doc. // Need to assign new mid only when user clicks "+" // New items added via "+" will have a zero length value. if (MLmid[index].value.length === 0) { // Assign unique ID to label so it can be referenced // in RI Mailing Labels field // Count the number of existing mailing labels on the form var maxId = 0; for (var i=0; i < MLmid.value.length; i++) { if (MLmid[i].value > maxId) { maxId = MLmid[i].value; } } var next = parseInt(maxId, 10) + 1; MLmid[index].value = next.toFixed(0); } }< dob2.getMonth() || (today.getMonth() === dob2.getMonth() && today.getDate() < dob2.getDate())) { age -= 1; } if (age >= 0) { Age.value = age; } else { Age.value = null; } } |
Duration
This form initializes the hospital discharge date using a rule, and when the user enters the admission date a 2nd rule calculates the number of days the patient stayed in the hospital.
Code Block |
---|
/ Calculate Hospital Stay Duration
if (A.value !== '' && D.value !== '') {
var da = A.value.split('-');
var Ams = new Date(da[0],da[1],da[2]);
da = D.value.split('-');
var Dms = new Date(da[0],da[1],da[2]);
if (Ams > Dms) {
Days.value = 'Discharge date must be after Admission Date';
} else {
Days.value = (Dms - Ams) / (1000*60*60*24) + ' days';
}
} |
Duration Between Date and Time
Here is a rule example to calculate the time difference between two Date/Time values in hours:minutes format :
Code Block |
---|
if (StartDateTime.value !== '' && EndDateTime.value !== '') {
var d = StartDateTime.value.split('-');
var d1 = d[2].split('T');
var t = d1[1].split('Z')[0];
var t1 = t.split(':');
var startDate = new Date(d[0],d[1],d1[0], t1[0], t1[1], t1[2]);
d = EndDateTime.value.split('-');
d1 = d[2].split('T');
t = d1[1].split('Z')[0];
var t1 = t.split(':');
var endDate = new Date(d[0],d[1],d1[0], t1[0], t1[1], t1[2]);
var diff = endDate.getTime() - startDate.getTime();
var hours = Math.floor(diff / 1000 / 60 / 60);
diff -= hours * 1000 * 60 * 60;
var minutes = Math.floor(diff / 1000 / 60);
TimeToComplete.value = (hours < 9 ? "0" : "") + hours + ":" + (minutes < 9 ? "0" : "") + minutes;
} |
Today's Date and Time
Use 's built-in date and time methods to set your date, time, and date/time controls to the current date and time in the user's local timezone.
Code Block |
---|
if (form.load) {
Tm.value = frevvo.currentTime(form);
Dt.value = frevvo.currentDate(form);
DtTm.value = frevvo.currentDateTime(form);
} |
Note |
---|
The currentTime(), currentDate() and currentDateTime() will not work in a form.load rule unless you specify a timezone on the form's Url via the _formTz Url parameter. This is because the form server needs to know the timezone in which to return the date and time. If you do not specify a formTz the methods will return null and the control values will remain blank. For example &formTz=America/New_York will set the control values to the current date and time in the eastern timezone. |
Date/Time Stamp
This rule sets a control named Signature to the value of a control named Name plus a date/time stamp. Note that it is better to use the built-in date and time methods if you want to set the date to the user's local timezone. The following method will set the date to the form server's timezone.
Code Block | ||
---|---|---|
| ||
var today = new Date();
var m = today.getMonth() + 1;
var d = today.getDate();
var y = today.getFullYear();
var h = today.getHours(); var min = today.getMinutes(); var todayStr = m + '-' + d + '-' + y + ' ' + h + ':' + min; Signature.value = 'Signed by ' + Name.value + ' on ' + todayStr; |
Invalid if Before Today
This rule makes the date control invalid if the date entered isn't before today's date.
Code Block |
---|
var today = new Date();
var bd = DOB.value.split('-');
var bd_date = new Date(bd[0],bd[1]-1,bd[2]);
if (bd_date.getTime() > today.getTime()) {
MyMsg.value = 'Birth Date must be earlier than today!!!!';
DOB.valid = false;
} else {
MyMsg.value = 'This is a good Birth Date: ' + DOB.value;
DOB.valid = true;
} |
Date no more then 14 days from Today
This rule checks that the date entered into a control named AppointmentDate is no more than 14 days greater than today's date.
Code Block |
---|
var date1 = DateUtil.today();
var date2 = Appointment.value;
date1 = date1.split("-");
date2 = date2.split("-");
var sDate = new Date(date1[0]+"/"+date1[1]+"/"+date1[2]);
var eDate = new Date(date2[0]+"/"+date2[1]+"/"+date2[2]);
var DaysApart = Math.round((eDate-sDate)/86400000);
if (DaysApart > 14) {
Appointment.status = "Date should be within 14 days from today's date.";
} else if (DaysApart < 0) {
Appointment.status = "Date should not be earlier to today's date.";
} |
Date no more then 30 days ago
This rule checks that the date entered into a control named EventStartDate is not more then 30 days ago.
Code Block |
---|
if (EventStartDate.value !== "") {
var date1 = DateUtil.today();
var date2 = EventStartDate.value;
date1 = date1.split("-");
date2 = date2.split("-");
var sDate = new Date(date1[0]+"/"+date1[1]+"/"+date1[2]);
var eDate = new Date(date2[0]+"/"+date2[1]+"/"+date2[2]);
var days = Math.round((eDate-sDate)/86400000);
if (!eval(parseInt(days,10) > parseInt(-30,10))) {
EventStartDate.valid = false;
EventStartDate.status = "The date entered can only go back a maximum of 30 days from the current date. Please try again.";
} else {
EventStartDate.valid = true;
}
} |
Add # of Years to a Date
Here is a rule that will add 3 years to a given date. For example, to calculate the expiration date of a three year contract by adding three years to the starting date, your form could have two date controls, one used to enter the starting date and the other to show the contract expiration date. This rule will take the date from the StartingDate field, add 3 years to it and populate the result in a field named ExpirationDate.
Code Block |
---|
if(StartingDate.value.length > 0) {
var md = StartingDate.value.split('-');
var today=new Date();
var hour = today.getHours();
var oldDate = new Date(md[0],md[1]-1,md[2],hour);
var year = parseInt(oldDate.getFullYear(), 10);
var month = parseInt(oldDate.getMonth(), 10);
var date = parseInt(oldDate.getDate(), 10);
var newDate = new Date(year + 3, month, date, hour);
var m = newDate.getMonth()+1;
var d = newDate.getDate();
var y = newDate.getFullYear();
var newDateStr = m + '-' + d + '-' + y;
ExpirationDate.value = newDateStr;
} |
Change the "3" in this line to the number of years that you want to add - var newDate = new Date(year + 3, month, date, hour);
Central Timezone adjusted for Daylight Savings
This rule adjust today's date in UTC timezone to Central timezone and adjust for daylight savings time. This additional conversion is most commonly needed for Online users as the javascript Date() and ' DateUtil.today() both return today's date in UTC timezone.
Code Block |
---|
// Converts UTC to either CST or CDT
if (form.load)
{
var today = new Date();
var DST = 1; // If today falls outside DST period, 1 extra hr offset
var Central = 5; // Minimum 5 hr offset from UTC
// Is it Daylight Savings Time?
//
var yr = today.getFullYear();
// 2nd Sunday in March can't occur after the 14th
var dst_start = new Date("March 14, "+yr+" 02:00:00");
// 1st Sunday in November can't occur after the 7th
var dst_end = new Date("November 07, "+yr+" 02:00:00");
var day = dst_start.getDay(); // day of week of 14th
// Calculate 2nd Sunday in March of this year
dst_start.setDate(14-day); day = dst_end.getDay(); // day of the week of 7th
// Calculate first Sunday in November of this year dst_end.setDate(7-day);
// Does today fall inside of DST period?
if (today >= dst_start && today < dst_end) { DST = 0;
}
// Adjust Date for Central Timezone
today.setHours(today.getHours() - Central - DST);
var m = today.getMonth() + 1;
var d = today.getDate();
var da = today.getDay();
var y = today.getFullYear();
var h = today.getHours();
var min = today.getMinutes();
if (min < 10) { min = '0' + min;}
var timezone = ['CDT', 'CST'];
var dom = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday'];
var todayStr = dom[da] + ' ' + m + '-' + d + '-' + y + ' ' + h + ':' + min + ' ' + timezone[DST];
DateTime.value = todayStr;
} |
Hours >= 4 and <= 6 Apart
This rule makes sure the end time is at least 4 hours great then the start time but no more then 6 hours later then the start time. Also start time must be on or after 1:00. The times must be entered in military units. TS is the name of the Time Start control and TE is the name of the Time End control.
- Use Text controls for the start and end times
- Use this pattern in the control to ensure valid military times 1:00 or greater: ([1-9]|1[0-9]|2[0-4]):([0-5][0-9])
Code Block |
---|
if (TS.value.length > 0 && TE.value.length > 0) {
var sTime = TS.value.split(':');
var sHour = sTime[0];
var sMin = sTime[1];
var sMins = sHour * 60 + parseInt(sMin,10);
var eTime = TE.value.split(':');
var eHour = eTime [0];
var eMin = eTime [1];
var eMins = eHour * 60 + parseInt(eMin,10);
if ((eMins - sMins) < 4*60 || (eMins - sMins) > 6*60)
{
TE.valid = false;
} else {
TE.valid = true;
}
} |
Times
The date control can be set to either just a date, just a time, or a combined date/time. Here are several examples of initializing a time control name Tm;
Code Block |
---|
/ Both set the control name Tm to the same time
Tm.value = '8:30 pm'; // uses am/pm notation
Tm.value = ' 20:00'; // uses military time
// Initialize a date/time uses a "T" to separate the values
DtTm.value = "5/15/2012T4:20";
// Copying values
Tm2.value = Tm.value;
Dt2.value = Dt.value;
DtTm2.value = DtTm.value; |
Tenants, Roles, Users
have several built-in methods that enable you to access information about your form server such as the list of tenants; users in a tenant; and roles in a tenant. See built-in methods for the complete list. Some of these methods return a boolean true/false value. Others return a JSON string. Here is a sample of how to use these methods.
Code Block |
---|
*member roles tenants users */
var x;
if(form.load)
{
eval('x=' + frevvo.listTenants());
Tenants.options = x.tenants; // Init a dropdown list of all tenants on this form server
}
if(tenant.value.length > 0)
{
// Check if a tenant already exists
if (frevvo.isUniqueRoleId(role.value, tenant.value) === false) {
ErrMsg.value = 'The role ' + role.value + ' already exists in tenant ' + tenant.value;
}
// Init dropdown with all tenant users
eval('x=' + frevvo.listUsers(tenant.value));
Users.options = x.users;
// Init dropdown with all tenant roles
eval('x=' + frevvo.listRoles(tenant.value));
Roles.options = x.roles;
// Init dropdown will all tenant roles except admin roles
eval('x=' + frevvo.listRoles(tenant.value, false));
RolesNA.options = x.roles;
} |
Code Block |
---|
// Verify that a role already exists in the tenant
if(role.value.length > 0)
{
t.value = frevvo.isUniqueRoleId(role.value, tenant.value);
if (frevvo.isUniqueRoleId(role.value, tenant.value) === false) {
ErrMsg.value = 'The role ' + role.value + ' already exists in tenant ' + tenant.value;
}
} |
Here is an example of populating a dropdown list of all tenants on the form server with value is set to tenant Id and label is set to the tenant description returned in the JSON string. The frevvo.listTenants() method returns a string like this: {"tenants":["Default tenant","My Test Tenant"],"ids":["d","nancy.com"]}. The eval function parses that string into an object that we can iterate as follows:
Code Block |
---|
/*member tenants ids */
if(form.load)
{
TA.value = frevvo.listTenants();
var x;
eval('x=' + frevvo.listTenants());
var opts = [];
for (var i = 0; i < x.tenants.length; i++) {
opts[i] = x.ids[i] + '=' + x.tenants[i];
}
// Initialize dropdown of all tenants on the form server
TenantList.options = opts;
|
Repeat Item Added
This rule executes when a new item is added to a repeat. Imagine your form contains a repeating section named Employee with name Erepeat. NOTE: that the name Erepeat is set on the Repeat control and not on the Section control. The Employee section control contains many controls such as Name, Phone, etc.. and a dropdown control labeled Manager and named M. It also contains a radio control labeled Employee Shift named ES whose options have been set to 'Day' and 'Evening'.
will execute this rule each time a user clicks "+" on the repeat to add a new item. Here we want to default the Employee Shift ES to the value 'Day', and populate the Manager dropdown dynamically with values from the Database Connector.
Usually your form will have a form.load rule to initialize dropdown options for the 1st repeat item visible on your form by default.
Code Block | ||
---|---|---|
| ||
/*member firstname lastname options resultSet value */
var x;
if (form.load)
{
var baseURL = 'http://www.myhost.com:8080/database/';
// Manager Dropdown
eval('x=' + http.get(baseURL + 'Manager'));
var opts = ['']; // default 1st option to blank
for (var i=0; i < x.resultSet.length; i++) {
if (x.resultSet[i]) {
opts[i+1] = x.resultSet[i].lastname+ ", " + x.resultSet[i].firstname;
}
}
// Repeat item index 0 is on the form by default
M[0].options = opts;
ES[0].value = 'Day'; // default the employee shift to day
} |
Now when a new item gets added when a user clicks the "+" icon we can save the overhead of going back to the database to retrieve dynamic options.
Code Block |
---|
if (Erepeat.itemAdded)
{
var index = Erepeat.itemIndex; // which item is this in the list
// No need to go back to the database for options.
// We already retrieved them in form.load rule for repeat item index 0
M[index].options = M[0].options;
ES[index].value = 'Day'; // default the employee shift to day
} |
Info |
---|
Tables are repeats. So the same rule can be written for a table control. The name of a table's repeat is always <TableName>Repeat. For example if you name your table Children. Then the repeat is named ChildrenRepeat. |
Repeat Item Added - Collapse Other Items
This rule executes when a new item is added to a repeat. This form contains a repeating section with a templatized label. It is nice to collapse the other items when adding a new item to keep the repeat list small and grid-like. Medrepeat is the name of the repeat control. Medication is the name of the section control inside the repeat.
Code Block |
---|
if (Medrepeat.itemAdded)
{
var index = Medrepeat.itemIndex;
for (var i = 0; i < Medication.value.length; i++) {
if (i !== index) {
Medication[i].expanded = false;
}
else {
Medication[i].expanded = true;
}
}
} |
Repeat Dynamic Min/Max
It is not currently possible to change a repeat control's min and max properties in a business rule. This feature may be added in a future release. One solution is to use hidden section controls rather than a repeat control. For example imagine an airline reservation form where the number of traveler detail rows displayed and required is based on the number of airline tickets purchased. This example form allows a maximum of 3 ticket purchases. So, first add 3 section controls for traveler details. It is important to make sure that you name the travel detail section controls uniquely -- Traveler1 is the name of the first section, Traveler2 is the name of the second section, etc.. Next uncheck the visible and required property on all 3 section controls. Then add a business rule that makes the number of sections visible and required based on the number of purchased airline tickets.
Code Block |
---|
Traveler1.visible = false;
Traveler2.visible = false;
Traveler3.visible = false;
Traveler1.required = false;
Traveler2.required = false;
Traveler3.required = false;
for (var i=0; i < NumTickets.value; i++) {
if (i >= 0) {
Traveler1.visible = true;
Traveler1.required = true;
}
if (i >= 1) {
Traveler2.visible = true;
Traveler2.required = true;
}
if (i >= 2) {
Traveler3.visible = true;
Traveler3.required = true;
}
} |
Tables
Tables are identical to repeat controls when referenced in business rules. Tables are a grid layout of repeating items. All the rule examples in this chapter that discuss repeats apply also to tables. The one important note is that you cannot explicitly name the repeat control inside your table. The repeat control inside a table is automatically named as <TableName>Repeat. For example a table named Expense automatically has a repeat named ExpenseRepeat. The rule ExpenseRepeat.itemAdded and ExpenseRepeat.itemIndex references an item added to your table and that item's index respectively.
One special consideration for tables is that rules which dynamically set the column header label, hint and help mesages must use the array syntax. This is somewhat counter-intuitive since each column appears to have only a single label, help and hint. Here is the rule to dynamically set these three properties:
Code Block |
---|
FirstName[0].label = 'blah label';
FirstName[0].hint = 'blah hint';
FirstName[0].help = 'blah help'; |
Clearing Values in a Table
This rule clears the values from all rows in a table. Notice the For loop that iterates over all the rows. Inside the loop a null value is assigned to all the columns in the table row.
Code Block | ||
---|---|---|
| ||
for (var i = 0; i < Col0.value.length; i++) {
Col0[i].value = null;
Col1[i].value = null;
Col2[i].value = null;
} |
You cannot clear an entire table from a rule.
Note | ||
---|---|---|
A section in a repeat will behave differently than a table row when using a rule to set the valid property. For Example, drag and drop a table control into your form. Name it T. Then drag and drop a section into the same form. Name it S. Add a control to the section. Name the control C. Drop the section into a repeat named R. Add this rule to the form.
The section header shows "Invalid value" as a message while the table row shows no such indication signifying an invalid row. |
form.load
Rules can be used to initialize field values. This is a very useful feature and is often used to dynamically populate dropdown options from a database. Rules using form.load are triggered when a form first loads and when a workflow is loaded from a task list.
Rules using itemAdded only execute for repeat items added when the user clicks +, and for those added from an initial instance document (See Document URIs). It does '''not''' execute for those items that you have added to your form in the Form Designer. You can either add defaults directly via the form designer or add a 2nd rule to your form as follows.
These two rules together initialize the dropdown fields inside a repeat that are already in the form via the Form Designer, as well as those added each time a user clicks "+" on the repeat to add a new item & via initial documents. These controls are initialized based on a value set in another field.
Code Block |
---|
//1st Rule - Default Items
if (form.load)
{
// The form contains two repeat items by default.
if (department.value === 'Marketing') {
Managers[0].options = ['Joe', 'Mary', 'Stan', 'Cindy'];
Managers[1].options = ['Joe', 'Mary', 'Stan', 'Cindy'];
}
if (department.value === 'Sales') {
Managers[0].options = ['Lou', 'George', 'Wendy'];
Managers[1].options = ['Lou', 'George', 'Wendy'];
}
}
//2nd Rule - Items Added
if (Erepeat.itemAdded)
{
var index = Erepeat.itemIndex; // which item is this in the list
ES[index].value = 'Day'; // default the employee shift to day
// Use options already selected in form.load rule
Managers[index].options = Managers[0].options;
} |
This rule is useful in a workflow where you want to make a the tab named Review visible only for the workflow activity named Manager Review. See Built-in Data for more details on _data.getParameter and flow.activity.name.
Code Block |
---|
if (form.load) {
if (_data.getParameter('flow.activity.name') === 'Manager Review') {
Review.visible = true;
}
} |
form.unload
Rules can be used to finalize field values after the users clicks the form's submit button but prior to the Form and Doc Action execution. Rules using form.unload are triggered when the form user clicks the submit button and for workflows when the user clicks continue to go to the next activity or finish to complete the flow.
One common example use is for an order form order number. You may only want to assign a unique sequential order number to each order submission. You could initialize the form's order number when the form loads using form.load. However if someone start filling in the order form but never submit it you do not want to consume the next order number in sequence if it will never be used. Using form.unload you can assign the number after the submit button click but before the form data is submitted.
Here OrderNum is the name of a invisible control.
Code Block |
---|
/*member num */
var x;
if (form.unload)
{
eval('x=' + http.get('http://(your webhost)/json/getNextOrdernum'));
OrderNum.value = x.num;
} |
Geo Location
Rules can be used to save a snapshot of location information of any form. For example, an insurance company may want to capture the GPS location of their representatives filling out a form used for audit purposes. The designer can use the Geo Location feature in conjunction with rules like the ones shown in the examples below to accomplish this. When the form loads in the browser, the user will be prompted by to allow/disallow the use of location services. If permission is granted, the position will be calculated and server will be updated via an Ajax which causes the rule to fire. If the user denies permission or there is a timeout, the server will get an Ajax with an error.
Use the special rule identifier, form.positionUpload, to set up a rule in a form that will execute every time the form location is updated. See Rules Position Data for the complete list of available position.* data that you can access from your rules.
Code Block |
---|
if (form.positionUpdated) {
Latitude.value = _data.getParameter ("position.latitude");
Longitude.value = _data.getParameter ("position.longitude");
Accuracy.value = _data.getParameter ("position.accuracy");
PositionError.value = _data.getParameter ("position.error.message");
} |
Here is an example of a location tab on a Police Report form.
Checking the Detailed Loc checkbox in the Form Property pane and implementing a rule like the one shown will go to Google Maps to get additional information and a map associated with the location.
Code Block |
---|
if (form.positionUpdated) {
Latitude.value = _data.getParameter ("position.latitude");
Longitude.value = _data.getParameter ("position.longitude");
Accuracy.value = _data.getParameter ("position.accuracy");
PositionError.value = _data.getParameter ("position.error.message");
StreetNumber.value = _data.getParameter ("position.street_number");
StreetLine1.value = _data.getParameter ("position.route");
City.value = _data.getParameter ("position.locality");
State.value = _data.getParameter ("position.administrative_area_level_1");
County.value = _data.getParameter ("position.administrative_area_level_2");
Country.value = _data.getParameter ("position.country");
ZipCode.value = _data.getParameter ("position.postal_code");
CompleteStreetAddress.value = _data.getParameter ("position.street_address");
} |
When you run the form (unless you get a timeout or error) the address will automatically get filled in and the Google map will display.
Sequential Numbers
The Google Spreadsheet connector can also be used to generate unique sequential numbers. This technique should not be used if you have multiple people using the form at the same time as it's possible that two people simultaneously opening the same form could get the same sequential number.
In this example have a checkbook form where we want to initialize a field named CheckNum with the next sequential check book number when the form loads. Here is our Google spreadsheet:
Here is the rule that reads the next sequential number from the spreadsheets and updates plus 1. See Connecting to Google Spreadsheets for more details.
Code Block |
---|
/*member nextid results */
var x;
if (form.load) {
var readmethod =
'http://www.frevvo.com/google/spreadsheets/query/u/<google username>/p/<google password>/s/SequentialNumberGenerator/w/Sheet1?media=json&query=formname="Checkbook"';
var updatemethod =
'http://www.frevvo.com/google/spreadsheets/update/u/<google username>/p/<google password>/s/SequentialNumberGenerator/w/Sheet1?media=json&query=formname="Checkbook"';
eval('x=' + http.get(readmethod));
var id = 'unknown';
if (x.results === null)
{ CheckNum.value = 'error';
} else if (x.results.length === 0) {
CheckNum.value = 'No Match';
} else {
id = x.results[0].nextid;
CheckNum.value = id;
id = id*1 + 1;
}
if (id !== 'unknown') {
eval('x=' + http.get(updatemethod + '&updates=nextid=' + id + '&_method=put'));
}
} |
Unique ID
Forms such as invoices, bills of lading, etc often need to be stamped with a unique ID. The Sequential Number example is one approach, however it has some limitations. One is that you must guarantee that only one person at a time is filling out your form. This is because there is no mutex around the read and update of the Google spreadsheet cell.
Here is a simpler method if your unique IDs do not need to be sequential. The data named form.id is guaranteed to be unique for every new form instance. You can just use it as follows:
Code Block |
---|
if (form.load)
{
InvoiceNum.value = _data.getParameter('form.id');
} |
Repeat Item Initialization
The rule above was one example of initializing a newly added repeating control with a default list of options. This same concept is useful if you want to initialize a repeating control's value. When you add a repeat to your form in the Form Designer you can set a default value in any of those repeating controls visible in the designer. However when the user clicks "+" while using the form to add an additional item the default entered in the Form Designer is not automatically used in this new item. In order to accomplish this you can add a simple rule as follows:
This rule initializes the value of one of the fields in the repeat to a default of '0'. RepeatTrack is the name of the repeat control containing the input control named albumOnly. This rule will execute each time a new RepeatTrack item is added when the user clicks "+".
Note |
---|
ItemAdded and itemIndex are properties of the Repeat control. |
Code Block |
---|
if (RepeatTrack.itemAdded)
{
var index = RepeatTrack.itemIndex;
albumOnly[index].value = 0;
} |
Again, to initialize repeat items already on your form in the Form Designer by default, either enter your initial default values directly into the in the Form Designer or add this rule.
Code Block |
---|
if (form.load) { for (var i=0; i < albumOnly.value.length; i++) { albumOnly[i].value = 0; } } |
This rule takes into account a repeat where min > 1. If min is 0 or 1, you can simplify this further by removing the for loop and simply have albumOnly[0].value = 0 inside the if (form.load).
Repeat ItemAdded by Init Doc
ItemAdded also executes when adds items found in an init doc. You may want to only initialize items added when the user clicks "+" on the form. And not those added from an initial document. This form contains a Mailing Label that repeats. Each label needs a unique ID assigned. However once the form is submitted the assigned IDs are saved in the database via the form's Doc URI. When the form loads it adds items automatically from rows in the database. They already have assigned Ids. We only need to assign new Ids in the sequence when the user manually adds a new Mailing Label by clicking the "+" button on the form. MLrepeat is the name of the Mailing Label repeat. MLmid is the name of the ID field in the repeat.
Code Block |
---|
if (MLrepeat.itemAdded)
{
var index = MLrepeat.itemIndex;
// This rule is fired both when the user clicks "+"
// and when frevvo adds items found in the init doc.
// Need to assign new mid only when user clicks "+"
// New items added via "+" will have a zero length value.
if (MLmid[index].value.length === 0) {
// Assign unique ID to label so it can be referenced
// in RI Mailing Labels field
// Count the number of existing mailing labels on the form
var maxId = 0;
for (var i=0; i < MLmid.value.length; i++)
{
if (MLmid[i].value > maxId) {
maxId = MLmid[i].value;
}
}
var next = parseInt(maxId, 10) + 1;
MLmid[index].value = next.toFixed(0);
}
} |
Repeat Item Increment
You can easily auto populate incremental items numbers in repeats using a business rule. In this example Erepeat is the name of the repeat control and Item is the name of the item control inside the repeat. You also need to set 1 as the default value of first repeating Item control directly into your form field via the form designer as shown here.
Code Block |
---|
if (Erepeat.itemAdded || Erepeat.itemRemoved){
for(var i = 0; i < Item.value.length; i++) {
Item[i].value = i+1;
}
} |
Display Uploaded Image
A rule can dynamically display an image uploaded to your form via the upload control. In this example the upload control is named 'u'. The form also must contain a message control as a place holder for displaying the uploaded image. The rule dynamically creates a URL to the uploaded image in the temporary attachment repository. The upload control's value 'u.value' is a GUID that uniquely identifies the attachment. The uploaded image is included in the submissions pdf.
Code Block | ||
---|---|---|
| ||
if (u.value.length > 0) { var baseUrl = _data.getParameter('_frevvo_base_url') + "/frevvo/web/tn/" + _data.getParameter('tn.id') + "/user/"+_data.getParameter('user.id') + "/app/"+_data.getParameter('app.id') + "/form/"+_data.getParameter('form.id'); im.value = "'<img src="'" + baseUrl + "'/attachment/"' + u.value+"'/does_not_matter'"/>"'; } |
Here is the example form before and after the user has upload uploaded the orangegrove.png image:
...
Code Block | ||
---|---|---|
| ||
var x = ""; for(var i=0; i< u.value.length; i++) { x = x + u[i].value + ","; } var y = x.split(','); var baseUrl = _data.getParameter('_frevvo_base_url') + "frevvo/web/tn/" + _data.getParameter('tn.id') + "/user/"+_data.getParameter('user.id') + "/app/"+_data.getParameter('app.id') + "/form/"+_data.getParameter('form.id'); if(y[0].length === 1) { im.value = "<img src='" + baseUrl + "/attachment/"+u.value+"/does_not_matter'/>"; } else { var z = ""; for(var i=0; i<= y.length; i++) { z = z + "<img src='" + baseUrl + "/attachment/"+ y[i] +"/does_not_matter'/>" + "<br/>"; } im.value = z; } |
Search Popup
forms can initialize dynamically from backend systems. A common form design pattern is the master/detail. An example of master/detail is a form that contains an order number or employee Id. When the user enters an existing Id into the form field you want all other form fields to populate dynamically from values in a database. This is done using a form created from XSD data source generated by the database connector. This part of the master/detail form design pattern needs no business rule. It happens automatically using the Doc Action manually set document URIs. See the DB Connector Tutorial for complete detail on how to initialize your form using a Doc Action document URI to your database.
...
Code Block |
---|
//Show Employee Search Criteria if (FindEmployee.clicked) { EmployeeSearch_s1.visible = true;visible = true; EmployeeSearch_s1.expanded = true; } |
The section control contains controls that are the search critiera for finding a RACF ID based on other pieces of information you may know about an employee such as name, type, email address. The values entered into the search criteria can be partial values. For instance entering a name "Smith" will find all employees whose name contains the letters "Smith". If you also select email, it will find all employees whose name contains "Smith" and have an email address containing the string "frevvo".
...
Code Block |
---|
/*member clicked racfid resultSet type */ var x; if (Search.clicked) { eval('x=' + http.get('database/myqset/findEmployee?name={name_s1}&type={type_s1}&racfid={id_s1}&email={email_s1}')); var opts= []; for (var i=0; i < x.resultSet.length; i++) { if (x.resultSet[i]) { opts[i] = x.resultSet[i].racfid + "=" + x.resultSet[i].name + ", " + x.resultSet[i].type; } } SearchMsg.value = x.resultSet.length + ' Matches Found. Change your criteria and click "search" to try again.'; SearchMsg.visible = true; SearchResults.options = opts; SearchResults.visible = true; } |
The Search returns one or more matches and dynamically populates a dropdown control named SearchResults. You can change the search criteria to narrow or expand you search. When you select one of the matches from the SearchResults dropdown this 3rd rule executes to copy the selection into the RACF ID control.
Code Block |
---|
//Select from Search results if (SearchResults.value.length > 0) { racfid.value = SearchResults.value; // Hide Employee Search and clear values EmployeeSearch.visible = false; SearchMsg.visible = false; false; SearchResults.visible = false; SearchResults.options = []; } |
Once the value is copied into RACF ID the form automatically loads all other values in the form from the database. This is due to the form action document Uri setting.
...
The values in the employee loaded into the form can now be edited. When the users clicks the form's submit button the values will automatically be updated in the database record. This is due to the form action document Uri setting.
Database Connector REST Services
The database connector REST services can be invoked via a rule. The http get returns a JSON object that contains a resultSet array containing each row returned from the SQL query. For details on configuring the database connector see the documentation for Connecting to your Database. For an overview of using JSON to dynamically populate dropdown controls click here.
...
Code Block |
---|
<query name="lastnames"> <statement> <retrieve>SELECT lastname FROM Customer</retrieve> </statement> </query> <query name="firstnames"> <statement> <retrieve>SELECT firstname FROM Customer</retrieve> </statement> </query> <query name="names"> <statement> <retrieve>SELECT firstname,lastname FROM Customer</retrieve> <statement> </query> |
The form contains a radio control with Yes/No options and a control named getCustomerList. When the Yes option is clicked this rule executes the http get to the database connector URI http://<host>/database/lastnames. The returned JSON resultSet object is an array of all the lastnames returned by the select specified above in configuration.xml. The resultSet is then used to set the options in a dropdown control named customerLastNames.
Code Block |
---|
/*member lastname resultSet */ var x; if (getCustomerList.value === 'Yes') { eval('x=' + http.get('http://<webhost>(your webhost)/database/lastnames')); var opts= []; for (var i=0; i < x.resultSet.length; i++) { if (x.resultSet[i]) { opts[i] = x.resultSet[i].lastname; } } customerLastNames.options = opts; } |
Warning |
---|
The case used in the select statements must be the exact case used in your rule. For example: if query is: select lastname from Customer ,then rule must use x.resultSet[i].lastname if query is: select LastName from Customer,then rule must use x.resultSet[i].LastName |
...
Code Block |
---|
/*member firstname lastname resultSet */ var x; if (getCustomerList.value === 'Yes') { eval('x=' + http.get('http://<webhost>(your webhost)/database/names')); var opts= []; for (var i=0; i < x.resultSet.length; i++) { if (x.resultSet[i]) { opts[i] = x.resultSet[i].firstname + " " + x.resultSet[i].lastname; } } customerFullNames.options = opts; } |
This example is similar except the event that triggers the execution of the rule is an initial instance document loaded into the form when it opens. See the section on Document URIs for details on configuring your form to load an instance document. This rule executes before the form becomes visible in the browser. Thus the dynamic options are populated prior to the user seeing the form.
...
Code Block |
---|
<query name="DocMgmt" autocreate="true"> <create>" autocreate="true"> <create> <!-- Maps to the HTTP POST method --> INSERT into documents SET docId='{docId}' </create> <retrieve> <!-- Maps to the HTTP GET method --> SELECT docname,docId FROM documents WHERE docId='{docId}' </retrieve> <update> <!-- Maps to the HTTP PUT method --> UPDATE documents SET docId='{docId}',docname='{docname}' WHERE docId='{docId}' </update> <delete> <!-- Maps to the HTTP DELETE method --> DELETE from documents WHERE docId='{docId}' </delete> </query> |
First, the URL http://<host>/database/DocMgmt/schema was opened in the browser to automatically generate an XSD describing the resultset returned from the DocMgmt <retrieve> method. This XSD was then uploaded into the application and the resultSet type added to the form. The docname control from that schema was edited and renamed 'docname'.
...
Code Block |
---|
/*member firstname resultSet */ var x; if (docname.value.length > 0) { eval('x=' + http.get('http://<webhost>(your webhost)/database/firstnames')); var opts= []; for (var i=0; i < x.resultSet.length; i++) { if (x.resultSet[i]) { opts[i] = x.resultSet[i].firstname; } } customerFirstNames.options = opts; } |