Solved

Can a formatting or validation rule be used to trigger a JavaScript function?

  • 24 July 2023
  • 6 replies
  • 199 views

Userlevel 1
Badge +8

Nintex SharePoint 2019 Classic Designer

I am creating a heavily customized form with lots of JS/jQuery and rules.  With certain field types, I am having difficulty getting JS functions to trigger on change, so I was hoping there was a way to have a validation (or formatting) rule that could, when resolves to true, invoke a JS function.

Thanks!

icon

Best answer by MegaJerk 26 July 2023, 10:03

View original

6 replies

Userlevel 5
Badge +14

Yes indeed you can. I use the method often to do things like advanced levels of formatting for controls, and for running a sort of real-time validation system I have in place.

 

Rules are essentially just a JS function wrapper that return the value of whatever you put inside of its Formula Builder.

 

Let’s say that I have a basic new Form: 

 

I select the Title and create a new Formatting Rule on it called “Formatting Test”:

 

and in the Rule’s Formula Builder dialog, I put the following code: 

(function(formControlCall){
"use strict";
var formControlID = formControlCall.split("'")[1] || "";
var targetControl = sourceContext.find("[formcontrolid='" + formControlID + "'].nf-filler-control");
var targetInput = NWF$(targetControl.find("[formcontrolid][id]")[0]);
debugger;

return false;
}("{Control:Self}"))

 

 

When I run preview the form, this is what the underlying code that Nintex generates the rule of the form with: 

 

As you can see, the code we put inside of the rule is actually the return value for the outermost named function “fn5209f33150e440968996dd2665970ed60”, which is the wrapper that Nintex produces for every unique rule upon form generation. That outermost Nintex created wrapper is what’s invoked whenever a change has been made to a control that would normally cause the Rule system to evaluate the rules on a given control. That call stack looks something like:

 

Where ProcessRuleOnControl would then reach this line:

 

and whatever statement we had put inside of the Rule would be evaluated and returned as the functionResult.

 

