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

/*
# cjt/directives/spinnerDirective.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
*/
/* global define: false */


// ------------------------------------------------------------
// Developer notes:
// ------------------------------------------------------------
// The concept for this construct was derived from:
// angular-spinner version 0.2.1
// License: MIT.
// Copyright (C) 2013, Uri Shaked.
// Sources:
// http://ngmodules.org/modules/angular-spinner
// Used with permission.
// ------------------------------------------------------------

define(
    [
        "angular",
        "lodash",
        "cjt/core",
        "cjt/util/parse",
        "cjt/templates" // NOTE: Pre-load the template cache
    ],
    function(angular, _, CJT, parse) {
        "use strict";

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

        /**
         * Service that runs the spinners in a user interface.
         *
         * @example
         *
         * Start all spinners:
         *
         * spinnerAPI.start();
         *
         * Stop all spinners:
         *
         * spinnerAPI.stop();
         *
         * Start a spinner by id:
         *
         * spinnerAPI.start("top");
         *
         * Stop a spinner by id:
         *
         * spinnerAPI.stop("top");
         *
         * Start a spinner by group name:
         *
         * spinnerAPI.stargGroup("loading");
         *
         * Stop a spinner by group name:
         *
         * spinnerAPI.stopGroup("loading");
         */

        module.factory("spinnerAPI", function() {

            /**
             * Collection of active spinners. Must register then with the semi-private
             * _add() api.
             * @type {Object}
             */
            var spinners = {};

            /**
             * Catchup queue. Actions are added here for spinners that can not be
             * found in the spinners collection and then run once those spinners appear.
             * @type {Array}
             */
            var queue = [];


            /**
             * Flush out any outstanding actions in the queue.
             *
             * @private
             * @method _flushQueue
             * @return {[type]} [description]
             */
            var _flushQueue = function() {
                queue = [];
            };

            /**
             * Make an action from the arguments
             *
             * @private
             * @_makeAction
             * @param  {String} fnName Name of the spinner method to call.
             * @param  {String} id     Optional spinner id
             * @param  {String} group  Optional spinner group name
             * @param  {Array} args    Array, usually just the parameters passed to the caller so it a psudo array.
             * @return {Object}        Packaged action object.
             */
            var _makeAction = function(fnName, id, group, args) {
                return {
                    fnName: fnName,
                    args: args,
                    id: id,
                    group: group
                };
            };

            /**
             * Runs the requested action
             *
             * @private
             * @_runAction
             * @param  {Object} action
             */
            var _runAction = function(action) {
                switch (action.fnName) {
                    case "start":
                        _start.apply(null, action.args);
                        break;
                    case "startGroup":
                        _startGroup.apply(null, action.args);
                        break;
                    case "stop":
                        _stop.apply(null, action.args);
                        break;
                    case "stopGroup":
                        _stopGroup.apply(null, action.args);
                        break;
                    case "kill":
                        _kill.apply(null, action.args);
                        break;
                    case "killGroup":
                        _killGroup.apply(null, action.args);
                        break;
                }
            };

            /**
             * Process the queue of pending catchup items
             *
             * @private
             * @_processQueue
             */
            var _processQueue = function() {
                var action;
                while ( ( action = queue.shift() ) ) {
                    if ( (action.id    && _has(action.id) ) ||
                        (action.group && _hasGroup(action.group))
                    ) {
                        _runAction(action);
                    }
                }
            };

            /**
             * Enqueue an action for future processing
             *
             * @private
             * @_enqueue
             * @param  {Object} action Action to perform
             */
            var _enqueue = function(action) {
                queue.push(action);
            };


            /**
             * Accounting helper to start a spinner.
             *
             * @method  _startSpin
             * @private
             * @param  {Spinner} spinner
             * @param  {Boolean} show
             */
            var _startSpin = function(spinner, show) {
                var className = spinner.scope.spinClass;
                if (className) {
                    spinner.element.addClass(className);
                }
                spinner.scope.display = show;
                spinner.scope.running = true;
            };

            /**
             * Accounting helper to stop a spinner.
             *
             * @method  _startSpin
             * @private
             * @param  {Spinner} spinner
             * @param  {Boolean} show
             * @param  {String} [className]
             */
            var _stopSpin = function(spinner, show) {
                var className = spinner.scope.spinClass;
                if (className) {
                    spinner.element.removeClass(className);
                }
                spinner.scope.display = show;
                spinner.scope.running = false;
            };

            /**
             * Test if the spinner is registered with the API.
             *
             * @method has
             * @param  {String}  [id] Optional Identifier for the spinner. If not passed then reports is any spinners are registered.
             * @return {Boolean}    true if the spinner exists, false otherwise.
             */
            var _has = function(id) {
                if (!id) {
                    return spinners.length > 0;
                } else {
                    return !!spinners[id];
                }
            };

            /**
             * Test if a spinner from the group is available.
             *
             * @method hasGroup
             * @param  {String} className CSS class to look for...
             * @return {Boolean}          true if the spinner exists, false otherwise.
             */
            var _hasGroup = function(className) {
                if (!className) {
                    return false;
                } else {
                    var keys = _.keys(spinners);
                    for (var i = keys.length; i > -1; i--) {
                        var key = keys[i];
                        if (key) {
                            var spinner = spinners[key];
                            if (spinner.element.hasClass(className)) {
                                return true;
                            }
                        }
                    }
                    return false;
                }
            };

            /**
             * Stop the specified spinner, if id is not passed, stops all spinners.
             *
             * @method stop
             * @param {String} id Identifier for the spinner.
             * @param {Boolean} [show] show state after the stop. Defaults to false meaning the element is hidden when stopped.
             */
            var _stop = function(id, show) {
                show = !_.isUndefined(show) ? show : false;
                if (!id) {
                    angular.forEach(spinners, function(spinner) {
                        _stopSpin(spinner, show);
                    });
                } else {
                    var spinner = spinners[id];
                    if (spinner) {
                        _stopSpin(spinner, show);
                    }
                }
            };

            /**
             * Stop the group of spinners with the designated CSS class.
             *
             * @method stopGroup
             * @param  {String} className CSS class to look for...
             * @param  {Boolean} [show] show state after the stop. Defaults to false meaning the element is hidden when stopped.
             */
            var _stopGroup = function(className, show) {
                show = !_.isUndefined(show) ? show : false;
                angular.forEach(spinners, function(spinner) {
                    if (spinner.element.hasClass(className)) {
                        _stopSpin(spinner, show);
                    }
                });
            };

            /**
             * Starts the specified spinner, if id is not passed, starts all the spinners
             *
             * @method start
             * @param  {String} id Identifier for the spinner.
             * @param  {Boolean} [show] show state after the start. Defaults to true meaning the element is visible when spinning.
             */
            var _start = function(id, show) {
                show = !_.isUndefined(show) ? show : true;
                if (!id) {
                    angular.forEach(spinners, function(spinner) {
                        _startSpin(spinner, true);
                    });
                } else {
                    var spinner = spinners[id];
                    if (spinner) {
                        _startSpin(spinner, true);
                    }
                }
            };

            /**
             * Start the group of spinners with the designated CSS class.
             *
             * @method startGroup
             * @param  {String} className CSS class to look for...
             * @param  {Boolean} [show] show state after the start. Defaults to true meaning the element is visible when spinning.
             */
            var _startGroup = function(className, show) {
                show = !_.isUndefined(show) ? show : true;
                angular.forEach(spinners, function(spinner) {
                    if (spinner.element.hasClass(className)) {
                        _startSpin(spinner, show, "fa-spin");
                    }
                });
            };

            /**
             * Kills the specified spinner, if id is not passed, kills all the spinners
             *
             * @private
             * @method _kill
             * @param  {String} id Identifier for the spinner.
             */
            var _kill = function(id) {
                var spinner;

                if (!id) {
                    var keys = _.keys(spinners);
                    for (var i = keys.length; i > -1; i--) {
                        var key = keys[i];
                        if (key) {
                            _stopSpin(spinners[key], false, "fa-spin");
                            spinners[key] = null;
                            delete spinners[key];
                        }
                    }
                } else {
                    if (id) {
                        spinner = spinners[id];
                        if (spinner) {
                            _stopSpin(spinner, false, "fa-spin");
                            spinners[id] = null;
                            delete spinners[id];
                        } else {

                            // check the queue instead
                            for (var j = queue.length - 1; j >= 0; j--) {
                                if (queue[j].id === id) {
                                    queue.splice(j, 1);
                                }
                            }
                        }
                    }
                }
            };

            /**
             * Kill a group of spinners by classname
             *
             * @private
             * @method _killGroup
             * @param  {String} className CSS class to look for...
             */
            var _killGroup = function(className) {
                var keys = _.keys(spinners);
                var found = false;
                for (var i = keys.length; i > -1; i--) {
                    var key = keys[i];
                    if (key) {
                        var spinner = spinners[key];
                        if (spinner.element.hasClass(className)) {
                            _stopSpin(spinner, false, "fa-spin");
                            spinners[key] = null;
                            delete spinners[key];
                            found = true;
                        }
                    }
                }

                if (!found) {

                    // Check the queue instead
                    for (var j = queue.length - 1; j >= 0; j--) {
                        if (queue[j].className === className) {
                            queue.splice(j, 1);
                        }
                    }
                }
            };

            return {
                spinners: spinners,

                /**
                 * Add a spinner management object to the system.
                 *
                 * @method add
                 * @protected
                 * @param {String}  id Identifier for the spinner.
                 * @param {Element} element    Wrapped element.
                 * @param {Boolean} autoStart  Start the animation if true, do not start the animation if false or undefined.
                 * @param {Boolean} show       Show state after the stop. Defaults to false meaning the element is hidden when stopped.
                 */
                _add: function(id, element, autoStart, show, scope) {
                    var spinner = spinners[id] = {
                        id: id,
                        element: element,
                        scope: scope
                    };

                    if (autoStart) {
                        _startSpin(spinner, show);
                    } else {
                        _stopSpin(spinner, show);
                    }

                    // Try to catch up now that there is a new one added
                    _processQueue();
                },

                has: _has,
                hasGroup: _hasGroup,

                /**
                 * Starts the specified spinner, if id is not passed, starts all the spinners. If id is passed, but the spinner has not
                 * been added yet, the request will be queued.
                 *
                 * @method start
                 * @param  {String} id Identifier for the spinner.
                 * @param  {Boolean} [show] show state after the start. Defaults to true meaning the element is visible when spinning.
                 */
                start: function(id, show) {
                    if (id && !_has(id)) {
                        _enqueue(_makeAction("start", id, null, arguments));
                    } else {
                        _start(id, show);
                    }
                },

                /**
                 * Start the group of spinners with the designated CSS class. If className is passed, but the spinner has not
                 * been added yet, the request will be queued.
                 *
                 * @method startGroup
                 * @param  {String} className CSS class to look for...
                 * @param  {Boolean} [show] show state after the start. Defaults to true meaning the element is visible when spinning.
                 */
                startGroup: function(className, show) {
                    if (className && !_hasGroup(className)) {
                        _enqueue(_makeAction("startGroup", null, className, arguments));
                    } else {
                        _startGroup(className, show);
                    }
                },

                /**
                 * Stop the specified spinner, if id is not passed, stops all spinners. If id is passed, but the spinner has not
                 * been added yet, the request will be queued.
                 *
                 * @method stop
                 * @param {String} id Identifier for the spinner.
                 * @param {Boolean} [show] show state after the stop. Defaults to false meaning the element is hidden when stopped.
                 */
                stop: function(id, hide) {
                    if (id && !_has(id)) {
                        _enqueue(_makeAction("stop", id, null, arguments));
                    } else {
                        _stop(id, hide);
                    }
                },

                /**
                 * Stop the group of spinners with the designated CSS class.
                 * Stop the group of spinners with the designated CSS class. If className is passed, but the spinner has not
                 * been added yet, the request will be queued.
                 *
                 * @method startGroup
                 * @param  {String} className CSS class to look for...
                 * @param  {Boolean} [show] show state after the start. Defaults to true meaning the element is visible when spinning.
                 */
                stopGroup: function(className, hide) {
                    if (className && !_hasGroup(className)) {
                        _enqueue(_makeAction("stopGroup", null, className, arguments));
                    } else {
                        _stopGroup(className, hide);
                    }
                },

                /**
                 * Kills the specified spinner, if id is not passed, kills all the spinners. If id is passed, but the spinner has not
                 * been added yet, the request will be queued.
                 *
                 * @method kill
                 * @param  {String} id Identifier for the spinner.
                 */
                kill: function(id) {
                    if (id && !_has(id)) {
                        _enqueue(_makeAction("kill", id, null, arguments));
                    } else {
                        _kill(id);
                    }
                },

                /**
                 * Kill a group of spinners by CSS class name.  If className is passed, but the spinner has not
                 * been added yet, the request will be queued.
                 *
                 * @method killGroup
                 * @param  {String} className CSS class to look for...
                 */
                killGroup: function(className) {
                    if (className && !_hasGroup(className)) {
                        _enqueue(_makeAction("killGroup", null, className, arguments));
                    } else {
                        _killGroup(className);
                    }
                },

                /**
                 * Flush out any outstanding actions in the action queue
                 *
                 * @method flushQueue
                 */
                flush: _flushQueue
            };
        });

        /**
         * Directive that generates a spinner in the user interface.
         *
         * @attribute {Object}  spinner          configuration for the spinner.
         * @attribute {Boolean} [cpAutostart] optional starts the spinner automatically if true, doesn't if false. Defaults to false.
         * @attribute {Boolean} [cpShow]      optional shows the glyph if true, doesn't if false. Defaults to false.
         * @attribute {Boolean} [groupClass]  optional css group name for use with startGroup and stopGroup API.
         * @attribute {Boolean} [glyphClass]  optional css class to define the glyph to use in the directive.
         * @attribute {Boolean} [spinClass]   optional css class to define how to start the animation.
         * @example
         * Basic spinner
         * <div spinner></div>
         *
         * Non-auto-start Spinner, invisible while not running:
         *
         * <div spinner cp-autostart="false"></div>
         *
         * Visible while not running:
         *
         * <div spinner cp-show="true"></div>
         *
         * Making two spinners part of a group:
         *
         * <div spinner group-class="loading" id="top"></div>
         * <div spinner group-class="loading" id="bottom"></div>
         *
         */
        module.directive("spinner", ["spinnerAPI",
            function(spinnerAPI) {
                var ct = 0;
                var RELATIVE_PATH = "libraries/cjt2/directives/spinner.phtml";
                return {
                    scope: true,
                    restrict: "EA",
                    replace: true,
                    controller: ["$scope",
                        function($scope) {
                            $scope.api     = spinnerAPI;
                            $scope.display = false;
                            $scope.running = false;
                        }
                    ],
                    templateUrl: CJT.config.debug ? CJT.buildFullPath(RELATIVE_PATH) : RELATIVE_PATH,
                    compile: function(element, attrs) {
                        return {
                            pre: function(scope, element, attrs) {
                                if (_.isUndefined(attrs.glyphClass)) {
                                    attrs.glyphClass = "fas fa-spinner fa-2x";
                                }

                                if (_.isUndefined(attrs.spinClass)) {
                                    attrs.spinClass = "fa-spin";
                                }

                                if (_.isUndefined(attrs.id) || attrs.id === "") {
                                    attrs.id =  "spinner_" + ct++;
                                }
                            },
                            post: function(scope, element, attrs) {
                                var show = !_.isUndefined(attrs.cpShow) ? parse.parseBoolean(attrs.cpShow) : false;
                                var autoStart = !_.isUndefined(attrs.cpAutostart) ? parse.parseBoolean(attrs.cpAutostart) : false;

                                var id = attrs.id;
                                element.attr("id", id);

                                // These are used to group spinners into groups
                                // for startGroup and stopGroup API calls. Not needed
                                // if you don't intend to use those api calls.
                                var groupClass = attrs.groupClass;
                                if (groupClass) {
                                    if (!element.hasClass(groupClass)) {
                                        element.addClass(groupClass);
                                    }
                                }

                                var glyphClass = attrs.glyphClass;
                                if (glyphClass) {
                                    if (!element.hasClass(glyphClass)) {
                                        element.addClass(glyphClass);
                                    }
                                }

                                // Setup the scope
                                scope.id        = id;
                                scope.spinClass = attrs.spinClass;


                                scope.$on("$destroy", function() {
                                    scope.api.kill(scope.id);
                                });

                                scope.api._add(scope.id, element, autoStart, show, scope);
                            }
                        };
                    }
                };
            }]);
    }
);
Back to Directory File Manager