Viewing File: /usr/local/cpanel/base/frontend/jupiter/user_manager/directives/validateUsernameWithDomain.js

/*
# base/frontend/jupiter/user_manager/directives/validateUsernameWithDomain.js
#                                                 Copyright(c) 2020 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
*/

define(
    [
        "angular",
        "cjt/util/locale",
        "cjt/validator/validator-utils",
        "cjt/validator/validateDirectiveFactory",
        "app/services/userService",
    ],
    function(angular, LOCALE, validatorUtils, validatorFactory, userService) {
        "use strict";
        var module = angular.module("App");

        /**
         * This set of directives is intended to help with the problem of length
         * validation for username@domain entry across two fields. In the product we
         * often have one field for username and another for the domain selection. As
         * of 11.54, we are imposing character limitations for the combined result of
         * these two fields, including the @ character. This directive automates that
         * validation.
         *
         * @example
         *
         * <form username-with-domain-wrapper>
         *     <input name="username" ng-model="username" username-with-domain="username">
         *     <input name="domain"   ng-model="domain"   username-with-domain="domain">
         *     <ul validation-container field-name="username"></ul>
         * </form>
         *
         * Note: Both the wrapper and child directives are restricted to attributes.
         */


        /**
         * The wrapper directive just serves as a communication point between the two
         * child directives.
         */
        module.directive("usernameWithDomainWrapper", [function() {

            var ParentController = function($attrs) {
                this.username = this.domain = "";
                this.$attrs = $attrs;
            };

            angular.extend(ParentController.prototype, {
                setDomain: function(domain) {
                    if (typeof domain !== "undefined") {
                        this.domain = domain;
                    }

                    return this.getTotalLength();
                },

                setUsername: function(username) {
                    if (typeof username !== "undefined") {
                        this.username = username;
                    }

                    return this.getTotalLength();
                },

                getUsernameAndDomain: function() {
                    return this.username + "@" + this.domain;
                },

                getTotalLength: function() {
                    return this.getUsernameAndDomain().length;
                },
            });

            return {
                restrict: "A",
                scope: false,
                controller: ["$attrs", ParentController],
            };

        }]);

        /**
         * This directive will need two instances to function as intended, and they
         * should both be descendants of the wrapper directive. One should have the
         * attribute value of "username" and the other value should be "domain".
         */
        module.directive("usernameWithDomain", ["userService", "$q", function(userService, $q) {

            return {
                restrict: "A",
                scope: false,
                require: ["^^usernameWithDomainWrapper", "ngModel"],
                link: function( scope, elem, attrs, ctrls ) {
                    var parentCtrl = ctrls[0]; // The controller from the wrapper directive
                    var ngModel    = ctrls[1]; // The ngModel controller from the current element

                    // Grab the type
                    var type = attrs.usernameWithDomain;

                    if (type === "username") {

                        // Save a reference to the $validate function on the wrapper so that the partner "domain"
                        // version of this directive can trigger validation for this "username" instance.
                        parentCtrl.validateUsername = ngModel.$validate;

                        // Set up the extended validation object the same way the validateDirectiveFactory does.
                        var formCtrl = elem.controller("form");
                        validatorUtils.initializeExtendedReporting(ngModel, formCtrl);

                        // This is the main validation function that checks the total length of the username@domain.
                        var validateUsernameWithDoamin = function(totalLength) {
                            var TOTAL_MAX_LENGTH = 254;
                            var result = validatorUtils.initializeValidationResult();

                            if (totalLength > TOTAL_MAX_LENGTH) {
                                result.addError("maxLength", LOCALE.maketext("The combined length of the username, [asis,@] character, and domain cannot exceed [numf,_1] characters.", TOTAL_MAX_LENGTH));
                            }

                            return result;
                        };

                        // Add the validator to the list. The validator goes through the validateDirectiveFactory
                        // "run" method to hopefully help compatibility going forward.
                        ngModel.$validators.usernameWithDomain = function(newUsername) {
                            var totalLength = parentCtrl.setUsername(newUsername);
                            return validatorFactory.run("usernameWithDomain", ngModel, formCtrl, validateUsernameWithDoamin, totalLength);
                        };

                        var validateUsernameIsAvailableAsync = function(value) {
                            return userService.checkAccountConflicts(value).then(function(responseData) {
                                scope.$eval(parentCtrl.$attrs.lookupCallback, { responseData: responseData });
                                return responseData;
                            }).then(
                                function() {
                                    return validatorUtils.initializeValidationResult();
                                },
                                function(error) {
                                    var result = validatorUtils.initializeValidationResult(true);
                                    result.addError("usernameIsAvailable", error);
                                    return result;
                                });
                        };

                        ngModel.$asyncValidators.usernameIsAvailable = function(modelValue, viewValue) {
                            var value = parentCtrl.getUsernameAndDomain();
                            return validatorFactory.runAsync($q, "usernameIsAvailable", ngModel, formCtrl, validateUsernameIsAvailableAsync, value);
                        };

                    } else if (type === "domain") {

                        // Unfortunately the viewChangeListeners array doesn't get triggered when you first set
                        // the model value (for whatever reason), so we'll need to set the domain to cover the
                        // case when the user doesn't change the default. $formatters don't get called for select
                        // controls when their value changes so this only fires on the initial render.
                        ngModel.$formatters.push(function(val) {
                            parentCtrl.setDomain( ngModel.$modelValue );
                            return val;
                        });

                        // When the domain model changes, we need to run the length check again, but the username
                        // is where people have the most flexibility to make changes, so we'll run the validation
                        // there to create the validation error messages near that field.
                        ngModel.$viewChangeListeners.push(function() {
                            parentCtrl.setDomain( ngModel.$modelValue );
                            parentCtrl.validateUsername();
                        });
                    } else {
                        throw new Error("The value for the username-with-domain directive needs to be set to 'username' or 'domain'.");
                    }

                },
            };

        }]);

    }
);
Back to Directory File Manager