I’ve been working on a project for the last year or so using Adxstudio Portals which is being upgraded to Dynamics Portals. The Portal definition includes some fairly complex Web Forms and Web Form Steps to capture data for a customized Case Entity. The data being collected ranges includes simple text values, dates, Option Sets, and related child records. The result is a multi step questionnaire with branching logic that ends with a summary screen rendering all responses for user review.

Dynamics Portals does offer methods for field validation through configuration. For example, setting form Attributes to Business Required will result in a required field on your Web Form Step page, similar to the CRM form behavior. Another option is to add regular expression Web From Metadata. We used this method to ensure a Social Security Number matches a specific pattern.

Our field validation requirements extended past the configurable options. For example, one of my first tasks was to ensure that a Date field did not allow future dates. One possible solution was to provide validation in a Pre-Create or Pre-Update plugin, throwing an exception when validation fails. Presenting the user with an exception is a less than ideal solution. Fortunately, Dynamics Portals includes the ability to extend your Web Form Steps with custom JavaScript.

Extending With JavaScript

Dynamics Portals documentation includes an entry on adding JavaScript: Add custom JavaScript. When rendering a Web Form and Web Form Step, Portals includes a common framework for form field validation that you can use.The bulk of the article is actually just a sample of how to add a custom validator to your form fields using this framework.

These validation objects are added to the page when you have configured validation on your form, such as adding a required field. When the user chooses Next on the form, the page validation methods fire and your custom validation code is invoked along with the configured validation. I like to view this as a similar model to the plugin execution framework: here is an event into which you can inject your own code and do some work. This also means custom validation error messages display exactly as the configured validation methods. So in my example of not allowing future dates, I might see validation message for a date as well as a missing value for First Name. The Portal would present the validation error messages to the end user in the same format:

This does not sound like a huge deal, but imagine if we needed to build our own method for providing validation. Some developers might use window pop ups, others bootstrap modals, while some might attempt to inject results in an unsupported way. And how would we know when to fire our validation logic? This validation framework is not hugely complex but it provides a consistent method for developers to extend the validation event and provide a consistent end user experience with form field validation.

Can I get more details?

The example in the article is relatively easy to swipe and add to your Portal project, but it does not go into a tremendous amount of detail. I’ve built several reusable validators in the past year, and here are a few things I discovered along the way.

Re-usability!

The first thing we ran into was how to manage your validator snippets to avoid copying and pasting code. The Web Form Step includes a field under Form Options labeled conveniently Custom JavaScript in which you add your script. The example in the Portals documentation immediately becomes hard to manage when you have more than one field on a form that require custom validation. This was our situation with the future date restrictions where three fields required the same validation. So instead of copying the sample code three times, I created a helper method for adding a validator to a field:

/** 
* Helper method that will initialize a new control validator for the current page
* @param {string} controlId schema name of the control being validated
* @param {string} validatorIdPrefix Prefix appended to the validator name that identifies the type, such as NotNull or FutureDate
* @param {string} failureMessageMask Message that will be displayed on error. the slug {label} will be replaced with the control label. The slug {label} will be replaced with the value field label
* @param {PromiseLike<boolean>} evalFunction function to be invoked on validation
* @param {string=} initialValue Default value for the field
* @param {string=} validationGroup Group?
* @return {void}
**/
public static addValidator(controlId: string, validatorIdPrefix: string, failureMessageMask: string, evalFunction: () => boolean, initialValue?: string, validationGroup?: string): void {

    if (typeof (Page_Validators) == 'undefined') {
        throw ("Page_Validators is undefined in this web form step");
    }
    // check default params
    if (FUTZ.utilities.isNullUndefinedEmpty(validationGroup)) {
        validationGroup = "";
    }
    if (FUTZ.utilities.isNullUndefinedEmpty(initialValue)) {
        initialValue = "";
    }

    // get control label and label text from page
    var controlLabelId: string = controlId + "_label";
    var controlLabel: string = "";

    // need to check if the control label exists. if not, we want to scroll to the control itself
    if ($("#" + controlLabelId).length == 0) {
        controlLabelId = controlId;
    }
    else {
        controlLabel = $("#" + controlLabelId).text();
    }

    //  replace the slug with the control label text
    var failureMessage = failureMessageMask.replace("{label}", controlLabel);

    var validator: any = document.createElement("span");
    validator.style.display = "none";
    validator.id = validatorIdPrefix + "_" + controlId;
    validator.controltovalidate = controlId;
    validator.errormessage = "<a href='#" + controlLabelId + "'>" + failureMessage + "</a>";
    validator.validationGroup = validationGroup; // Set this if you have set ValidationGroup on the form
    validator.setAttribute("initialvalue", initialValue);
    validator.evaluationfunction = evalFunction;

    // add the page validator and hook the error message click
    Page_Validators.push(validator);
}

