cancel
Showing results for 
Search instead for 
Did you mean: 
danyale
Nintex Newbie

Runtime Validation on a input control only fires in the first row of the repeating section of responsive form?

Jump to solution

0 Kudos
Reply
10 Replies
Automation Master
Automation Master

Re: Runtime Validation on a input control only fires in the first row of the repeating section of responsive form?

Jump to solution

This problem has existed for some time and, as of the version I'm running (4.3.0.1 in SharePoint 2016), has yet to be fixed. 

That being said, to solve this in Classic Form mode is a trivial thing. You simply need to add the following code to your Custome Javascript editor in the Form Settings: 

NWF.FormFiller.Events.RegisterRepeaterRowAdded(function() {
  ValidatorOnLoad();
});‍‍‍

Super easy! 

Responsive Forms on the other hand... well... that's an entirely different ballgame. Because the ability to easily add javascript to the form consistently has been mostly removed, we have to resort to hackish methods. There used to be a workaround with the Rich Text Editor (see: Unable to find the Custom Javascript text area in Nintex Responsive forms), but in the version I'm using that control is no longer accessible from the editor

Though not all is lost. The Calculated Control can be used as a means of running custom JavaScript, and can be set to run in every instance of the form load. It's just not ideal :-/ 

To get this up and running, throw a Calculated Control, a Repeating Section, and (inside of the Repeating Section) a Single Line Text Control onto your form. 

This what my test form looks like: 

I have a Validation Rule on the Single Line Text control (titled "Control To Check") that will invalidate the control if the text inside of it equals either the word "cheese" or "pizza". The rule looks like: 

The validation code (for copying and pasting) is: 

trim({Control:Self}.toLowerCase()).match(/^cheese|^pizza/gi) !== null

The Validation Message simply reads: This field cannot start with "cheese" or "pizza"!

The setup for the Calculated Control is fairly simple:


Make sure that you have all of the Recalculation Modes set to Yes, and give it a cool title so that your co-workers respect you. Also! Add a Rule to the Calculated Control that automatically hides it when the form loads like: 

