Viewing File: /usr/local/cpanel/share/libraries/cjt2/src/validator/validator-utils.js

/*
# validator-utils.js                                 Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
*/

/**
 * The module contains utility functions for validators
 *
 * @module validator-utils
 */
define([], function() {
    "use strict";

    // NOTE: Having results contain multiple messages add a lot of complication to the system since
    // the validation system already has a system for registering multiple messages. I think we should
    // refactor this to remove this complication as its us is very edge case and makes renders much more
    // complicated.

    /**
     * The results object is used by all of the custom validator to embed response messages generated
     * by complex validation logic.
     *
     * @constructor
     */
    var ValidationResult = function() {
        this.isValid = true;
        this.messages = [];
        this.lookup = {};
    };

    ValidationResult.prototype = {

        /**
         * Converts the message collection into a delimited string
         *
         * @method  toString
         * @param  {String} [delimiter] Optional delimiter, defaults to newline.
         * @return {String}             Message from the has concatenated with delimiter separations.
         */
        toString: function(delimiter) {
            delimiter = delimiter || "\n";
            var message = "";
            for (var i = 0, l = this.messages.length; i < l; i++) {
                var item = this.messages[i];
                if (item && i > 0) {
                    message += delimiter;
                }
                message += item.message;
            }
            return message;
        },

        /**
         * Checks if the message collection contains an item for the given name.
         *
         * @method hasMessage
         * @param  {String}  name Name of the message set by the validator.
         * @return {Boolean}      true if there is a message with that name, false otherwise.
         */
        hasMessage: function(name) {
            var item = this.lookup[name];
            return typeof (item) !== "undefined";
        },

        /**
         * Checks if the message collection contains any messages.
         *
         * @method hasMessages
         * @return {Boolean} [description]
         */
        hasMessages: function() {
            return this.messages.length > 0;
        },

        /**
         * Add a message for this validator
         *
         * @method add
         * @param {String} name      A short name for the validation rule that triggered the message
         * @param {String} message   The actual message text
         */
        add: function(name, message) {
            var obj = { name: name, message: message };
            this.messages.push(obj);
            this.lookup[name] = obj; // NOTE: The lookup only supports one message per name. Last message added wins.
            return this;
        },

        /**
         * Shortcut for the "add" method when the validation message results from an error.
         *
         * @method addError
         * @param  {String} name        A short name for the validation rule that triggered the error
         * @param  {String} message     A longer description of the error
         * @return {ValidationResult}   This object, for chaining
         */
        addError: function(name, message) {
            this.add(name, message);
            this.isValid = false;
            return this;
        },

        /**
         * Clear all the messages from the ValidationResult object.
         *
         * @method clear
         * @return {ValidationResult}   This object, for chaining
         */
        clear: function() {
            this.messages = [];
            this.lookup = {};
            this.isValid = true;
            return this;
        },

        /**
         * Gets all the messages or a single message by name.
         *
         * @method get
         * @param  {String} [name] Optional validator name.
         * @return {Object|Array}
         *         {String} .name - Name of the validator that placed the message
         *         {String} .message - Message output by the specific validator.
         */
        get: function(name) {
            if (typeof (name) === "string") {
                return this.lookup[name];
            } else {
                return this.messages;
            }
        }
    };

    /**
     * Attached to each ngModelDirective is an $error_details member of this type.
     * This collection contains the results for each validator attached to a ngModelDirective.
     * @constructor
     */
    var ExtendedModelReporting = function() {
        this.data = [];
        this.lookup = {};
    };

    ExtendedModelReporting.prototype = {

        /**
         * Fetch the extended validation information for a given validator
         *
         * @method get
         * @param  {String} valName Name of the validator
         * @return {ValidationResult}         ValidationResult object for the validator.
         */
        get: function(valName) {
            return this.lookup[valName];
        },

        /**
         * Set a ValidationResult for a specific validator.
         *
         * @method set
         * @param {String} valName Name of the validator
         * @param {ValidationResult} result  ValidationResult object for the validator.
         */
        set: function(valName, result) {
            this.data.push(result);
            this.lookup[valName] = result;
        },

        /**
         * Remove a result for a specific validator
         *
         * @method remove
         * @param {String} valName Name of the validator
         */
        remove: function(valName) {
            if (!this.data.length) {
                return;
            }

            var item = this.lookup[valName];

            for (var index = this.data.length - 1; index >= 0; index--) {
                if (this.data[index] === item) {
                    this.data.splice(index, 1);
                }
            }
            delete this.lookup[valName];
        },

        /**
         * Check if there are any results objects stored here.
         *
         * @method  hasResults
         * @return {Boolean} true if there are results, false otherwise.
         */
        hasResults: function() {
            return this.data.length > 0;
        },

        /**
         * Clear the data so we can recalculate
         */
        clear: function() {
            this.data = [];
            this.lookup = {};
        }
    };

    /**
     * Attached to each ngFormDirective is an $error_details member of this type.
     * This collection contains the results for each field and for each validator attached to a ngFormDirective.
     * @constructor
     */
    var ExtendedFormReporting = function() {
        this.data = {};
    };

    ExtendedFormReporting.prototype = {

        /**
         * Fetch the results for a field and validator.
         *
         * @method get
         * @param  {String} fieldName Name of the field.
         * @param  {String} valName   Name of the validator.
         * @return {ValidationResult}           ValidationResult object for the validator.
         */
        get: function(fieldName, valName) {
            var field = this.data[fieldName] || new ExtendedModelReporting();
            if (valName) {
                return field.get(valName);
            } else {
                return field;
            }
        },

        /**
         * Set the results for a field and validator.
         *
         * @method set
         * @param  {String} fieldName Name of the field.
         * @param  {String} valName   Name of the validator.
         * @param  {ValidationResult}           ValidationResult object for the validator.
         */
        set: function(fieldName, valName, result) {
            this.data[fieldName] = this.data[fieldName] || new ExtendedModelReporting();
            this.data[fieldName].set(valName, result);
            return this;
        },

        /**
         * Remove the results for a field and validator.
         *
         * @method remove
         * @param  {String} fieldName Name of the field.
         * @param  {String} valName   Name of the validator.
         */
        remove: function(fieldName, valName) {
            if (this.data[fieldName]) {
                this.data[fieldName].remove(valName);
                if (!this.data[fieldName].hasResults()) {
                    this.data[fieldName] = null;
                    delete this.data[fieldName];
                }
            }
        }
    };

    return {

        // Unit Testing Only
        ValidationResult: ValidationResult,
        ExtendedModelReporting: ExtendedModelReporting,
        ExtendedFormReporting: ExtendedFormReporting,

        // Public API

        /**
         * Helper method to create a result structure in a uniform way.
         * @param {Boolean} [clear] Optional, clear the same item if is true so it does not accumulate messages.
         *                          Otherwise accumulate the messages.
         * @return {ValidationResult}
         *             {Boolean} .isValid  - true if the results represents a valid value, false otherwise.
         *             {Object}  .messages - Hash of key/message pairs
         */
        initializeValidationResult: function(clear) {
            var result = new ValidationResult();
            if (clear) {
                result.clear = true;
            }
            return result;
        },

        /**
         * Initialize the extended error reporting objects.
         * @param  {ModelController} ctrl  Model controller managing the data.
         * @param  {FormController} [form] Optional form controller.
         */
        initializeExtendedReporting: function(ctrl, form) {
            ctrl.$error_details = new ExtendedModelReporting();
            if (form) {
                form.$error_details = new ExtendedFormReporting();
            } else {
                if (window.console) {
                    window.console.log("To participate in extended form validation you must have a ngForm or form around your controls with custom validation.");
                }
            }
        },

        /**
         * Update a collection of possible error messages. This is use for multi-error aggregate validators.
         *
         * @param  {ModelController} ctrl  Model controller managing the data.
         * @param  {FormController} [form] Optional form controller.
         * @param  {String[]} names - List of validator names to check.
         * @param  {ValidationResult} multiResult
         */
        updateExtendedReportingList: function(ctrl, form, names, multiResult) {
            names.forEach(function(name) {
                var error = multiResult.lookup[name];
                if (error) {
                    var result = new ValidationResult();
                    result.add(error.name, error.message);
                    this.updateExtendedReporting(multiResult.isValid, ctrl, form, name, result);
                }
            }, this);
        },

        /**
         * Updates the extended reporting based on the validity of the
         * value for a specific field validator.
         *
         * @param  {Booelan} valid  true if the model is valid, false otherwise.
         * @param  {ModelController} ctrl  Model controller managing the data.
         * @param  {FormController} [form] Optional form controller.
         * @param  {String} name
         * @param  {ValidationResult} result
         */
        updateExtendedReporting: function(valid, ctrl, form, name, result) {
            if (!valid) {
                if (result.clear) {
                    ctrl.$error_details.remove(name);
                    if (form) {
                        form.$error_details.remove(ctrl.$name, name);
                    }
                }
                ctrl.$error_details.set(name, result);
                if (form) {
                    form.$error_details.set(ctrl.$name, name, result);
                }
            } else {
                ctrl.$error_details.remove(name);
                if (form) {
                    form.$error_details.remove(ctrl.$name, name);
                }
            }
        }
    };
});
Back to Directory File Manager