Предотвращение/обращение с двойными нажатиями кнопок в angular
В angular мы можем настроить кнопку для отправки таких запросов ajax, как это:
... ng-click="button-click"
и в контроллере:
...
$scope.buttonClicked = function() {
...
...
// make ajax request
...
...
}
Итак, чтобы предотвратить двойную отправку, я мог установить флаг buttonclicked = true, когда кнопка щелкнула и отменила его, когда завершится обратный вызов ajax. Но даже после этого управление возвращается к angular, который будет обновляться до Dom. Это означает, что есть маленькое окно, где кнопка может быть нажата снова, прежде чем исходный щелчок на кнопке будет полностью завершен на 100%.
Это небольшое окно, но все же может случиться. Любые советы, чтобы полностью избежать этого - клиентская сторона, то есть без каких-либо обновлений для сервера.
Спасибо
Ответы
Ответ 1
Использование ng-disabled
работало просто отлично в этом примере. Независимо от того, как яростно щелкнул консоль, сообщение попало только один раз.
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>
Ответ 2
Сначала вам лучше добавить ngDblclick, когда он обнаружит двойной щелчок, просто верните false
:
<ANY ng-click="buttonClicked()" ng-dblclick="return false">
Если вы хотите дождаться завершения вызова Ajax, вы можете отключить эту кнопку, установив ng-disabled
<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 успешный или неудачный, поскольку, если он не работает, вы не хотите, чтобы он отображался как disabled, чтобы запутать пользователя.
Ответ 3
Я просто расширил код zsong, чтобы добавить проверку в обработчик для флага. Если его true, то просто возвращайтесь, потому что клик уже обрабатывается. Это предотвращает двойные щелчки, не беспокоясь о сроках angular или о том, что происходит.
$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;
})
}
Ответ 4
Вы можете использовать только что законченную директиву, чтобы пользователь не нажимал несколько раз на элемент при выполнении асинхронного действия
https://github.com/mattiascaricato/angular-click-and-wait
Вы можете добавить его в свой проект с помощью npm или bower
npm install angular-click-and-wait
или же
bower install angular-click-and-wait
Пример использования
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);
}
});
<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>
Ответ 5
Мне понравилось решение пользователя: zsong
Но ng-dblclick = "return false;" (я использую Chrome Windows7), на консоли js вы можете увидеть эту ошибку.
Я не могу комментировать (у меня недостаточно репутации, чтобы прокомментировать его решение)
Просто используйте только ng-disabled.
Как вы можете видеть на плунжере ниже, если у вас есть две функции:
ng-click и ng-dblclick
И сделайте двойной щелчок, который вы выполните:
2 раза клик и 1 раз dblclick
<bla ng-dblclick="count=count+1" ng-click="count=count+0.1" />
Двойной щелчок дает вам 1.2, поэтому вы не можете предотвратить 2 щелчка с помощью ng-dblclick, просто добавьте еще одно поведение, когда произойдет второй клик.
Dblclick и нажмите
Джонатан Палумбо привел пример с ng-отключенной работой в этом потоке.
Ответ 6
Недавно мне пришлось это сделать, и я привел пару решений вместе. Это работает для меня, это директива, которая является альтернативой 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);
});
});
Ответ 7
Как было предложено, использование ng-disabled
решит вашу проблему. Я сделал plunker, чтобы проиллюстрировать его здесь.
Ответ 8
чтобы уточнить ответ @Jonathan Palumbo (используйте ngDisabled) и @andre вопрос ( "как использовать это в директиве вместо контроллера?" ): чтобы позволить событию клика или представления пузыриться, вам нужно установить атрибут "отключен" для вашего интерактивного элемента (будь то кнопка, ссылка, span или div) программно внутри функции тайм-аута (даже с задержкой 0 мс), которая позволяет передать событие до его отключения:
$timeout(function(){ elm.attr('disabled',true); }, 0);
Я ссылаюсь на @arun-p-johny answer: предотвращать появление нескольких форм с помощью angular.js - отключить кнопку формы.
Ответ 9
Вы можете создать директиву для предотвращения двойного щелчка:
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>
Ответ 10
Как было предложено в одном из ответов, я попытался использовать ng-dbliclick="return false;"
, который дает предупреждение JS.
Я использовал ng-dblclick="return"
, который работает плавно. Хотя это работает только внутри тега <form>
.
Ответ 11
Чтобы снова развернуть zsong doe:
Во-первых, с помощью этого решения люди могут использовать двойной клик для использования вашего приложения. Старые люди когда-то делают это (поскольку они использовались для двойного щелчка, чтобы открыть программу на окнах), а другие тоже делают это по ошибке тоже.
Во-вторых, пользователь может нажимать так быстро, как только может, их браузер будет ждать ответа сервера, прежде чем снова включить кнопку (это исправление для комментария Марка Райкока к сообщению 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 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 --;
})
}
Ответ 12
Простое решение, которое я нашел, и я думаю, лучше, чем другой ответ здесь, это предотвращение поведения браузера по умолчанию в событии mousedown.
ng-mousedown="$event.preventDefault();"
Он не предотвращает событие click, но предотвращает событие двойного щелчка:)
Ответ 13
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();
}
}
});