(DO NOT USE THE CONTROL'S APPEARANCE SETTINGS TO HIDE THE CONTROL OR THE CONTROL WILL NOT EVALUATE ITS FORMULA!) 

The most difficult part of all of this is the formula you need to add to the control. 

The code for that is as follows: 

(function(jsLibraries) {

  /*

    This code was lifted directly from:
      http://www.javascriptkit.com/javatutors/loadjavascriptcss.shtml

  */
 
  var loadjscssfile = function(filename, filetype) {
    if (filetype == "js") {

      var fileref = document.createElement('script');
      fileref.setAttribute("type", "text/javascript");
      fileref.setAttribute("src", filename);
    } else if (filetype == "css") {
      var fileref = document.createElement("link");
      fileref.setAttribute("rel", "stylesheet");
      fileref.setAttribute("type", "text/css");
      fileref.setAttribute("href", filename);
    }
    if (typeof fileref != "undefined") {
      document.getElementsByTagName("head")[0].appendChild(fileref);
    }
  };

  /*
    The Following Functions:

      registerHighlightControlOnChangeError
      highlightControlOnChangeError
      clearControlError
      validateControlsOnChange

    are the property of Nintex, and are simply being reused / exposed here.
    You can also invoke these functions by running the exposed FormFiller
    Function:

    NWF.FormFiller.Functions.FillerPreReadyProcessing(formfillerDiv);

    However, doing so will scroll the page to (0,0) and while you can
    capture the position of the of the screen beforehand,
    and simply set it back, it causes the view to jump,
    which could be jarring for users.

  */


  var registerHighlightControlOnChangeError = function() {
    if (typeof(Page_Validators) != 'undefined') {
      NWF$.each(Page_Validators, function() {
        var controlKey = '#' + this.controltovalidate;
        var currentControl = NWF$(controlKey);
        var validators = ControlValidatorsMap[controlKey];
        if (validators === undefined || validators === null) {
          validators = [];
          validators.push(this);
          ControlValidatorsMap[controlKey] = validators;
          currentControl.change(function() {
            validateControlsOnChange(controlKey);
          });
          currentControl.on('keypress', function(event) {
            if (event.keyCode == '13' && event.key == 'Enter') {
              validateControlsOnChange(controlKey);
            }
          });
        } else {
          validators.push(this);
        }
      });
    }
  };

  var highlightControlOnChangeError = function(control) {
    if (!control.hasClass('nf-validation-error')) {
      control.addClass('nf-validation-error');
    }
  };

  var clearControlError = function(control) {
    control.removeClass('nf-validation-error');
    control.attr("validation-error", null);
  };

  var validateControlsOnChange = function(controlKey) {
    var currentControl = NWF$(controlKey);
    var controlToHighlight = currentControl.closest('.nf-col');
    clearControlError(controlToHighlight);
    var validators = ControlValidatorsMap[controlKey];
    var isValidControl = true;
    for (var i = 0; i < validators.length; i++) {
      ValidatorValidate(validators[i]);
      if (!validators[i].isvalid) {
        isValidControl = false;
        break;
      }
    }
    if (!isValidControl) {
      highlightControlOnChangeError(controlToHighlight);
    }
  };


  /*
    If you passed in any http paths to external javascript or css libraries
    This is where they would be evaluated and (hopefully) loaded.
  */

  jsLibraries.forEach(function(targetFile) {
    if (targetFile) {
      loadjscssfile(targetFile, (targetFile.match(/[^\.]+$/gi) !== null) ? targetFile.match(/[^\.]+$/gi)[0] : "");
    }
  });


  /*
    Hey look! I finally wrote some of my OWN code!
  */


  NWF.FormFiller.Events.RegisterRepeaterRowAdded(function() {

    var currentRow = arguments[0];
    var currentRowValidators = arguments[0].find(".nf_rulesvalidators:not" + "(.nf-hiddenrepeaterrow)");
    var hiddenRow = currentRow.siblings(".nf-repeater-row-hidden");
    var hiddenRowValidators = hiddenRow.find(".nf_rulesvalidators");

    currentRowValidators.each(function(index, currentRowValidator) {

      var validatorFunctionName = currentRowValidator.functionName;
      var parentValidator = hiddenRowValidators.filter(function(index, hiddenRowValidator) {
        return hiddenRowValidator.functionName === validatorFunctionName;
      });

      if (parentValidator.length === 1 && currentRowValidator.display !== parentValidator[0].display) {
        currentRowValidator.display = parentValidator[0].display;
        if (currentRowValidator.style.visibility !== "") {
          currentRowValidator.style.visibility = "";
        }

        var targetValidatingControlContainer = NWF$("#" + currentRowValidator.controltovalidate).closest(".nf-filler-control");
        var controlValidationContainer = targetValidatingControlContainer.find(".nf_rulesvalidators").parent();

        NWF$(currentRowValidator).appendTo(controlValidationContainer);
      }

    });
    registerHighlightControlOnChangeError();

  });

  return "";

}([]));

/*

  If you needed to load an external css or js file
  into the form, you can do so by putting the path(s) (as a string)
  into the above empty array.

  Each file should be at a different index

  ex:

  ...}(["myCoolJavascript.js", "myGreatCss.css"]));

  If you don't have anything to load... don't worry about it!

*/
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Paste that into the Formula section of the Calculated Control, and whenever a new row is added, things should work as expected, as shown: 

Neat! 

Explanation / Learning Stuff - For The Curious


How does this work / what does it do?

First let's review what a Validation Rule actually is. 

Every time you create a validation rule on a control, there is a <span> element that is generated as a 'bridge' between the Control in question and the Validation Rule proper. These span elements contain all of the data needed by the Validation system to determine everything about how the control should be validated. It stores the location of the validation code that you actually place into your Rule's formula, as well as other bits of information such as the target control to style, and even the 'type' of validation. 

Importantly, for the Responsive Forms, it also contains custom properties that dictate whether or not the validator is currently in a visible or hidden state, as well as they way that the validator should be rendered once it reaches a state of invalidation. You can see every available Validation Rule available on your form by looking at the contents of the Page_Validators variable. 

(For additional resources on the Rules System, see my blog: Breaking The Rules: A Dive Into The Nintex Forms Rule System)

Additionally, it's also important to understand what is happening when you click on the 'Add New Row' button. 

Upon adding a new row to a Repeating Section, the topmost and always hidden row that is generated when the form is created (as a kinda of 'clean' root), is copied using the jQuery.clone(true, true) function. This creates a deep clone of all of the available nodes, however and critically, this leaves out any custom properties that exist on a node! This means that as it starts to evaluate the Rule Validators on the controls it's copying, it doesn't bother to ever set the custom properties to what they are on their Hidden Row counterparts. Instead it just leaves them as is without any of the stylistic customer properties that would allow them to show up if the control is invalidated! 

Additionally, instead of placing them inside of the Control's <div> container (note: A Control's 'container' will always have the class ".nf-filler-control"), it places them inside of the Repeater Row's Container!!!! This means that even if they were rendered, they wouldn't render in the correct place and if you had multiple invalidation messages, they would just be stacked atop one another at the bottom of the Repeater Row! 