This helper method simply wraps the sample script, providing some options for general use as method parameters. The parameters are self explanatory for the most part, but these are the key items:

  • controlId: the schema name of your field requiring validation
  • validatorPrefix: string that ensures validator uniqueness
  • failureMessageMask: a string to be displayed when validation fails, including a field label slug in the custom message
  • evalFunction: callback method that will perform the validation, returning false if validation fails

This simple method adds a new validator to the list of existing page validators, but it doesn’t perform any actual validation logic. An example of using this helper method for the future date validation would look something like this:

/** Injects a new control validator that restricts users from entering dates set in the future
* @param controlId schema name of the control being validated
* @return {void}
**/
public static addFutureDateValidator(controlId: string): void {
    // make sure the control exists
    if (!FUTZ.utilities.elementExists(controlId)) {
        return;
    }

    // use the common method to add the future date validation
    FUTZ.validators.addValidator(controlId, "FutureDateValidator", "{label} cannot be set to a future date.",
        () => {
            var isValid = true;
            var dateVal = $("#" + controlId).val();
            if (dateVal != null) {
                // compare time values of date objects
                var newDate = new Date(dateVal), now = new Date();
                if (newDate.getTime() > now.getTime()) {
                    isValid = false;
                }
            }
            return isValid;
        }
    );
}

This example, the bulk of the code is the callback function that will tell if the Date in the form field is in the future. If the validation fails, we return false and our error message is displayed. This new addFutureDateValidator method can be used to add the same validation to multiple Date fields on your form. For example:

FUTZ.validators.addFutureDateValidator("futz_dateofbirth"); 
FUTZ.validators.addFutureDateValidator("futz_dateofincident");

Each of these fields will be validated when the user chooses next!

Can I add a validator?

As you can see in the article and code snippets, the object that provides the hook into page validation is Page_Validators. You create a new custom validator object and add it to this collection of other configured page validators. I do a quick null check in my helper method and throw an error if this object is not found. This is because an issue that I encountered is that the Page_Validators object does not exist in the page unless you have at least one configurable validator on your form. So if you have no required fields or metadata defined validators on your form, your attempt to add a custom validator will throw an error. I actually tried creating the Page_Validators object manually, but the validators were not executed when the user selected Next. There is more to this special validator object than meets the eye! I would love to hear if someone was able to programmatically add the Page_Validators object manually.

One way around this was to add a hidden required field on your form that would always have a value, such as CreatedOn or ModifiedOn. Another option is to add a validator using metadata, such as a regular expression that always returns true. These will force Portals to make the Page_Validators object available in the page and accessible to your scripts.

How do I remove this thing?

In the examples provided, you can see that collection of validators hangs off the Page_Validators object as an Array, as indicated by the .push() when adding to the list. So what happens when I need to remove a validator?  You might hide and show entire form sections based on certain field values. For example, a section that asks for a primary point of contact information if this is someone other than the user filling out the application. The user might uncheck a field that hides a First and Last name field, but when visible, the fields are required. Because the Page_Validators is just a big array of objects, removing a validator is not as easy as grabbing your object by control Id and removing it. So this was another quick helper method that I created to get around this issue.

/**
    * Helper method that will remove a validator for a given control Id
    * @param validatorId
    */
public static removeValidator(validatorId: string):void {
    var validators: Array<any> = (<any>window).Page_Validators;

    if (validators == undefined) return;

    for (let validator of validators) {
        var id: string = validator.id;
        if (id.indexOf(validatorId) > -1) {
            (<any>Array).remove(validators, validator);
            return;
        }
    }
    FUTZ.validators.toggleFieldRequired(validatorId, false);
}

This helper will simply iterate through the validator array and match on the validator Id provided. The validator Id can either be the attribute schema name or the full control name including the validator type. If it is found, the item is removed. Helper libraries like lodash would allow easier queries, but the number of validators is usually relatively small so I just used a loop.

Final notes

On this project, we use TypeScript to manage Portal scripts because it allows for strong typing throughout our code. When you start building out your Portal scripts, you will likely include helper libraries and TypeScript files for each Portal record that uses script, such as Web Page, Web Form Step, and Web Template. Using TypeScript has been a huge help in managing all of this code and inter-dependencies such as enforcing standards across the solution, ensuring proper usage of shared code, and simple debugging while developing.

Speaking of inter-dependencies and helper libraries, you probably noticed in the snippets that I am referencing some shared objects and additional namespaces. These need to be included for the validator helper function to work correctly. I am sure there are several methods for managing scripts in Dynamics Portals but we came up with a model to minimize duplicate code and sharing of common scripts across Portal resources. Managing these scripts is another topic entirely so I plan on covering our model in a follow up post. I’ll be posting it in the next few weeks and I look forward to comments on how this follows Dynamics Portals best practices.

As always, comments, corrections (including code!), questions, and feedback are welcome!

0 Points


Leave a Reply

Your email address will not be published. Required fields are marked *