由于网络等各方面原因,有时执行一个ajax操作时有时需要等待比较长的时间,如果不进行特殊处理就无法知道当前操作的状态,严重影响用户体验。

比较常见的解决方案是执行可能的长操作前先打开一个蒙版,覆盖页面,通过动画指示当前页面处于执行状态,获得返回结果后在关闭蒙版。这种方式虽然可以很清晰的展现正在等待操作结果的状态,但是对于网络条件比较好,操作结果很快就返回的情况,会给用户造成一种页面闪烁的感觉,也会影响用户体验。

为了解决上面的问题,考虑利用angular的directive对执行长操作时需要进行控制的元素进行设置,实现以元素为单位进行页面状态的控制。

需要进行状态控制的元素有几类:1、input元素,执行长操作时它们应该处于disabled状态;2、button等发起操作的元素,它们应该出于disabled的状态,同时发起正在执行的长操作的元素应该出于running的状态,例如:通过动画。

实现思路如下:
1、定义directive,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、设置运行状态的样式

@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、给需要锁定的元素添加属性

<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>

示例

其他仍在考虑的问题:1、是否允许用户主动解除页面锁定状态?2、是否需要对硬的页面导航操作,例如:后退,进行控制?


游于藩篱
576 声望15 粉丝

研究产品和技术,实践编程技巧。