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

/*
# cjt/directives/formWaiting.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 */

define(
    [
        "angular",
        "cjt/core"
    ],
    function(angular, CJT) {
        "use strict";

        var DEFAULT_SPINNER_SIZE = 4;

        var module = angular.module("cjt2.directives.formWaiting", []);

        var TEMPLATE_PATH = "libraries/cjt2/directives/formWaiting.phtml";

        /**
         * An attribute directive that disables and overlays a mask with
         * spinner on top of a form when the form is submitted. The
         * directive’s value is evaluated according to these rules:
         *  - If it’s boolean true, set the mask on.
         *  - If it’s a promise, set the mask on only when the promise
         *    is not yet finished.
         *  - If it’s falsey, set the mask off.
         *
         * IMPORTANT: The promise’s resolution needs to interact with
         * AngularJS. If you don’t use one of AngularJS’s own promises,
         * then you have to imitate its interaction with AngularJS’s
         * digest cycle. (maybe $apply() after a 0 $timeout?)
         *
         * Also, the <form> isn’t itself disabled; it’s actually
         * a <fieldset> element that gets inserted into the <form> and
         * wraps the given content. But there should be no functional
         * difference as long as the browser supports disabled <fieldset>.
         * This means that IE9 is not supported and that some other IE
         * versions act strangely, but visually there’s no actual breakage.
         *
         * This could be implemented by disabling the <form> rather than the
         * <fieldset>. (Maybe even getting rid of the <fieldset>?) This would
         * allow older browsers to work better, but this way seems better
         * encapsulated since we’re not altering the <form> element beyond
         * the directive’s transclusion.
         *
         * @example
         *
         * Example of how to use it:
         *
         * <form cp-form-waiting="doSubmit()">
         *      ... form elements
         * </form>
         *
         */

        module.directive("cpFormWaiting", [ "$parse", function($parse) {

            return {
                restrict: "A",

                transclude: true,

                scope: {
                    spinner_size: "@cpFormWaitingSpinnerSize",
                },

                templateUrl: CJT.config.debug ? CJT.buildFullPath(TEMPLATE_PATH) : TEMPLATE_PATH,

                link: function(scope, iElement, iAttrs, controller, transcludeFn) {
                    var _clear_promise = function _clear_promise() {
                        delete scope._show_mask;
                    };

                    if (!iAttrs.cpFormWaiting) {
                        throw "cp-form-waiting needs an expression!";
                    }

                    // Much of the below is adapted from the AngularJS
                    // source (src/ng/directive/ngEventDirs.js).

                    var fn = $parse(iAttrs.cpFormWaiting, /* interceptorFn */ null, /* expensiveChecks */ true);

                    iElement.on("submit", function(event) {
                        var promise = fn(scope.$parent, { $event: event });

                        if (promise === true) {
                            scope._show_mask = true;
                        } else if (promise) {

                            // We can’t assume this promise
                            // has a .finally() method …
                            scope._show_mask = promise;

                            // It’s possible that this promise is already
                            // completed. $q seems to do .then() on such a
                            // promise after a timeout; just in case, though,
                            // add .then() *after* we assign to scope; that way,
                            // if some engine were to do .then() in in the same
                            // execution thread, this won’t break.
                            promise.then(
                                _clear_promise,
                                _clear_promise
                            );
                        } else {
                            _clear_promise();
                        }

                        scope.$apply();
                    });
                },

                controller: [ "$scope", function($scope) {
                    if (!$scope.spinner_size) {
                        $scope.spinner_size = DEFAULT_SPINNER_SIZE;
                    }
                } ]
            };
        } ] );

        // ngTransclude will always use a sub-scope. This frustrates
        // the intent of this directive to be “seamless”; we don’t want
        // the <form>’s transcluded content to have to be “aware” that it
        // is transcluded. We use our own transclude logic so we can
        // manually set the scope to the same scope that the <form> uses.
        module.directive("cpFormWaitingTransclude", function() {
            return {
                restrict: "C",
                link: function(scope, el, attr, ctrl, transclude) {

                    // Magic incantation lifted from ngTransclude.js
                    transclude(

                        // Why we need this directive
                        scope.$parent,

                        function(clone) {
                            el.append(clone);
                        },
                        null,
                        attr.ngTransclude || attr.ngTranscludeSlot
                    );
                },
            };
        } );
    }
);
Back to Directory File Manager