Viewing File: /usr/local/cpanel/share/libraries/cjt2/src/directives/labelSuffixDirective.js

/*
 * cpanel - share/libraries/cjt2/src/directives/labelSuffixDirective.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
 */

/* global define: false */

/**
 * Directive that shows the validation status of an input box in a form.
 *
 * Default behavior with no attributes specified:
 *   - For regular input fields: Do nothing
 *   - For required input fields: Show an asterisk when not filled out
 *
 * Required attributes
 *
 * for=...
 *   - Given an element id, associates the label-suffix with that input
 *     element. Think of this as analogous to 'for' in label itself.
 *
 * Optional attributes
 *
 * show-validation-status
 *   - Enables the display of the 3-part validation status, which includes:
 *       1. (For async validators only) A spinner to let the user know the
 *          validation is still pending.
 *       2. An X to let the user know the input is invalid.
 *       3. A check mark (√) to let the user know the input is valid.
 *
 * @example
 *
 * This directive mimics the attribute naming of labels, so you can say:
 *
 *   <label for="myField">
 *       [% locale.maketext('My Field') %]
 *       <label-suffix for="myField" show-validation-status></label-suffix>
 *   </label>
 *   <input name="myField .......
 *
 * Note that it is recommended for styling reasons, but not technically required,
 * to place the label-suffix inside of the label.
 */
define([
    "angular",
    "cjt/core",
    "cjt/util/locale",
    "cjt/directives/spinnerDirective",
],
function(angular, CJT, LOCALE) {
    var module = angular.module("cjt2.directives.labelSuffix", [
        "cjt2.templates"
    ]);
    var RELATIVE_PATH = "libraries/cjt2/directives/labelSuffix.phtml";
    module.directive("labelSuffix", ["spinnerAPI", "$timeout", function(spinnerAPI, $timeout) {
        return {
            restrict: "E", // label-suffix currently only works as a standalone element, but it could probably
            // be adapted pretty easily to fit directly as an attribute on the label itself.
            templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
            scope: {
                fieldId: "@for",
            },
            require: "^form",
            link: function(scope, element, attrs, form) {
                scope.showValidationStatus = ( typeof attrs["showValidationStatus"] !== "undefined" );

                if (!scope.fieldId) {
                    throw new Error("You must provide the 'for' attribute for label-suffix.");
                }

                /* All of this setup needs to be done in a 0 ms timeout so that it gets postponed until the
                     * next digest cycle. This allows sibling directives that create inputs to be initialized in
                     * time for us to interact with them.
                     *
                     * For example:
                     *
                     *   <label for="foo">
                     *       My Input <label-suffix for="foo"></label-suffix>
                     *   </label>
                     *   <my-input id="foo" name="foo" required></my-input>
                     *
                     * We ran into a problem like this with passwordFieldDirective, which wasn't consistently
                     * initializing the input it creates in time for this directive to check the required status.
                     */
                scope.form = form; // For use with the scope watch that starts/stops the spinner

                /* For simplicity of being able to set up the watch without necessarily being able to locate
                     * the element itself yet (if it's added by a directive that hasn't been processed yet), we're
                     * going to assume that the field name is exactly the same as its id. This may not always be
                     * the case, but it should usually be. If it turns out that this is too much of a limitation,
                     * feel free to find an alternative approach, as long as it doesn't break compatibility with
                     * directives like passwordFieldDirective that delay creation of inputs until after this one
                     * has already finished its setup. */
                scope.fieldName = scope.fieldId;

                scope.spinnerId = "validationSpinner_" + scope.fieldName;

                scope.showAsterisk = function() {
                    return scope._findInputElem()           &&
                               scope.inputElem.prop("required") &&
                               (form[scope.fieldName].$pristine || !scope.inputElem.val());
                };

                scope.isValid = function() {
                    return  scope.showValidationStatus      &&
                                scope._findInputElem()          &&
                                scope.inputElem.val()           &&
                               !form[scope.fieldName].$pristine &&
                                form[scope.fieldName].$valid    &&
                               !form[scope.fieldName].$pending;
                };

                scope.isInvalid = function() {
                    return  scope.showValidationStatus      &&
                                scope._findInputElem()          &&
                                scope.inputElem.val()           &&
                               !form[scope.fieldName].$pristine &&
                               !form[scope.fieldName].$valid    &&
                               !form[scope.fieldName].$pending;
                };

                scope.text = function(name) {
                    switch (name) {
                        case "required":
                            return LOCALE.maketext("This value is required.");
                        case "valid":
                            return LOCALE.maketext("The value you entered is valid.");
                        case "invalid":
                            return LOCALE.maketext("The value you entered is not valid.");
                        case "validating":
                            return LOCALE.maketext("Validating …");
                        default:
                            return LOCALE.maketext("An unknown problem occurred with the validation.");
                    }
                };

                scope.$watch("form." + scope.fieldName + ".$pending", function(pending) {
                    if (scope.showValidationStatus) {
                        if (pending) {
                            spinnerAPI.start(scope.spinnerId);
                        } else {
                            spinnerAPI.stop(scope.spinnerId);
                        }
                    }
                });

                scope._findInputElem = function() {
                    if (!scope.inputElem || !scope.inputElem[0]) {
                        scope.inputElem = angular.element("#" + scope.fieldId);
                    }
                    return scope.inputElem && !!scope.inputElem[0];
                };
            }
        };
    }]);
}
);
Back to Directory File Manager