防止/处理角度的双击按钮

时间:2013-08-08 16:05:16

标签: angularjs

在角度,我们可以设置一个按钮,在视图中发送像这样的ajax请求:

... ng-click="button-click"

并在控制器中:

...
$scope.buttonClicked = function() {
   ...
   ...
   // make ajax request 
   ...
   ...
}

因此,为了防止双重提交,我可以在单击按钮时将buttonclicked = true设置为true,并在ajax回调完成时取消设置。但是,即使这样,控制也会被处理回角色,谁将更新到Dom。这意味着在原始按钮单击完全100%完成之前,有一个小窗口可以再次单击该按钮。

这是一个小窗口,但仍然可以发生。完全避免这种情况发生的任何提示 - 客户端,即不对服务器进行任何更新。

由于

13 个答案:

答案 0 :(得分:39)

首先,您最好添加ngDblclick,当它检测到双击时只返回false

<ANY ng-click="buttonClicked()" ng-dblclick="return false">

如果您想等待Ajax调用完成,则可以通过设置禁用ng来禁用该按钮

<ANY ng-click="buttonClicked()" ng-dblclick="return false;" ng-disabled="flag">

在你的控制器中,你可以做到

$scope.flag = false;
$scope.buttonClicked = function() {
    $scope.flag = true;
    Service.doService.then(function(){
        //this is the callback for success
        $scope.flag = false;
    }).error(function(){
        //this is the callback for the error
        $scope.flag = false;
    })
}

当ajax调用成功或失败时,您需要同时处理这两种情况,因为如果它失败了,您不希望它显示为带有混乱的用户。

答案 1 :(得分:33)

使用ng-disabledexample中运行得很好。无论我多么激烈地点击控制台消息只填充一次。

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.submitData = function() {
    $scope.buttonDisabled = true;
    console.log("button clicked");
  }

  function augment() {
    var name, fn;
    for (name in $scope) {
      fn = $scope[name];
      if (typeof fn === 'function') {
        if (name.indexOf("$") !== -1) {
          $scope[name] = (function(name, fn) {
            var args = arguments;
            return function() {
              console.log("calling " + name);
              console.time(name);
              fn.apply(this, arguments);
              console.timeEnd(name);
            }
          })(name, fn);
        }
      }
    }
  }

  augment();
});
<!doctype html>
<html ng-app="plunker">

<head>
  <meta charset="utf-8">
  <title>AngularJS Plunker</title>
  <link rel="stylesheet" href="style.css">
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js"></script>
  <script src="app.js"></script>
</head>

<body ng-controller="MainCtrl">
  <input type="button" ng-click="submitData()" ng-disabled="buttonDisabled" value="Submit" />
</body>

</html>

我很好奇将角度应用于buttonDisabled标志需要多长时间。如果您在plunker example中检查控制台,则会显示执行$eval$apply方法所需的时间。在我的机器上,它平均需要1-2毫秒。

答案 2 :(得分:11)

我只是扩展了zsong的代码,在标志的处理程序中添加一个检查。如果它是真的那么只返回因为已经处理了一个点击。这可以防止双击而不用担心角度定时或类似的事情。

$scope.flag = false;
$scope.buttonClicked = function() {
    if ($scope.flag) {
        return;
    }
    $scope.flag = true;
    Service.doService.then(function(){
        //this is the callback for success
        $scope.flag = false;
    }).error(function(){
        //this is the callback for the error
        $scope.flag = false;
    })
}

答案 3 :(得分:6)

您可以使用我刚刚完成的指令,以防止用户在执行异步操作时多次单击元素

https://github.com/mattiascaricato/angular-click-and-wait

您可以使用 npm bower

将其添加到项目中
npm install angular-click-and-wait

bower install angular-click-and-wait

用法示例

&#13;
&#13;
const myApp = angular.module('myApp', ['clickAndWait']);

myApp.controller('myCtrl', ($scope, $timeout) => {
  $scope.asyncAction = () => {
    // The following code simulates an async action
    return $timeout(() => angular.noop, 3000);
  }
});
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="https://cdn.rawgit.com/mattiascaricato/angular-click-and-wait/master/dist/click-and-wait.js"></script>

<section ng-app="myApp">
  <div ng-controller="myCtrl">
    <button click-and-wait="asyncAction()">Click Me!</button>
  </div>
</section>
&#13;
&#13;
&#13;

  

注意:作为参数传递的异步操作应该是Promise

答案 4 :(得分:3)

我喜欢用户的解决方案:zsong

但是ng-dblclick =&#34;返回false;&#34;在js控制台上出现问题(我使用的是Chrome Windows7),您可以看到错误。

我无法评论(我没有足够的声誉评论他的解决方案)

只使用ng-disabled。

正如您在下面的plunker中看到的那样,如果您有两个功能: ng-click和ng-dblclick 然后双击你将执行: 2次点击和1次dblclick

<bla ng-dblclick="count=count+1" ng-click="count=count+0.1" />

双击为您提供1.2,因此您无法使用ng-dblclick阻止2次点击,只需在第二次点击时再添加一个行为。

Dblclick and click

Jonathan Palumbo在这个帖子中举了一个关于ng-disabled工作的例子。

答案 5 :(得分:2)

我最近不得不这样做,而且我已经把几个解决方案放在了一起。这对我有用,它是一个替代ng-click的指令,只能点击一次。

此解决方案会引发错误,这使得测试非常容易。