Last but not least. The way that the Responsive Form does error highlighting is by using a public object called ControlValidatorsMap. It contains a reference to all of the controls that can be styled as Invalid, and is updated whenever the form is first being generated or whenever the form is being submitted. Being that we'd like the Invalidation styling (that is - the red border that is put onto any control that is invalid), we'd need to get any newly created control along with its Validation Rules, and place it into the ControlValidatorsMap object. 

The above code accomplishes all of those things. Specifically, this section: 

NWF.FormFiller.Events.RegisterRepeaterRowAdded(function() {

    var currentRow = arguments[0];
    var currentRowValidators = arguments[0].find(".nf_rulesvalidators:not" + "(.nf-hiddenrepeaterrow)");
    var hiddenRow = currentRow.siblings(".nf-repeater-row-hidden");
    var hiddenRowValidators = hiddenRow.find(".nf_rulesvalidators");

    currentRowValidators.each(function(index, currentRowValidator) {

      var validatorFunctionName = currentRowValidator.functionName;
      var parentValidator = hiddenRowValidators.filter(function(index, hiddenRowValidator) {
        return hiddenRowValidator.functionName === validatorFunctionName;
      });

      if (parentValidator.length === 1 && currentRowValidator.display !== parentValidator[0].display) {
        currentRowValidator.display = parentValidator[0].display;
        if (currentRowValidator.style.visibility !== "") {
          currentRowValidator.style.visibility = "";
        }

        var targetValidatingControlContainer = NWF$("#" + currentRowValidator.controltovalidate).closest(".nf-filler-control");
        var controlValidationContainer = targetValidatingControlContainer.find(".nf_rulesvalidators").parent();

        NWF$(currentRowValidator).appendTo(controlValidationContainer);
      }

    });
    registerHighlightControlOnChangeError();

  });