So in the above case, because our IIEF (see: https://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife) returns false, the ‘functionResult’ will be false, and therefore nothing will happen. It should of course be noted that nothing will happen even if we returned true because I didn’t bother checking any of the boxes for Hiding, Disabling, or otherwise Formatting the Control IN the Formatting Rule, but it’s important to remember sometimes as you may want the best of both worlds at some point (doing some complex work on a control in addition to Enabling / Disabling or Showing / Hiding said control). 

 

There are however many considerations you have to be aware of when taking this approach of putting your own JS inside of Rules. Mainly that Formatting Rules and Validation Rules behave differently!

 

Here is a junk Validation Rule: 

with the following code as the formula: 

(function(sourceContext, rowIndex) {
"use strict";

debugger;

return false;
}(sourceContext, rowIndex));

 

When I run this, and look at the debugger, I can see that both the sourceContext and rowIndex values have values!

 

But what are those values? 

 

In Nintex Forms the term “context” is usually meant to describe from “where” a Control is placed on a form. Specifically, is it inside of a Repeating Section or is it just part of the form proper? If it’s just on the Form proper, then the Context will be the table data element that acts as the Form container. However if I were to place this control inside of a Repeating Section, the context would be completely different because each Row of a Repeating Section is considered its own little unique world. 

 

The Rule system gives us some info about the context in the form of a value called sourceContext, which is defined in the wrapper that Nintex generates around your rule. You can reference it IF you pass it into your IIEF, but otherwise might cause a syntax error! We can see this in action if I move the above control into a repeating section: 

 

Now when the rule is invoked, I can see that the sourceContext is defined differently, not as a <td> element but as a div:

 

which has a class indicating that it’s a Repeating Row Item:

 

 

This is incredibly useful because it would otherwise be impossible to figure out *which* control you the rule was running on without its context. Actually… just with what I’ve told you here, it would still be impossible, but that’s where the ‘rowIndex’ comes into play. rowIndex contains the html ID that Nintex generates for the Control.

 

Because IDs are generated fresh every time a form is loaded, you can use the rowIndex value to dynamically target the control that the rule is running on (in a Validation Rule): 

 

So using the code: 

NWF$("#" + rowIndex);

 

will return a jQuery object of the ‘actual’ Control element that stores a Control’s value (MOST of the time!), but you will have to use some caution as this is not always the case for all of the different types and configurations of controls!

 

So that covers Validation, but what about Formatting? Well… that’s where things get messy. Formatting rules get the sourceContext defined, BUT nobody saw fit to pass along the rowIndex value into Formatting Rules, so we are stuck having to figure out a different way to get a reference to the control that the rule is being ran on. You may have noticed that for the JS I’m using in the formatting rule above, I’m passing in a value of “{Control:Self}”, but why? Because it’s the only way that I know of to get a Formatting Rule’s target Control’s reference! 

 

Because all of the built in Formula Builder references are just keywords that Nintex Forms looks for to replace with values at the time of a Form’s creation, we can abuse this by capturing whatever Nintex sets the {Self} value to as a string by enclosing {Self} in quotes.

 

(NOTE: In my code I write “{Control:Self}” this is because Nintex typically only display a shorthand “token” version of common references and item properties to the user when a user clicks on them. Those tokens (colored in red) show the shorthand name, but contain the metadata and longhand reference name in the background. And while Nintex can identify Common References and Item Properties in plaintext, it can only do so if they use the longhand name. So in order to use something like “{Self}” in code, we must use “{Control:Self}”. In fact once we save the form, the rule will typically replace the longhand plaintext name with the tokenized name without us needing to do anything. Just be careful if you’re copying / pasting from a Rule’s formula textbox into your code editor or reverse as you can easily end up using a shorthand or plaintext reference where a token actually needs to be!)

 

If we look at that code again from the first example: 

(function(formControlCall) {
"use strict";
var formControlID = formControlCall.split("'")[1] || "";
var targetControl = sourceContext.find("[formcontrolid='" + formControlID + "'].nf-filler-control");
var targetInput = NWF$(targetControl.find("[formcontrolid][id]")[0]);
debugger;

return false;
}("{Control:Self}"))

 

We can start to see what’s happening when we run it:

 

our “{Control:Self}” turns into "NWF.FormFiller.Functions.GetValue('b0800d5f-d260-4d1a-a15d-f961e60879be', sourceContext, 'string')", which had we not placed it inside of quotation marks, would have been interpreted correctly as a function call to get the value of the control the rule is running on (which is what {Self} does. it returns the current value of the control). By capturing it as a string however, we give ourselves a way to look at the value Nintex would have used to produce the value for the control. One of the arguments it passes is GUID that has no immediate meaning, but if I were to search the html, I would discover that it has a few matches, one of those being directly on some HTML that appears to be the control the rule is running on:

 

 

So we now know that it’s the value of the ‘formcontrolid’ attribute and with a little work can tease that out to get to our Control:

var formControlID = formControlCall.split("'")[1] || "";
var targetControl = sourceContext.find("[formcontrolid='" + formControlID + "'].nf-filler-control");
var targetInput = NWF$(targetControl.find("[formcontrolid][id]")[0]);

 

In the above code, “targetControl” gets a reference to the outermost element container of the Control in question, which does not produce a value! digging deeper into the children elements, namely one that has both the “formcontrolid” and “id” attributes, we can get to the ‘inner’ control that contains the actual value, which is why I’m doing for the ‘targetInput’ variable. 

 

Again you may have to dig around using different selectors for your jQuery based on the Control type in question and how that control is configured!


 

Examples and GOTCHAs

 

In my first example I talked about using a Formatting Rule to ensure some arbitrary but complex formatting on a Control. Let’s take a look at how we can accomplish something like that.

 

Let’s say that I want my Title to ALWAYS start with the following string “1123_” and then whatever the user types as the Title. Let’s try the following code in a rule:

(function(formControlCall) {
"use strict";
var formControlID = formControlCall.split("'")[1] || "";
var targetControl = sourceContext.find("[formcontrolid='" + formControlID + "'].nf-filler-control");
var targetInput = NWF$(targetControl.find("[formcontrolid][id]")[0]);
var prefix = "1123_";

/* debugger; */

if (!targetInput.val().startsWith(prefix)) {
targetInput.val(prefix + targetInput.val()).trigger("change");
}

return false;
}("{Control:Self}"))

 

When the form runs…

 

Something is wrong involving the term “RuntimeFunctions”. Let’s take a look at the code being generated in the Source and see what the rule looks like:

 

How did “!targetInput.val().startsWith(prefix)” turn into that mess!? well… it’s because Nintex Forms aggressively tries to replace any instance it sees that looks like a user friendly reference to one of its user friendly functions (in the formula builder) with its own function calls!

 

So because Nintex has its own ‘startsWith’ function:

 

when we want to use the native one, we must figure out a way to make whatever regex Nintex is using to discover these keywords skip our code. To do so all we need to do is to add a space between the function name and the open paren:

 

Now the form runs and our Title is being pre-formatted correctly: 

 

Even if we erase it and replace it with something else:

 

as soon as the rule runs, it will be corrected:

 

Now this is a silly example, but in my own use cases I will do things like ensuring that numeric values are being correctly zeroed out in a way that makes sense, especially for things like money. 

 

Here is a function that I use on a control that I use for a currency value which will format it using a small currency library (currency.js). If it is formatted, then a change event is triggered on the control to ensure that other controls that calculate using the target currency control in question, will update to reflect any changes.

(function(formControlCall, event) {
"use strict";
event = event || window.event;
var formControlID = formControlCall.split("'")[1] || "";
var targetControl = sourceContext.find("[formcontrolid='" + formControlID + "'].nf-filler-control").find("[formcontrolid][id]");

var priceValue = targetControl.val();
var convertedValue = currency(priceValue, {
separator: "",
formatWithSymbol: false
}).format();

var eventTarget = NWF$((event ? (event.target || event.srcElement) : ""));
var isNewRow = eventTarget.attr("value") === "Add New Row";

if (NCU.FormVariables.pageIsReady && !isNewRow && priceValue !== convertedValue) {
targetControl.val(convertedValue).trigger("change");
}

return false;
}("{Control:Self}", event))

 

So what that looks like is a control that’s initialized to being blank:

 

And if I interact with it:

 

Will have its value formatted to something coherent: 

 

In the above example it has also been validated to valid as denoted by the checkmark placed using that custom validation stuff I have running. 

 

And if I were to type in a different value but not bother to format it manually:

 

That also gets taken care of: 

 

 

 

I know this is a bit longer than a typical response but a lot of this stuff is obscure enough that without any guidance, it can be a real pain in the but to understand. This is by no means a comprehensive guide (something I’m working on for my own blog), but it should get you at least part of the way there towards understanding how to utilize the rule system for your own desires. 

 

As a bonus you will also be interested in knowing that most of this is also applicable to Calculated Controls, which will also execute an IIEF totally fine and can be used to do all sorts of work that would otherwise be out of reach using the built in user friendly runtime functions. 


Let me know if you have any further questions about this topic. I might not be able to answer them in a timely fashion, but I’ll get around to them when I can. 

 

Userlevel 1
Badge +8

Some great information - it will definitely take me some time to digest it all!

I have been going down the path of identifying each type of control and using jQuery to pick up its value on change and perform some function.  Rather than keep it all wrapped up in a .js file, I was looking for a way to incorporate it in the rules engine.  This will be a huge help!

Userlevel 5
Badge +14

It’s still helpful to know how and when things trigger by default because certain controls update in particular ways that are not always obvious. For instance, when you focus on a Single Line Text control, the value of whatever is in the Control currently is pushed into a property called “OldValue” in its jQuery data object. That ‘OldValue’ property is then used as a comparison against whatever value is in the Control when the blur event has been triggered after a user moves away from it. If the values don’t match, then it will run the ProcessOnChange method and any dependencies (Rules, Calculations, etc.) should run.

 

There is an internal method that runs upon a Form being rendered for the user called AttachOnChangeEvents, which contains all of the information you need to see how every control behaves in regards to triggering updates. This is the code it contains:

AttachOnChangeEvents: function (formFillerDivCurrent) {

formFillerDivCurrent.on('focus', 'input:text', function (event) {
NWF$(this).data("OldValue", this.value);
});

formFillerDivCurrent.on('focus', 'textarea', function (event) {
NWF$(this).data("OldValue", this.value);
});

// Attach event to trigger calucations (dynamic values) on text boxes using the "blur" event
formFillerDivCurrent.on('blur', 'input:text', function (event) {
if (NWF$(this).data("OldValue") !== this.value) {
NWF.FormFiller.Functions.ResetCircularReferenceRelatedVariables();
NWF.FormFiller.Functions.ProcessOnChange(NWF$(this));
}
});

// Attach event to trigger calucations (dynamic values) on option buttons using the "change" event
formFillerDivCurrent.on('change', 'input:radio', function (event) {
// Reset all variables used to detect circular references
NWF.FormFiller.Functions.ResetCircularReferenceRelatedVariables();

NWF.FormFiller.Functions.ProcessOnChange(NWF$(this));
});

// Attach event to trigger calucations (dynamic values) on option buttons using the "change" event
formFillerDivCurrent.on('change', 'input:checkbox', function (event) {
// Reset all variables used to detect circular references
NWF.FormFiller.Functions.ResetCircularReferenceRelatedVariables();

NWF.FormFiller.Functions.ProcessOnChange(NWF$(this));
});

// Attach event to trigger calucations (dynamic values) on multi line text boxes using the "blur" event
formFillerDivCurrent.on('input propertychange blur change', 'textarea', function (event) {
if (NWF$(this).data("OldValue") !== this.value) {
// Reset all variables used to detect circular references
NWF.FormFiller.Functions.ResetCircularReferenceRelatedVariables();

NWF.FormFiller.Functions.ProcessOnChange(NWF$(this));
}
});

// Attach event to trigger calucations (dynamic values) on drop down list using the "change" event
formFillerDivCurrent.on('change', 'select', function (event) {
// Reset all variables used to detect circular references
NWF.FormFiller.Functions.ResetCircularReferenceRelatedVariables();

NWF.FormFiller.Functions.ProcessOnChange(NWF$(this));
});
}

 

(because nobody has bothered to turn code formatting on for the forums, here is a prettier looking version that’s just an image) 

 

This is also just helpful to know because sometimes these sorts of things might change slightly in updates to the Nintex Forms platform, so it’s useful to have a reference to how things used to be done vs how they are done in an update.

 

Lastly, in the above examples for how to use JS in the Rule system I left out some auxiliary information regarding its usage. Personally I would avoid using the Validation rules to do anything regarding formatting / setting a value for a control. It’s helpful to have some sort of consistency across how the different types of rules are being used, and because they are a non-obvious place for code, it can really lead to peril if you start putting random JS everywhere. It will also make your life difficult if you update and something breaks.

 

The only thing I’m using Validation Rules for is for Validation. Albeit, an advanced form of validation because I’m using a custom means of validating with three states (pass, fail, and warning), so instead of having multiple validation rules for a given control that can fail in multiple ways, I have a single validation rule that is configured in a custom fashion which gets passed to a small custom library that will check if *any* of the tests fail, and will then act accordingly. 

 

Let me know if you have any other questions about any of this stuff. I have spent a silly amount of time digging into the Nintex made code for Forms out of necessity and might be able to save you a little time depending on what it is you’re trying to do. 

 

Till next time. 
 

 

Userlevel 1
Badge +8

Again, wow.

This stuff is like gold.  Until recently we had been using simple responsive forms, but as needs grew switched over to classic forms.

Baby steps so far for me - simple things like changing a panel’s background color to green if a certain collection of checkboxes is selected, etc. so my simplified code for a SLT field is as follows, where the JS ID is SLT and the alert would be replaced with my action:

		_spBodyOnLoadFunctionNames.push("myCustomFunctionName");
function myCustomFunctionName() {
NWF$('#'+SLT).change(function(){
var valueSLT = NWF$('#'+SLT).val();
alert(valueSLT);
});
}

Have similar samples for just about all the main types: MLT, Radio buttons, multi-checkboxes, lookups, person/people, date/time, etc.

Thank you so much for sharing your knowledge!

Userlevel 5
Badge +14

Yeah you can do a lot of really neat things with it. Here is a silly example of how one might format a multiline textbox control to react to user inputs.

 

Here is a simple example form with a few labels, choices, and a single MLT control:

 

The choice controls are setup as options (radio buttons), and are named from top to bottom as shown:

 

The Multiline Textbox has been disabled by default and has a custom classname for the control:

 

With a little custom css in the form’s settings:

 

textarea.encouragementTextarea {
background-color: black !important;
color: white !important;
font-size: 1.5rem;
}

textarea.incorrect {
background-color: red !important;
color: black !important;
font-size: 1.5rem;
}

textarea.correct {
background-color: #00ff40 !important;
color: black !important;
font-size: 1.5rem;
}

textarea.almostCorrect {
background-color: yellow !important;
color: black !important;
font-size: 1.5rem;
}

 

and a custom formatting rule on the MLT control:

 

(function(formControlCall, MC1, MC2, MC3) {
"use strict";
var formControlID = formControlCall.split("'")[1] || "";
var targetControl = sourceContext.find("[formcontrolid='" + formControlID + "'].nf-filler-control");
var targetTextarea = NWF$(targetControl.find("[formcontrolid][id]")[0]);
var answerKey = ["4", "6", "8"];
var userAnswers = [MC1, MC2, MC3];
var totalCorrect = answerKey.filter(function(answer, index){return answerKey[index] === userAnswers[index];}).length;

targetTextarea.removeClass("almostCorrect incorrect");


if (totalCorrect === answerKey.length) {
targetTextarea.removeClass("almostCorrect incorrect");
targetTextarea.addClass("correct");
targetTextarea.val("Great Job!");
} else if (totalCorrect > 0) {
targetTextarea.removeClass("correct incorrect");
targetTextarea.addClass("almostCorrect");
targetTextarea.val("Almost There! " + totalCorrect + " of " + answerKey.length + " Correct!");
} else if (totalCorrect === 0 && (MC1 || MC2 || MC3)){
targetTextarea.removeClass("almostCorrect correct");
targetTextarea.addClass("incorrect");
targetTextarea.val("No Correct Answeres :( Try Again!");
} else {
/* the user hasn't made any selections so we will encourage them */
targetTextarea.removeClass("correct almostCorrect incorrect");
targetTextarea.val("Try To Answer The Questions!");
}

return false;
}("{Control:Self}", control_MC1, control_MC2, control_MC3))

 

(Note: Replace the control references with ACTUAL Named Control references using the Formula Builder!)

 

We can make a pretty versatile little form that gives a lot of feedback without that much work: 

 

 

 

I hope you (and anyone else who might sees this) find it useful informative for future projects. 

Userlevel 1
Badge +8

Nice example!  Not being classically trained in JS, I live by example.  Thanks!

Reply