.directive('oneClickOnly', [
    '$parse', '$compile', function($parse, $compile) {
        return {
            restrict: 'A',
            compile: function(tElement, tAttrs) {

                if (tAttrs.ngClick)
                    throw "Cannot have both ng-click and one-click-only on an element";

                tElement.attr('ng-click', 'oneClick($event)');
                tElement.attr('ng-dblclick', 'dblClickStopper($event)');

                tElement.removeAttr('one-click-only');
                var fn = $parse(tAttrs['oneClickOnly']);

                return {
                    pre: function(scope, iElement, iAttrs, controller) {
                        console.log(scope, controller);
                        var run = false;
                        scope.oneClick = function(event) {
                            if (run) {
                                throw "Already clicked";
                            }
                            run = true;
                            $(event.toElement).attr('disabled', 'disabled');

                            fn(scope, { $event: event });

                            return true;
                        };
                        scope.dblClickStopper = function(event) {
                            event.preventDefault();
                             throw "Double click not allowed!";
                            return false;
                        };

                        $compile(iElement)(scope);
                    }
                };
            },
            scope: true
        };
    }
])

以下是我的测试(如果有人感兴趣的话)

'use strict';
describe("The One click button directive", function() {
var $scope, testButton, $compile, clickedEvent;
var counter = 0;

beforeEach(function () {
    counter = 0;

    module('shared.form.validation');

    inject(function ($rootScope, _$compile_) {

        $compile = _$compile_;
        $scope = $rootScope.$new();

        $scope.clickEvent = function (event) {
            counter++;
        };
    });
});

it("prevents a button from being clicked multiple times", function () {

    var html = "<a one-click-only='clickEvent()'>test button</a>";
    testButton = $compile(html)($scope);

    $scope.$digest();

    testButton.click();
    expect(function () { testButton.click(); }).toThrow("Already clicked");

    expect(counter).toBe(1);
});

it("doesn't allow ng-click on the same tag", function() {
    var html = "<a ng-click='clickEvent()' one-click-only='clickEvent()'>test button</a>";
    expect(function () { $compile(html)($scope); }).toThrow("Cannot have both ng-click and one-click-only on an element");
});

it("works for multiple buttons on the same scope", function () {

    var counter2 = 0;
    $scope.clickEvent2 = function (event) {
        counter2++;
    };

    var html = "<a one-click-only='clickEvent()'>test button</a>";
    var html2 = "<a one-click-only='clickEvent2()'>test button</a>";
    testButton = $compile(html)($scope);
    var testButton2 = $compile(html2)($scope);

    $scope.$digest();

    testButton.click();
    expect(function () { testButton2.click(); }).not.toThrow("Already clicked");

    expect(counter).toBe(1);
    expect(counter2).toBe(1);
});
});

答案 6 :(得分:1)

根据建议,使用ng-disabled可以解决您的问题。我做了一个插图来说明它here

答案 7 :(得分:1)

详细说明@Jonathan Palumbo的回答(使用ngDisabled)和@andre的问题(&#34;如何在指令中使用它而不是控制器?&#34;):允许点击或提交事件要冒泡,你需要设置“禁用”&#39;在超时函数内以编程方式(即使延迟为0毫秒)将属性指向可点击元素(无论是按钮,链接,跨度还是div),这允许事件在被禁用之前传递:

$timeout(function(){ elm.attr('disabled',true); }, 0);

我指的是@ arun-p-johny的回答:prevent multiple form submissions using angular.js - disable form button

答案 8 :(得分:1)

您可以创建指令以防止双击:

angular.module('demo').directive('disableDoubleClick', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, elem, attrs) {
            elem.bind('click', function(){
                $timeout(function(){
                    elem.attr('disabled','disabled');
                }, 20);

                $timeout(function(){
                    elem.removeAttr('disabled');
                }, 500);
            });
        }
    };
});

你可以在任何可点击的项目上使用它,如下所示:

<button ng-click="clickMe()" disable-double-click >Click Me</button>

答案 9 :(得分:0)

正如其中一个答案中所建议的那样,我尝试使用ng-dbliclick="return false;"来提供JS警告。

Insted我使用了ng-dblclick="return",它工作顺利。虽然这只适用于<form>标记。

答案 10 :(得分:0)

再次扩展zsong的母鹿:

首先,通过此解决方案,人们可以使用双击来使用您的应用。老人有时这样做(因为他们习惯于双击以在Windows上打开程序),而其他人也是这样做的。

其次,用户可以尽快点击,他们的浏览器会在重新启用按钮之前等待服务器响应(这是对Mark Rajcok对zsong帖子的评论的解决方法:“如果AJAX请求花费的时间比浏览器的双击时间/窗口长,那么这将无效。即,用户可以暂停并再次单击。“)。

  你的HTML中的

<ANY
  ng-click="buttonClicked();
            submitButtonDisabled = 1 + submitButtonDisabled;" 
  ng-disabled="submitButtonDisabled > 0;"
  ng-init="submitButtonDisabled = 0;"
>
  控制器中的

$scope.buttonClicked = function() {
    Service.doService.then(function(){
        //this is the callback for success
        // you probably do not want to renable the button here : the user has already sent the form once, that's it - but just if you want to :
        $scope.submitButtonDisabled --;
        //display a thank you message to the user instead
        //...
    }).error(function(){
        //this is the callback for the error
        $scope.submitButtonDisabled --;
    })
}

答案 11 :(得分:0)

我发现一个简单的解决方案,我认为这比其他答案更好的是防止mousedown事件上的浏览器默认行为。

ng-mousedown="$event.preventDefault();"

它不会阻止点击事件,但会阻止双击事件:)

答案 12 :(得分:0)

You can handle the form validation 
    $('form.your-form').validate({
        rules: {
            name: 'required',
            email: {
                required: true,
                email: true
            }
        },
        submitHandler: function (form) {
            // Prevent double submission
            if (!this.beenSubmitted) {
                this.beenSubmitted = true;
                form.submit();
            }
        }
    });