Here is the same section with comments showing how all of what I have talked about is working:

  // Every time we Add a New Row, we want to run the code contained in here:
  NWF.FormFiller.Events.RegisterRepeaterRowAdded(function() {

    // Set the variable 'currentRow' to the New Row that we've created
    var currentRow = arguments[0];

    // Get all of the newly created Validation Rule <span>s for the Current Row
    var currentRowValidators = arguments[0].find(".nf_rulesvalidators:not" + "(.nf-hiddenrepeaterrow)");

    // Set a reference to the Hidden 'root' Row of the Repeating Section
    var hiddenRow = currentRow.siblings(".nf-repeater-row-hidden");

    // Get all of the Validation Rules inside of that Hidden 'root' Row
    var hiddenRowValidators = hiddenRow.find(".nf_rulesvalidators");

    // Going through every Validation Rule in our New Row
    currentRowValidators.each(function(index, currentRowValidator) {

      // Get the Function Name that the Validator is storing.
      var validatorFunctionName = currentRowValidator.functionName;

      // See if we can find a Validator in the Hidden Row that has a matching
      // 'functionName' property and return it!
      var parentValidator = hiddenRowValidators.filter(function(index, hiddenRowValidator) {
        return hiddenRowValidator.functionName === validatorFunctionName;
      });

      // There should only be (1) Validation Rule per Validation Function (as far as I'm aware),
      // but I want to make sure that's the case, so I'm specifying that there should only be (1)

      // Assuming there was only (1) Validator returned, and that the value of the custom property 'display'
      // doesn't equal the value of the custom property 'display' on our New Row's Validator, then we
      // need to fix that!
      if (parentValidator.length === 1 && currentRowValidator.display !== parentValidator[0].display) {

        // Set the New Validator's .display property to that of the Root one.
        currentRowValidator.display = parentValidator[0].display;

        // Additionally, if the New Validator has its .style.visibility property set to
        // anything other than "", update it to "";
        if (currentRowValidator.style.visibility !== "") {
          currentRowValidator.style.visibility = "";
        }

        // Then all we need to do is to get a reference to the Control Container that our New Validator
        // should be associated with.
        var targetValidatingControlContainer = NWF$("#" + currentRowValidator.controltovalidate).closest(".nf-filler-control");

        // And then the Container that the existing (unused / dead) Validator is inside of...
        var controlValidationContainer = targetValidatingControlContainer.find(".nf_rulesvalidators").parent();

        // And MOVE our New Validator to inside of that Container!
        NWF$(currentRowValidator).appendTo(controlValidationContainer);
      }

    });

    // Now that we have moved ALL of the New Validators, we can add all of their controls and Rules
    // to the ControlsValidatorsMap object.

    // This particular function is borrowed directly from Nintex's code (as I have commented in the previous
    // code block.)
    registerHighlightControlOnChangeError();

  });

Outro / And Slight Complaining / Ranting 

I hope that this helps you accomplish what you're trying to. I learned a lot of new junk about how Validation works in the Responsive Forms (something that I have been putting off because I don't use them... yet), but also kinda feel a way now that I have figured stuff out. 


I feel like one of the biggest challenges we all face when trying to use Nintex in production is when something like this just doesn't work. And what are we to do? In Classic Forms, even though it could still be a pain, having an easy way to include custom JavaScript made it reasonably easy to fix stuff like this on the go without too much effort, and without WAITING for the busy Nintex devs to come to your rescue. Responsive Forms is a really nice looking update to the product, but it seems like even more of a danger to use when the lengths to fix anything on your own comes with the cost of using extremely hacky methods / work-arounds. 

Additionally, with a new even more restricted and locked down Rule System on the horizon, I worry that even things like this will eventually be deprecated in favor of 'friendlier' interfaces, leaving us with zero ways to fix stuff or customize things beyond what is 'desirable'. 

I really do wish that they would consider adding in some type of Developer Mode that would allow people to do these thing without finding dumb work arounds, while at the same time giving them a way to clearly see, from a Support perspective, whether or not there are 'unsupported' enhancements made to the forms environment when trouble shooting. 

Until then we'll just have to keep on hackin! 

The End! 

View solution in original post

Reply
Automation Master
Automation Master

Re: Runtime Validation on a input control only fires in the first row of the repeating section of responsive form?

Jump to solution

Did you resolve this issue at all? 

Reply
danyale
Nintex Newbie

Re: Runtime Validation on a input control only fires in the first row of the repeating section of responsive form?

Jump to solution

