Locking pages while waiting for long operation based on angular

Time:2021-7-2

Due to the network and other reasons, sometimes it takes a long time to execute an Ajax operation. If you don’t do special processing, you can’t know the status of the current operation, which seriously affects the user experience.

A common solution is to open a mask before performing a possible long operation, cover the page, and indicate that the current page is in execution state through animation. After getting the returned result, close the mask. Although this way can clearly show the state of waiting for the operation result, for the situation that the network conditions are better and the operation result will return soon, it will give the user a feeling of page flicker and affect the user experience.

In order to solve the above problem, consider using the directional of angular to set the elements that need to be controlled when performing long-term operations, and realize the control of page state based on elements.

There are several types of elements that need state control: 1. Input elements should be in the disabled state when long operations are performed; 2. Button and other elements that initiate the operation should be in the state of disabled, while the elements that initiate the executing long operation should be in the state of running, for example, through animation.

The realization idea is as follows:
1. Define direct, TMS lock

app = angular.module('app', []);
app.directive('tmsLock', function() {
    return {
        restrict: 'A',
        scope: {
            lock: '=tmsLock'
        },
        priority: 99,
        compile: function(tElem, tAttrs) {
            var originalFn, lockableFn;
            if (tAttrs.tmsLockPromoter === 'Y' && tAttrs.ngClick) {
                originalFn = tAttrs.ngClick;
                lockableFn = '__lockable__' + originalFn;
                tAttrs.ngClick = lockableFn;
            }
            return {
                pre: function(scope, iElem, iAttrs) {
                    if (lockableFn) {
                        scope.$parent[lockableFn.replace(/\(.*\)/, '')] = function() {
                            var eleIndicator = document.createElement('div');
                            eleIndicator.classList.add('indicator');
                            scope.lock = true;
                            iElem.addClass('tms-lock-running');
                            iElem.append(eleIndicator);
                            scope.$parent[originalFn.replace(/\(.*\)/, '')].apply(scope, arguments).then(function() {
                                scope.lock = false;
                                iElem.removeClass('tms-lock-running');
                                iElem[0].removeChild(eleIndicator);
                            });
                        };
                    }
                    scope.$watch('lock', function(locked) {
                        if (locked === true) {
                            iElem.addClass('tms-locked');
                            iAttrs.$set('disabled', true);
                        } else if (locked === false) {
                            iElem.removeClass('tms-locked');
                            iAttrs.$set('disabled', undefined);
                        }
                    });
                }
            }
        }
    }
});
app.controller('ctrl', ['$scope', '$q', '$timeout', function($scope, $q, $timeout) {
    $scope.lock = false;
    $scope.longFn = function() {
        var defer;
        defer = $q.defer();
        $timeout(function() {
            defer.resolve();
        }, 10000);
        return defer.promise;
    };
    $scope.otherFn = function() {
        // do nothing
    };
}]);

2. Sets the style of the running state

@keyframes tmsRunning{
    0%{transform:rotate(0deg);}
    12%{transform:rotate(45deg);}
    25%{transform:rotate(90deg);}
    37%{transform:rotate(135deg);}
    50%{transform:rotate(180deg);}
    62%{transform:rotate(225deg);}
    75%{transform:rotate(270deg);}
    87%{transform:rotate(315deg);}
    100%{transform:rotate(360deg);}
}
@-webkit-keyframes tmsRunning{
    0%{-webkit-transform:rotate(0deg);}
    12%{-webkit-transform:rotate(45deg);}
    25%{-webkit-transform:rotate(90deg);}
    37%{-webkit-transform:rotate(135deg);}
    50%{-webkit-transform:rotate(180deg);}
    62%{-webkit-transform:rotate(225deg);}
    75%{-webkit-transform:rotate(270deg);}
    87%{-webkit-transform:rotate(315deg);}
    100%{-webkit-transform:rotate(360deg);}
}
@-moz-keyframes tmsRunning{
    0%{-moz-transform:rotate(0deg);}
    12%{-moz-transform:rotate(45deg);}
    25%{-moz-transform:rotate(90deg);}
    37%{-moz-transform:rotate(135deg);}
    50%{-moz-transform:rotate(180deg);}
    62%{-moz-transform:rotate(225deg);}
    75%{-moz-transform:rotate(270deg);}
    87%{-moz-transform:rotate(315deg);}
    100%{-moz-transform:rotate(360deg);}
}
@-o-keyframes tmsRunning{
    0%{-o-transform:rotate(0deg);}
    12%{-o-transform:rotate(45deg);}
    25%{-o-transform:rotate(90deg);}
    37%{-o-transform:rotate(135deg);}
    50%{-o-transform:rotate(180deg);}
    62%{-o-transform:rotate(225deg);}
    75%{-o-transform:rotate(270deg);}
    87%{-o-transform:rotate(315deg);}
    100%{-o-transform:rotate(360deg);}
}
.btn.tms-lock-running {
    position: relative;
}
.btn.tms-lock-running .indicator::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 4px;
    bottom: 4px;
    width: 4px;
    margin-left: -2px;
    background: #333;
}
.btn.tms-lock-running .indicator {
    position: absolute;
    left: 50%;
    top: 50%;
    margin-left: -1em;
    margin-top: -1em;
    border-radius: 1em;
    width: 2em;
    height: 2em;
    border: 2px solid #333;
    background: #fff;
    animation: tmsRunning 1s infinite;
    -webkit-animation: tmsRunning 1s infinite;
    -moz-animation: tmsRunning 1s infinite;
    -o-animation: tmsRunning 1s infinite;
}

3. Add attributes to the elements that need to be locked

<input class='form-control' type='text' tms-lock="lock">
<button class='btn btn-default' tms-lock="lock" tms-lock-promoter="Y" ng-click="longFn()">long action</button>
<button class='btn btn-default' tms-lock="lock" ng-click="otherFn()">other action</button>

Examples

Other issues still under consideration: 1. Whether to allow users to actively unlock the page? 2. Do you need to control hard page navigation operations, such as backward?