Bundle & Bundle of Thanks for your such a simple and detailed response. Since, we were very annoyed by the limited and irritated behaviour of responsive forms and not being able to use javascript. When I showed your solution to my Boss he hardly controlled his joy. As soon I impliment your solution, I will mark this as Correct.

0 Kudos
Reply
Automation Master
Automation Master

Re: Runtime Validation on a input control only fires in the first row of the repeating section of responsive form?

Jump to solution

Excellent! I really do hope that it works. It was a great oppertunity to learn a lot more about the mysteries of the Repeating Section and how validation / validators are used in the new form system. 

I haven't switched over to them yet, but knowing this stuff will hopefully make the change a lot easier. 

0 Kudos
Reply
danyale
Nintex Newbie

Re: Runtime Validation on a input control only fires in the first row of the repeating section of responsive form?

Jump to solution

Works like Charm....

0 Kudos
Reply
Automation Master
Automation Master

Re: Runtime Validation on a input control only fires in the first row of the repeating section of responsive form?

Jump to solution

That is very good to hear  

Glad that it worked! 

0 Kudos
Reply
danyale
Nintex Newbie

Re: Runtime Validation on a input control only fires in the first row of the repeating section of responsive form?

Jump to solution

Thanx again for your prompt help, I was wondering that you could provide any hint that validations only implemented through ADD RULE button are registered and showing in every added row, however, validations enforce using the Control properties like regular expression are not showing any error message.

0 Kudos
Reply
Highlighted
Automation Master
Automation Master

Re: Runtime Validation on a input control only fires in the first row of the repeating section of responsive form?

Jump to solution

I can't get deep into it for a while, but I can tell you that by changing only a few lines, it will *kinda* get you to where you're going. 

NWF.FormFiller.Events.RegisterRepeaterRowAdded(function() {
    var currentRow = arguments[0];
    var currentRowValidators = arguments[0].find(".nf-validator-error:not" + "(.nf-hiddenrepeaterrow)");
    var hiddenRow = currentRow.siblings(".nf-repeater-row-hidden");
    var hiddenRowValidators = hiddenRow.find(".nf-validator-error");

    currentRowValidators.each(function(index, currentRowValidator) {

      var validatorFunctionName = currentRowValidator.functionName;
      var parentValidator = hiddenRowValidators.filter(function(index, hiddenRowValidator) {
        return hiddenRowValidator.functionName === validatorFunctionName;
      });

      if (parentValidator.length === 1 && currentRowValidator.display !== parentValidator[0].display) {
        currentRowValidator.display = parentValidator[0].display;

        if (currentRowValidator.style.visibility !== "") {
          currentRowValidator.style.visibility = "";
        }

        var targetValidatingControlContainer = NWF$("#" + currentRowValidator.controltovalidate).closest(".nf-filler-control");
        var controlValidationContainer = targetValidatingControlContainer.find(".nf-validator-error").parent();
        NWF$(currentRowValidator).appendTo(controlValidationContainer);
      }
    });
    registerHighlightControlOnChangeError();
  });

All this does is targets the ".nf-validator-error" class instead of the ".nf_rulesvalidators" class (which is only applied to Custom Rules that have been added via the Add Rule button. 

This actually will place all of your errors below the control, BUT, the catch now is that if you error out the control in one way (let's say, I trigger a Custom Validation Rule), if you then trigger a 'Properties' Rule (like regex), you will render BOTH errors! 

That being said, you might be able to feel your way through it and solve this on your own. If you do, I encourage you to share that here on the forums. If I have some more time in the near future, I will revisit this. 

*NOTE: My error message is misleading as it's actually .{2,100} or "at least 2 ~ 100 characters long". Whoops! 
0 Kudos
Reply
danyale
Nintex Newbie

Re: Runtime Validation on a input control only fires in the first row of the repeating section of responsive form?

Jump to solution

How can I enforce regex through custom rules, so there is no need to defined it as Property Rule. I tried using js code ie. (new RegExp(pattern)).exec('string')), but no success. 

0 Kudos
Reply